structlog 默认不输出 json,必须显式配置处理器:需将 jsonrenderer 加入处理器链末尾,搭配 loggerfactory 和 boundlogger 接管 stdlib logger,用 renameprocessor 重命名字段,asyncioawarejsonrenderer 处理异步渲染,自定义 jsonencoder 避免序列化错误。

Structlog 默认不输出 JSON,必须显式配置处理器
Structlog 本身只是日志事件的结构化组装器,structlog.stdlib.ProcessorFormatter 或 structlog.dev.ConsoleRenderer 这类处理器决定最终输出格式。直接用 logging.basicConfig() 会走默认文本路径,看不到 JSON。
- 必须把
structlog.processors.JSONRenderer加入处理器链,且放在最后(它把字典转成 JSON 字符串) - 推荐搭配
structlog.stdlib.AsyncioAwareJsonRenderer(若用 asyncio)或structlog.processors.TimeStamper(fmt='iso')提前注入时间字段 - 别漏掉
logging.StreamHandler的formatter设置——它要包装 structlog 的处理器,不是反过来
如何让 stdlib logger 也输出 JSON?
很多项目已用 logging.getLogger(__name__),不能重写所有日志调用。这时需用 structlog.stdlib.LoggerFactory 和 structlog.stdlib.BoundLogger 统一接管。
- 调用
structlog.configure()时设logger_factory=structlog.stdlib.LoggerFactory() - 绑定
processor链:至少包含structlog.stdlib.filter_by_level、structlog.stdlib.add_log_level、structlog.stdlib.add_logger_name,再接JSONRenderer - 关键点:
logging.getLogger()返回的 logger 实例,其.info()等方法会被自动代理到 structlog 的BoundLogger,无需改业务代码
timestamp 和 level 字段名被覆盖或丢失怎么办?
默认情况下,JSONRenderer 输出的字段名是 Python 键名(如 "event"、"level"),但有些日志平台(如 ELK、Loki)期望 "@timestamp" 或 "severity"。
- 用
structlog.processors.RenameProcessor重命名字段:RenameProcessor({"timestamp": "@timestamp", "level": "severity"}) - 注意顺序:重命名必须在
JSONRenderer之前,否则它看到的是原始键名 - 如果用了
TimeStamper,确保它的key参数和 RenameProcessor 匹配,例如TimeStamper(key="timestamp")
异步写入导致日志乱序或丢日志?
JSON 渲染后交给 StreamHandler 写 stdout/stderr 是同步阻塞的,高并发下可能拖慢主线程。但盲目套 asyncio 日志 handler 容易出问题。
- 优先用
structlog.stdlib.AsyncioAwareJsonRenderer——它只处理渲染,不碰 I/O,安全 - 真要异步 I/O,建议用
concurrent.futures.ThreadPoolExecutor包装StreamHandler.emit(),而非自己实现 async handler - 避免在
JSONRenderer.__call__()里做耗时操作(比如调用socket.gethostname()),它在每条日志路径上执行
datetime、自定义类实例)时,JSONRenderer 会直接抛 TypeError,这个错误不会被 logging 捕获,而是崩掉整个线程。得提前用 structlog.processors.JSONEncoder 或自定义 encoder 处理。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











