示例说明
让日志带请求上下文的朴素做法是层层传参,函数签名迅速腐化。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},
)