2、Python日志模块:logging模块基础、Handler与Formatter、日志级别与过滤
好,咱们正式开始聊Python的logging模块。说实话,我见过太多团队在日志这块翻车了——要么是日志满天飞,磁盘撑爆;要么是出了线上事故,翻遍日志啥也找不到。说白了,日志写得好不好,直接决定了你半夜被叫起来排查问题的效率。
我个人习惯,不管项目大小,第一件事就是把日志体系搭好。今天咱们就把它彻底讲透。
2.1 logging模块基础:从零开始
Python自带的logging模块,其实已经很强大了。你不需要装任何第三方库,就能搞定90%的日志需求。
先看一个最简单的例子:
import logging
logging.debug("这是debug信息")
logging.info("这是info信息")
logging.warning("这是warning信息")
logging.error("这是error信息")
logging.critical("这是critical信息")
你运行一下试试?你会发现,只有warning及以上的日志被打印出来了。为什么?因为默认的日志级别是WARNING。嗯,这里要注意,初学者经常在这踩坑——写了debug日志,结果啥也没看到,还以为代码没执行。
2.2 日志级别:五个等级,各有用途
Python定义了五个标准日志级别,从低到高分别是:
| 级别 | 数值 | 什么时候用 |
|---|---|---|
| DEBUG | 10 | 调试信息,比如变量值、函数入口出口 |
| INFO | 20 | 正常流程信息,比如服务启动、请求到达 |
| WARNING | 30 | 潜在问题,比如磁盘快满了、重试操作 |
| ERROR | 40 | 出错了但还能继续跑,比如数据库连接失败 |
| CRITICAL | 50 | 系统要挂了,比如内存耗尽、核心服务宕机 |
我在项目中遇到过一种情况:有人把所有日志都写成ERROR级别。结果报警系统天天响,最后大家直接无视了所有ERROR日志。这其实是个管理问题——日志级别不是随便选的,它代表了严重程度。
你可以通过setLevel()来控制日志输出门槛:
import logging
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
logger.debug("这条会输出,因为级别够了")
2.3 Handler:日志该去哪?
日志不能光在控制台看吧?你得把它写到文件里、发到远程服务器、甚至推送到消息队列。Handler就是干这个的。
常见的Handler有这些:
- StreamHandler:输出到控制台
- FileHandler:写入文件
- RotatingFileHandler:按文件大小滚动
- TimedRotatingFileHandler:按时间滚动
- SMTPHandler:发邮件(慎用,容易把邮箱塞爆)
来看一个实际例子:
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("my_service")
logger.setLevel(logging.DEBUG)
# 控制台输出
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# 文件输出,每个文件5MB,保留3个备份
file_handler = RotatingFileHandler(
"app.log", maxBytes=5*1024*1024, backupCount=3
)
file_handler.setLevel(logging.DEBUG)
logger.addHandler(console)
logger.addHandler(file_handler)
2.4 Formatter:日志长啥样?
日志的格式,说白了就是一条字符串。但这条字符串里包含什么信息,差别很大。
我常用的格式模板:
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
)
常用的格式化字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| %(asctime)s | 时间戳 | 2024-01-15 14:30:22,123 |
| %(name)s | Logger名称 | my_service |
| %(levelname)s | 日志级别 | INFO |
| %(filename)s | 文件名 | app.py |
| %(lineno)d | 行号 | 42 |
| %(message)s | 日志内容 | 用户登录成功 |
把Formatter挂到Handler上:
formatter = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(message)s'
)
console.setFormatter(formatter)
file_handler.setFormatter(formatter)
你想想看,如果没有时间戳和行号,线上出了错你都不知道是哪一秒、哪一行代码出的问题。所以,Formatter不是装饰,是刚需。
2.5 日志过滤:只抓你想要的
有时候,你不想看到所有日志。比如,第三方库的debug信息太吵了,或者你只想关注某个模块的ERROR日志。
Filter就是干这个的:
import logging
class SensitiveFilter(logging.Filter):
def filter(self, record):
# 过滤掉包含密码的日志
return "password" not in record.getMessage()
logger = logging.getLogger("my_app")
logger.addFilter(SensitiveFilter())
更常见的场景是按模块过滤:
# 只让my_app模块输出DEBUG,其他模块保持WARNING
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
# 第三方库的日志,保持安静
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)
2.6 完整示例:一个生产级的日志配置
最后,给你一个我常用的模板。直接拿去用,改改路径就行:
import logging
import logging.handlers
import os
def setup_logger(name, log_dir="logs"):
os.makedirs(log_dir, exist_ok=True)
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
# 格式
formatter = logging.Formatter(
'%(asctime)s | %(name)s | %(levelname)-8s | '
'%(filename)s:%(lineno)d | %(message)s'
)
# 控制台:只输出INFO及以上
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
# 文件:输出所有级别,按天滚动,保留30天
file_handler = logging.handlers.TimedRotatingFileHandler(
os.path.join(log_dir, f"{name}.log"),
when="midnight",
interval=1,
backupCount=30
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# 错误日志单独存一份
error_handler = logging.handlers.RotatingFileHandler(
os.path.join(log_dir, f"{name}_error.log"),
maxBytes=10*1024*1024,
backupCount=5
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
logger.addHandler(console)
logger.addHandler(file_handler)
logger.addHandler(error_handler)
return logger
# 使用
logger = setup_logger("order_service")
logger.info("订单服务启动成功")
logger.error("数据库连接超时,重试中...")
这个配置,我用了好几年。控制台看个大概,文件里查细节,错误日志单独追踪。三个Handler各司其职,互不干扰。
好了,logging模块的基础就这些。下一节咱们聊多进程环境下的日志问题——那才是真正考验功底的地方。