python结构化json日志需自定义formatter或用python-json-logger,字段名小写加下划线,显式传exc_info=true,时间用iso 8601,extra键禁用点号,异常和上下文必须完整捕获,字符串需转义防解析失败。

Python structured logging 输出 JSON 的核心约束
logging 本身不生成 JSON,必须靠自定义 Formatter 或第三方库(如 python-json-logger)把日志字段序列化。直接用 json.dumps() 拼字符串容易漏掉异常堆栈、丢失上下文字段,或在多线程下破坏日志结构。
- 日志记录必须走
logger.info()等标准方法,不能手动 print JSON 字符串 - 所有字段名应小写、下划线分隔(如
service_name、trace_id),避免大小写混用导致下游解析失败 -
exc_info=True必须显式传入,否则json.dumps()无法自动捕获异常对象,只会输出空"exc_info": null - 时间字段建议统一用 ISO 8601 格式(
datetime.now().isoformat()),不要依赖%(asctime)s,它默认是字符串且格式不可控
import json
import logging
from datetime import datetime
<p>class JsonFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
"level": record.levelname,
"message": record.getMessage(),
"logger": record.name,
"module": record.module,
"function": record.funcName,
"line": record.lineno,
}
if record.exc_info:
log_entry["exception"] = self.formatException(record.exc_info)
return json.dumps(log_entry)</p>python-json-logger 库的字段映射陷阱
python-json-logger 默认只输出基础字段,比如 levelname、message,但不会自动带 exc_info 或 stack_info。更隐蔽的问题是:它把 extra 字典里的键原样塞进 JSON,但如果键名含点号(如 "request.id"),某些日志平台(如 Datadog、Loki)会把它当嵌套对象解析,实际却只是字符串字段。
- 使用
extra传参时,键名只能用字母、数字、下划线,禁用点、连字符、斜杠 - 若需嵌套结构(如
{"request": {"id": "abc", "path": "/api"}}),必须提前构造好字典再传给logger.info("msg", extra={"request": req_dict}),不能指望库自动展开 -
JsonFormatter初始化时传reserved_attrs可覆盖默认保留字段,但别删掉exc_info,否则异常日志永远不完整 - 不要重命名
levelname为level后就以为符合 OpenTelemetry 规范——OTel 要求的是severity_text和severity_number,得额外加字段
LogRecord extra 字段的生命周期问题
logger.info("msg", extra={"user_id": 123}) 看似简单,但 extra 字典只作用于当前日志条目。如果想让所有日志都带 service_name,不能靠反复传 extra,而要用 LoggerAdapter 或 Filter 注入。
-
LoggerAdapter是最轻量的方式,但它的process()方法里修改extra时,不能覆盖原有键(比如extra["message"]),否则会丢原始消息 -
Filter更底层,适合动态注入(如从 thread-local 获取 trace_id),但要注意它在LogRecord创建后才运行,此时exc_info已固化,无法补异常信息 - 如果用了
structlog,它和logging的extra不兼容,混用会导致字段丢失或重复,选一个体系到底
JSON 日志在生产环境的性能与兼容性雷区
纯文本日志可直接 tail -f 查看,JSON 日志一旦格式出错(比如字段值含未转义换行符),整个日志行就解析失败,Kibana 或 Loki 会显示为空或截断。更麻烦的是,有些云厂商的日志代理(如 AWS CloudWatch Logs Agent)对单行 JSON 长度有限制(默认 256KB),超长日志会被静默截断。
立即学习“Python免费学习笔记(深入)”;
- 所有字符串字段在写入前应做基本清洗:
value.replace("\n", "\n").replace("\r", "\r"),尤其message和exception - 不要在日志里 dump 整个 request body 或数据库 record,用摘要代替(如
"body_size": len(body)) - 测试阶段务必用
json.loads()对每条日志反序列化验证,不能只靠肉眼扫一眼是否“看起来像 JSON” - 如果用
RotatingFileHandler,注意 JSON 行日志不能跨行,必须确保maxBytes不会把一条 JSON 切成两半,否则后续解析全崩
字段命名一致性、异常处理完整性、extra 注入时机、JSON 安全转义——这四点没对齐,日志看着是 JSON,其实下游系统根本没法可靠消费。










