Python 日志模块 logging 的最佳实践
刚学 Python 时排错全靠 print,上线后才发现:print 的东西重定向到哪了?时间戳呢?日志级别呢?切到 logging 只需要改个习惯,收益巨大。这篇是我的常用模板。
基础配置:一次到位
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger(__name__)
log.info("服务启动")
log.warning("磁盘使用率超过 80%")
log.error("数据库连接失败", exc_info=True)
exc_info=True 会自动把异常堆栈打出来,排错时救命。注意 getLogger(__name__) 用模块名,日志里能直接定位到哪个文件哪一行。
生产环境:按天切分 + 同时输出到文件
日志不能一直往一个文件里写,用 TimedRotatingFileHandler 按天切:
import logging
from logging.handlers import TimedRotatingFileHandler
log = logging.getLogger("myapp")
log.setLevel(logging.INFO)
fmt = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s"
)
# 每天一个文件,保留 30 天
fh = TimedRotatingFileHandler(
"/var/log/myapp/app.log",
when="midnight",
backupCount=30,
encoding="utf-8",
)
fh.setFormatter(fmt)
log.addHandler(fh)
# 同时输出到控制台,docker logs 能直接看到
ch = logging.StreamHandler()
ch.setFormatter(fmt)
log.addHandler(ch)
几个容易踩的坑
- 重复打印:同一个 logger 加了两次 handler,或者子 logger 向上传播到 root 又打一遍。检查
log.handlers,或者设log.propagate = False - basicConfig 不生效:如果之前已经有人调过 basicConfig(比如引用的第三方库),再调就没效果了。解决办法是别依赖 basicConfig,自己配 handler
- 多进程写同一个文件:TimedRotatingFileHandler 不是进程安全的,多进程部署用
concurrent-log-handler第三方库,或者每个进程写自己的文件 - 日志级别:开发用 DEBUG,上线用 INFO。别在生产环境开 DEBUG,磁盘会哭
日志格式建议
我的格式固定包含:时间、级别、模块名、行号、消息。 structured logging(json 格式)更适合上 ELK 的场景,小项目文本格式够了,grep 起来方便。
print 是调试工具,logging 是工程工具。项目超过 500 行,就该换 logging 了。