Python标准库logging如何不修改内层代码为日志添加trace/correlation ID
实现方案
完全基于Python标准库即可实现,不需要引入任何重型第三方工具,核心依赖logging模块的Filter机制和contextvars(Python 3.7+ 内置标准库)实现线程/协程安全的上下文传递。
第一步:编写通用工具代码
只需要实现一次,全局生效:
import logging from contextvars import ContextVar from contextlib import contextmanager # 全局上下文变量,存储要附加到日志的字段,原生支持线程、协程隔离 _log_context = ContextVar("log_context", default={}) @contextmanager def decorate_logs(**kwargs): # 保存原有上下文,支持嵌套使用 old_context = _log_context.get() new_context = old_context.copy() new_context.update(kwargs) token = _log_context.set(new_context) try: yield finally: # 退出上下文时恢复原有状态 _log_context.reset(token) class ContextInjectFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: # 将上下文变量注入到每条日志的LogRecord对象中 context = _log_context.get() for key, value in context.items(): setattr(record, key, value) # 可选:给未出现在上下文中的字段设置默认值,避免格式化报错 if not hasattr(record, "txn_id"): record.txn_id = "" if not hasattr(record, "trace_id"): record.trace_id = "" return True
第二步:全局配置logging
应用启动时执行一次日志配置,给日志处理器加上上面实现的过滤器即可:
def init_logging(): # 日志格式中直接引用上下文注入的字段 log_formatter = logging.Formatter( "%(asctime)s | %(levelname)s | %(name)s | txn_id:%(txn_id)s | %(message)s" ) # 这里可以是任何日志处理器:StreamHandler、FileHandler、RotatingFileHandler等 stream_handler = logging.StreamHandler() stream_handler.setFormatter(log_formatter) # 注入上下文过滤器 stream_handler.addFilter(ContextInjectFilter()) root_logger = logging.getLogger() root_logger.addHandler(stream_handler) root_logger.setLevel(logging.INFO)
第三步:业务代码使用
完全符合你期望的写法,不需要修改任何内层业务函数的代码:
def process_a_transaction(req): # 进入上下文后,所有触发的日志都会自动带上txn_id字段 with decorate_logs(txn_id=req.txn_id): y = run_a_task(req.data) result = y + 1 logger.info("Processed transaction %s", req.txn_id) return result
特性说明
- 完全无第三方依赖,只使用Python标准库
- 线程、协程安全,支持FastAPI、Django异步、asyncio等所有异步场景
- 支持上下文嵌套,内层上下文新增的字段不会污染外层上下文
- 对现有业务代码零侵入,原有已经定义的logger不需要做任何修改
内容的提问来源于stack exchange,提问作者dingus
相关产品推荐
相关产品推荐

