← 中文资源

简体中文版本

Structlog 上下文绑定日志

用 structlog 的上下文变量绑定请求级字段:每行日志自动携带请求 ID 与租户标识,无需把状态穿透进函数签名。

阅读英文原文

示例说明

让日志带请求上下文的朴素做法是层层传参,函数签名迅速腐化。structlog 的 contextvars 处理器配合 bind_contextvars:在请求入口绑定一次,后续任何深度的日志输出自动合并这些字段——JSON 渲染后的每行都有 request_id 与 tenant,检索时按键过滤即可拼出单次请求的完整轨迹。

技术要点

  • bind_contextvars 一次绑定全程生效
  • exception 处理器自动附带堆栈
  • JSON 渲染让字段可直接检索

代码解读

configure 注册的处理器链依次做上下文合并、级别标注、ISO 时间戳与 JSON 渲染。handle_request 入口绑定请求 ID 与租户;charge_order 深处两行日志无需任何上下文参数即携带这些字段,金额超限走 exception 处理器输出完整堆栈。

使用建议

上下文在请求结束时要显式解绑或用上下文管理器圈定范围。日志量与字段数要有治理,别把整包数据塞进日志。开发态可用 console 渲染器换成彩色输出。

代码示例

import logging
import uuid

import structlog

structlog.configure(
    processors=[
        structlog.contextvars.merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt='iso'),
        structlog.processors.JSONRenderer(),
    ],
    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
    logger_factory=structlog.PrintLoggerFactory(),
)

logger = structlog.get_logger()


def charge_order(order_id: str, amount: int) -> None:
    logger.info('charge_started', order_id=order_id, amount=amount)
    try:
        if amount > 100_000:
            raise ValueError('amount exceeds single-charge limit')
    except ValueError:
        logger.exception('charge_rejected', order_id=order_id)
        raise


def handle_request(request_id: str, order: dict) -> None:
    structlog.contextvars.bind_contextvars(
        request_id=request_id,
        tenant=order['tenant'],
    )
    charge_order(order['id'], order['amount'])


handle_request(
    request_id=str(uuid.uuid4())[:8],
    order={'id': 'ord-91', 'tenant': 'acme', 'amount': 250_000},
)