Python 日志最佳实践:从基础配置到生产级落地的十条军规
Python 日志最佳实践中文指南:避免直接使用 root logger、集中式日志配置、正确使用日志级别、占位符惰性求值、异常堆栈记录、JSON 结构化、ISO-8601 时间戳、敏感数据脱敏、日志轮转与集中化管理,并给出观测云平台的落地方案。
Python 的 logging 模块功能强大但灵活性高,团队协作中如果没有统一规范,日志很快就会变得格式混乱、级别滥用、敏感信息泄漏。本文总结十条经过生产验证的 Python 日志最佳实践,帮助团队写出"出事时真能救命"的日志。
核心要点速览
- 永远不要直接用 root logger 写日志:用
getLogger(__name__)创建命名 logger,才能按模块精细控制。 - 配置集中在应用入口做一次:库代码只获取 logger,绝不添加 handler;应用用 dictConfig 统一配置。
- 异常日志必须带堆栈:
logger.exception()或exc_info=True,否则排查时缺少最关键的上下文。 - 结构化 + 轮转 + 集中化三件套:JSON 输出、RotatingFileHandler 防磁盘写满、接入观测云统一存储检索告警。
1. 为什么不要直接用 root logger?
logging.info(...) 这种模块级函数操作的是 root logger,问题很多:无法区分日志来源模块、级别一刀切、配置会被任何第三方库污染。
正确做法——每个模块创建自己的命名 logger:
import logging
logger = logging.getLogger(__name__)
def process_order(order_id):
logger.info("开始处理订单 %s", order_id)
__name__ 自动带上包路径(如 shop.services.order),形成层级结构,之后可以单独调高某个模块的级别,比如在排查时只给 shop.services 开 DEBUG。
2. 为什么日志配置要集中在入口做一次?
日志配置(handler、formatter、级别)应该只在应用启动时配置一次,推荐用 dictConfig 写声明式配置:
# logging_config.py
from logging.config import dictConfig
def setup_logging():
dictConfig({
"version": 1,
"disable_existing_loggers": False,
"formatters": {"default": {"format": "[%(asctime)s] %(levelname)s %(name)s: %(message)s"}},
"handlers": {"console": {"class": "logging.StreamHandler", "formatter": "default"}},
"root": {"level": "INFO", "handlers": ["console"]},
})
库代码的铁律:只 getLogger,永不 basicConfig、永不 addHandler。如果你在写被他人引用的库,添加 handler 会导致使用方的日志重复输出。给库 logger 挂一个 NullHandler 即可。
3. 如何正确使用日志级别?
级别用错是最常见的问题——要么一切 INFO 淹没关键信息,要么一切 ERROR 让告警失灵:
| 级别 | 什么时候用 |
|---|---|
| DEBUG | 开发和深度排查用的细节,生产默认关闭 |
| INFO | 关键业务节点:启动完成、订单创建、任务结束 |
| WARNING | 不影响运行但值得注意:配置缺失用了默认值、API 弃用 |
| ERROR | 功能失败但应用还活着:单次请求失败、外部调用超时 |
| CRITICAL | 系统级严重问题:启动失败、核心依赖不可用 |
级别是后续过滤、聚合和告警的维度。在观测云中,Pipeline 提取的标准 status 字段直接驱动告警规则——乱用级别等于让告警体系失效。
4. 为什么日志消息要用占位符而不是 f-string?
# 不推荐:无论日志是否输出,字符串拼接都会执行
logger.debug("处理用户 %s 的订单 %s" % (user, order))
logger.debug(f"处理用户 {user} 的订单 {order}")
# 推荐:惰性求值,级别不够时完全不拼接
logger.debug("处理用户 %s 的订单 %s", user, order)
把变量作为参数传给日志方法,只有当日志真的会被输出时才做字符串格式化。高频路径上的 DEBUG 日志用占位符能省掉大量无谓的字符串运算,也让消息模板固定、便于聚类分析。
另外,消息要写清"发生了什么 + 关键上下文 ID":"支付回调处理成功 order_id=A123 amount=99.00" 远好于 "处理成功"。
5. 为什么异常日志必须带堆栈?
try:
result = charge(user, amount)
except PaymentError:
logger.exception("扣款失败 user=%s", user.id) # 自动带完整堆栈
raise
logger.exception() 等价于 logger.error(..., exc_info=True)。没有堆栈的错误日志只能告诉你"出事了",有堆栈才能告诉你"在哪出的、怎么出的"。对于未捕获的全局异常,用 sys.excepthook 兜底记录 CRITICAL。
6. 为什么要用 JSON 结构化日志?
纯文本日志给人看还行,给机器分析就是灾难。生产环境用 python-json-logger 输出 JSON:
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(levelname)s %(name)s %(message)s",
}
}
每条日志变成可解析的字段集合,级别、模块、消息、自定义字段都能直接过滤聚合,不需要脆弱的正则。观测云 DataKit 对 JSON 日志自动解析为字段,配合 Pipeline 可进一步标准化 time 和 status。
7. 为什么时间戳要统一标准?
默认的 %(asctime)s 输出 2026-08-25 14:30:22,123 这种格式——不带时区、不是标准格式。生产建议统一 ISO-8601 带时区格式(如 2026-08-25T14:30:22.123+08:00),多时区部署时尤为重要。日志平台解析时间字段时,歧义格式会导致事件时间错乱。
8. 如何防止敏感数据进入日志?
日志是数据泄漏的重灾区:密码、身份证号、银行卡、Token 一旦写入日志并同步到各处,合规风险极大。军规:
- 立法禁止在日志中打印密码、密钥、完整卡号、身份证;
- 必须记录时做掩码(如手机号
138****5678); - 在应用侧自定义 Filter 做正则脱敏,作为第一道防线;
- 平台侧启用观测云敏感数据扫描,内置 70+ 规则在存储前自动脱敏,双重保险。
9. 为什么必须配置日志轮转?
不做轮转的日志文件迟早把磁盘写满,直接把应用搞挂。用 RotatingFileHandler(按大小)或 TimedRotatingFileHandler(按时间):
"handlers": {
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "logs/app.log",
"maxBytes": 20 * 1024 * 1024,
"backupCount": 10,
"encoding": "utf-8",
}
}
容器化部署更简单:只写 stdout,轮转交给容器运行时,采集交给 DataKit。
10. 为什么要集中化管理日志?
应用一旦多实例部署,登录每台机器 grep 日志的方式就彻底失效了。集中化日志管理的价值:
- 一处检索所有实例:观测云日志查看器按
service、host、status任意过滤; - 聚类分析:自动把海量日志按模式聚类,新出现的异常模式一眼可见;
- 告警闭环:监控器对 ERROR/5xx 日志设阈值,告警策略路由到钉钉/企业微信/飞书;
- 成本可控:多索引 + 存储策略,错误日志长保留、调试日志短保留、历史数据归档对象存储;
- 可观测性打通:日志与 APM 链路、指标关联,trace_id 一键跳转。
常见问题(FAQ)
Python 项目应该什么时候开始规范日志?
从项目第一天。用 getLogger(__name__) + 入口 dictConfig 的成本几乎为零,等项目大了再补规范,改造成本极高。
开发和生产环境日志配置怎么区分?
把 dictConfig 的字典做成函数参数或环境变量驱动:开发用控制台 + DEBUG + 人类可读格式;生产用 JSON + INFO + 文件/stdout,由 DataKit 采集。同一份代码,配置随环境切换。
第三方库的日志太吵怎么办?
在 dictConfig 的 loggers 里单独调高它的级别,例如 "urllib3": {"level": "WARNING"}。命名 logger 的层级结构让这种精细化控制成为可能——这正是实践 1 的价值。
这十条和十二要素应用(12-Factor App)的日志原则冲突吗?
不冲突。十二要素要求应用把日志当作事件流写到 stdout、不自己管存储——本文的轮转一条主要面向传统主机部署;容器化部署时遵循十二要素,stdout + DataKit 采集即可,两条路线在观测云上汇合。
系列阅读
- 上一篇:Flask 日志实战
- 下一篇:Python 日志库六款横向对比
- 相关阅读:日志最佳实践总论 | 日志中的敏感数据处理