2、Python日志处理基础:内置logging模块、日志级别、日志格式配置
做审计日志巡检,第一关就是搞懂日志到底是怎么写出来的。很多人一上来就写 print(),等上了生产环境才发现——日志没时间戳、没级别、没有代码位置,想查个问题比大海捞针还难。我个人习惯,不管项目大小,先把 Python 内置的 logging 模块搭好。这套东西标准库自带,不用装任何第三方包,而且灵活度足够应对 99% 的场景。
2.1 logging 模块长什么样
说白了,logging 模块就是一套“日志生产流水线”。你写代码的时候往里面丢消息,它帮你过滤、格式化、输出到文件或终端。下面这张图能让你一眼看清它的骨架。
你看,Logger 是你打交道的入口,Handler 决定日志去哪(文件、控制台、网络等),Formatter 决定日志长什么样。Filter 这东西嘛,我平时用得不算多,但遇到要按用户 IP 或者模块名过滤日志时,它特别好使。
2.2 日志级别:从 DEBUG 到 CRITICAL
为什么要有级别?我在项目中遇到过好多次:调试阶段刷了一堆 print,上线前懒得删,结果生产日志里全是无关信息。级别就是用来做“关卡”的,你设一个门槛,低于门槛的日志直接忽略。
Python 内置了 5 个标准级别,按严重程度从低到高排列:
| 级别 | 数值 | 典型用途 | 什么时候用 |
|---|---|---|---|
DEBUG |
10 | 详细诊断信息 | 开发/调试模式,排查问题 |
INFO |
20 | 常规运行信息 | 确认程序按预期运行 |
WARNING |
30 | 潜在问题提醒 | 不影响当前功能,但值得关注 |
ERROR |
40 | 严重错误,功能异常 | 程序无法执行某个操作 |
CRITICAL |
50 | 致命错误,程序可能崩溃 | 整个系统或核心功能不可用 |
小技巧:我一直保持一个习惯——开发环境设 DEBUG,测试环境设 INFO,生产环境设 WARNING。审计日志巡检场景下,建议把 ERROR 和 CRITICAL 级别的日志单独收集到报警系统里。
级别数值是整数,10、20、30、40、50。你可以通过 logger.setLevel(logging.INFO) 来设门槛。嗯,这里要注意:门槛设定是针对 Logger 的,但 Handler 自己也可以再设一道门槛。我曾经踩过这个坑——Logger 设了 INFO,Handler 却设了 ERROR,结果 INFO 信息全丢了。记住,两条关卡是“与”的关系,只有都通过才会输出。
2.3 日志格式配置:让日志有“身份证”
原始日志长啥样?不加格式化的话,默认只有消息文本。对于审计巡检来说,这根本不够用。你需要知道:这条日志什么时候发生的?在哪个模块、哪行代码?进程号是多少?请求的 Trace ID 是什么?
Formatter 就是干这个的。你给它一个格式字符串,它帮你把各种信息填进去。我常用的格式之一:
import logging
fmt = '%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s'
formatter = logging.Formatter(fmt)
常用占位符一览:
| 占位符 | 含义 | 示例输出 |
|---|---|---|
%(asctime)s |
时间戳 | 2025-03-18 14:23:10,456 |
%(levelname)s |
日志级别名称 | INFO / ERROR |
%(name)s |
Logger 名称(通常用模块名) | audit.parser |
%(lineno)d |
日志调用所在行号 | 42 |
%(message)s |
你写的日志消息 | 用户登录失败 |
%(process)d |
进程 PID | 12345 |
%(thread)d |
线程 ID | 140736198343232 |
避坑指南:我曾经因为没加 %(name)s,查半天不知道日志来自哪个模块。审计日志巡检时,建议至少包含:时间、级别、模块名、行号、消息正文。如果涉及多线程,加上进程/线程 ID。
2.4 完整示例:从零配置日志系统
把上面讲的串起来,写一个实用配置。直接看代码:
import logging
# 创建 Logger
logger = logging.getLogger('audit_patrol')
logger.setLevel(logging.DEBUG) # 门槛放最低,由 Handler 细分
# 创建控制台 Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 创建文件 Handler(审计日志专用)
file_handler = logging.FileHandler('patrol_audit.log', encoding='utf-8')
file_handler.setLevel(logging.INFO)
# 创建格式化器
formatter = logging.Formatter(
fmt='%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 绑定
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 使用
logger.info('审计巡检脚本启动完成')
logger.error('连接数据库超时,重试中...')
运行后,终端会输出 INFO 及以上级别的日志,同时所有日志(包括 DEBUG)都会写进 patrol_audit.log。为什么文件用 DEBUG?因为审计日志有时候需要记录更细的调用链路,而终端只显示关键信息。你想想看,这样是不是很灵活?
2.5 实战中容易翻车的地方
- 重复添加 Handler:每次调用
logger.addHandler()都会新增一个,不小心会看到日志重复输出。建议在模块初始化时只做一次。 - 编码问题:FileHandler 默认用系统编码(Windows 可能是 gbk),写中文日志会乱码。记得传
encoding='utf-8'。 - 日志文件轮转:单个日志文件越来越大,可以用
RotatingFileHandler按大小或时间切割。这个后面专讲文件处理时会展开。 - 根 Logger 的默认配置:你没配置时,
logging.info()会调用根 Logger,输出到控制台,格式简单。一旦你自定义了 Logger,根 Logger 可能会“双重输出”。解决办法:logger.propagate = False。
重点提醒:审计日志巡检最怕日志丢、日志乱、日志漏。上面提到的每个细节都可能导致你排查问题时抓瞎。我建议你写完配置后,专门做一轮“日志完整性测试”——手动制造一条 ERROR 日志,确认它出现在正确的位置,带上完整的上下文信息。
好了,掌握这些,日志处理的基础你就基本拿下了。后面章节我们会把它用到真正的审计巡检场景里,比如如何结构化日志、如何按时间切片、如何从海量日志里快速定位异常。一步步来,不急。