Python 结构化日志入门:先让日志能被查,再谈好看
出问题时去翻日志,最怕的不是没有日志,而是日志只有人能看懂、机器很难查。一句“订单处理失败了”,既没说清是哪个订单,也没说清失败在哪一步,最后只能靠猜。结构化日志要解决的,就是把每条日志变成一条结构固定的记录:字段相同、含义相同,事后可以按字段过滤、统计和关联。
先定字段,再写日志。
一条可用的结构化日志,至少要有时间、级别、服务名、事件名和消息。时间建议用带时区的 ISO 格式,避免本地时间在跨时区排查时产生歧义。级别不要滥用:INFO 记录正常的关键步骤,WARNING 记录可恢复的异常,ERROR 只留给需要人介入的失败。事件名要用固定动词,比如 order_created、payment_failed,而不是每次换一种说法。
再加两个能把一次请求串起来的字段:request_id 和 user_id。request_id 由入口生成并在调用链中透传,这样前端报错、网关日志和后端日志就能对上同一条链路。user_id 则方便确认影响范围,但要注意只记标识,不要把密码、令牌、完整手机号或身份证号写进日志。
在 Python 里,不需要一开始就引入重型框架。标准库 logging 加一个把记录转成 JSON 的 Formatter 就够入门:
import json, logging
class JsonFormatter(logging.Formatter):
def format(self, record):
return json.dumps({
"time": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"event": getattr(record, "event", ""),
"message": record.getMessage(),
}, ensure_ascii=False)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger = logging.getLogger("order")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("订单已创建", extra={"event": "order_created"})
这样输出的每行都是一个 JSON 对象,可以直接被日志平台解析。排查时不再用肉眼逐行翻,而是直接查 event=payment_failed,再按 request_id 看同一请求的完整链路。
还有三个容易被忽略的细节。第一,不要在循环里打高频 DEBUG 日志,量一大就会把真正有用的信息淹没,也会拖慢服务。第二,日志要轮转和设上限,避免磁盘被打满;这是运维成本,不是可选项。第三,开发环境可以保留更易读的格式,但生产环境尽量统一为 JSON,不要让两种格式混在同一个采集链路里。
最后给自己一个自查清单:这条日志离开上下文还能看懂吗?能按一个字段过滤吗?能和上下游的日志关联吗?三个问题都答 yes,才算合格。结构化日志不是为了好看,而是为了在出问题时少猜一次、多快一步。
- 点赞
- 收藏
- 关注作者
评论(0)