Python日志模块入门:logging模块基础与日志级别
日志这东西,说实话,我早年做项目时根本没当回事。那时候觉得「程序能跑就行,记什么日志」。直到有一次线上出了个诡异的bug,查了三天没头绪,最后发现是某个第三方库在特定条件下抛了个异常——但没有任何记录。嗯,从那以后,我再也不敢轻视日志了。
今天我们就来聊聊Python自带的logging模块。别小看它,虽然标准库里的东西看起来简单,但用好了,能帮你省下大把排查问题的时间。
为什么非要用logging,而不是print?
很多新手喜欢用print来调试。我个人习惯是:开发阶段用print没问题,但生产环境必须上logging。为什么?
- print输出到控制台,没法控制级别。你想想看,线上日志里如果全是调试信息,真正重要的错误反而被淹没了。
- logging可以输出到文件、网络、邮件等多种地方。我在项目中就遇到过需要把错误日志实时发到企业微信的场景,print根本做不到。
- logging支持格式化、过滤、分级。说白了,它就是为生产环境设计的。
核心观点:print是调试工具,logging是运维工具。两者定位不同,别混为一谈。
日志的五个级别:从DEBUG到CRITICAL
Python的logging模块定义了五个标准级别。我按严重程度从小到大给你捋一遍:
| 级别 | 数值 | 什么时候用 |
|---|---|---|
| DEBUG | 10 | 开发调试时用的详细信息,比如变量值、函数入口出口 |
| INFO | 20 | 程序正常运行的信息,比如「服务启动成功」「用户登录」 |
| WARNING | 30 | 发生了预期之外的事情,但程序还能继续跑。比如磁盘空间不足80% |
| ERROR | 40 | 发生了错误,某个功能无法正常执行。比如数据库连接失败 |
| CRITICAL | 50 | 严重错误,程序可能无法继续运行。比如内存耗尽、配置文件损坏 |
我的经验:生产环境通常把日志级别设为INFO或WARNING。DEBUG日志太啰嗦,会拖慢性能。我曾经在一个高并发项目里开了DEBUG日志,结果日志文件每小时涨10GB……后来被运维同事骂了一顿。
快速上手:最简单的logging用法
先来个最基础的例子。你不需要任何配置,直接就能用:
import logging
logging.debug('这是调试信息')
logging.info('这是普通信息')
logging.warning('这是警告信息')
logging.error('这是错误信息')
logging.critical('这是严重错误')
运行一下你会发现——只有WARNING及以上级别的日志输出了。为什么?因为默认的日志级别是WARNING。说白了,DEBUG和INFO被过滤掉了。
要改级别也很简单:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug('现在这条能看到了')
注意:basicConfig()只能在程序启动时调用一次。如果你在多个地方调用,后面的调用会失效。我见过有人写循环里调basicConfig的,结果日志全乱了。
日志记录器、处理器、格式化器——三件套
刚才的例子用的是根日志记录器(root logger)。但在真实项目中,我建议你创建自己的日志记录器。这样不同模块的日志可以分开管理。
来看一个完整的例子:
import logging
# 创建日志记录器
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 创建文件处理器
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
# 创建格式化器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 把格式化器挂到处理器上
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 把处理器挂到日志记录器上
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 开始使用
logger.info('应用启动成功')
logger.error('数据库连接失败,重试中...')
这段代码做了几件事:
- 创建了一个叫my_app的日志记录器
- 控制台只输出INFO及以上级别的日志
- 文件里记录所有DEBUG及以上级别的日志
- 每条日志都带时间、记录器名、级别和消息
你想想看,这样是不是比print灵活多了?线上排查问题时,我经常把某个模块的日志级别临时调到DEBUG,看完再调回去,完全不影响其他模块。
格式化字符串:日志里该记什么?
日志格式决定了你事后能不能快速定位问题。我个人习惯至少包含这些字段:
format = '%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s'
解释一下:
%(asctime)s:时间戳,精确到毫秒%(levelname)-8s:日志级别,左对齐占8个字符%(name)s:%(lineno)d:哪个模块的哪一行%(message)s:具体的日志消息
输出效果大概是这样的:
2025-01-15 14:23:45,123 | INFO | order_service:56 | 订单创建成功,订单号:20250115001
一个小技巧:在日志消息里加上关键的业务ID(比如订单号、用户ID)。这样你grep日志时能快速找到关联记录。我在排查分布式系统问题时,全靠trace_id把不同服务的日志串起来。
避坑指南:新手最容易犯的三个错误
讲几个我踩过的坑:
- 重复日志:如果你不小心给同一个日志记录器添加了多个相同的处理器,日志会重复输出。我曾经在初始化函数里调了两次addHandler,结果每条日志都打印两遍。
- 日志文件不轮转:默认的FileHandler不会自动切割日志文件。线上跑几天,日志文件能到几个GB。用RotatingFileHandler或TimedRotatingFileHandler代替。
- 在日志里拼接字符串:别写
logger.info('用户' + user_id + '登录')。用logger.info('用户 %s 登录', user_id)。后者只在日志真正输出时才格式化字符串,性能更好。
知识体系总览
下面这张图帮你理清logging模块的核心结构:
这张图展示了logging模块的层级关系。一个Logger可以挂多个Handler,每个Handler可以独立设置级别和格式。比如你让控制台只显示WARNING以上,但文件里记录所有DEBUG信息——完全没问题。
小结
今天的内容其实就三件事:
- 五个日志级别,从DEBUG到CRITICAL,选对级别很重要
- Logger、Handler、Formatter三件套,各司其职
- 别用print替代logging,生产环境会吃亏
嗯,logging模块的基础就这些。下一节我们会聊怎么把日志写到文件里,以及如何处理日志文件的轮转——毕竟线上日志不能无限增长,对吧?
公众号:蓝海资料掘金营,微信deep3321