Python 结构化日志实践:从 print 调试到可检索的日志系统
上线后排查问题时,散落在各处的 print 往往帮不上忙:没有时间、没有级别、没有请求上下文,出了问题只能凭印象翻代码。本文用 Python 标准库 logging,从零搭建一套可检索的结构化日志方案,适合中小型服务直接复用。
一、为什么 print 不够用
print 只能输出文本,无法区分 DEBUG、INFO、WARNING、ERROR;无法统一格式;无法按模块控制级别;更难把日志交给日志平台按字段检索。logging 的核心是三件套:Logger 负责发日志,Handler 负责输出到哪里,Formatter 负责输出成什么样子。
二、最小可用的结构化配置
先定义一个 JSON 友好的 Formatter,把时间、级别、模块、消息和关键字段固定下来:
import json
import logging
class JsonFormatter(logging.Formatter):
def format(self, record):
payload = {
"time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"module": record.name,
"message": record.getMessage(),
}
payload.update(getattr(record, "fields", {}) or {})
return json.dumps(payload, ensure_ascii=False)
def setup_logging(level=logging.INFO):
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
root = logging.getLogger()
root.handlers = [handler]
root.setLevel(level)
调用时用 extra 携带业务字段,而不是把变量拼进消息里:
setup_logging()
logger = logging.getLogger("order")
logger.info("订单创建成功", extra={"fields": {"order_id": "20261101001", "user_id": 1001}})
这样日志平台可以直接按 order_id 过滤,而不用写正则从一句话里抠字段。
三、级别与模块划分的实用规则
- DEBUG:只在本地和联调时打开,记录分支、入参摘要和中间结果,注意不要输出密码、Token 等敏感信息。
- INFO:记录业务里程碑,例如订单创建、支付回调收到、任务开始与结束。
- WARNING:服务还能继续,但出现了值得注意的异常,例如重试、降级、配置缺失用了默认值。
- ERROR:当前请求或任务失败,需要人工介入;务必带上可定位的业务标识。
按模块取 Logger,例如 logging.getLogger(“order”)、logging.getLogger(“payment”),上线后可以只把某个模块调到 DEBUG,而不用全量打开。
四、请求上下文:一次请求一个标识
多用户并发时,只看时间很难把日志串起来。可以用 contextvars 给每个请求分配 request_id,并在 Formatter 中自动带上:
import contextvars
request_id = contextvars.ContextVar("request_id", default="-")
class ContextFormatter(JsonFormatter):
def format(self, record):
record.fields = {"request_id": request_id.get(), **(getattr(record, "fields", {}) or {})}
return super().format(record)
在 Web 框架的中间件里设置 request_id,之后该请求产生的所有日志都会自动带上它,排查时按 request_id 一搜就是完整链路。
五、轮转与输出:不要让日志写爆磁盘
本地开发用控制台即可;生产环境建议用 RotatingFileHandler 或 TimedRotatingFileHandler,例如按天轮转、保留 7 天,并把级别设为 INFO 以上。更进一步,可以把日志直接发往集中日志服务,由平台做检索、告警和统计,应用本身不再操心文件清理。
六、常见坑
- 在库代码里调用 basicConfig 会干扰主程序配置,库应该只 getLogger,不配置 Handler。
- 不要在日志里输出完整请求体、身份证号、银行卡号等敏感数据,必要时先脱敏。
- 异常不要只记一句话,ERROR 级别建议用 logger.exception 记录堆栈,方便复现。
- 日志级别不是越多越好,INFO 应该是“读一遍能讲清业务走向”的密度。
结语
结构化日志的关键不是格式多漂亮,而是每条日志都能回答三个问题:什么时候、哪个模块、哪个业务对象出了什么事。先把时间、级别、模块和业务标识固定下来,再补请求上下文和轮转策略,一套能长期维护的日志系统就成型了。
- 点赞
- 收藏
- 关注作者
评论(0)