4、Python日志处理:logging模块详解、日志分级、Handler与Formatter、配置文件化
日志这东西,说白了就是程序的「黑匣子」。
我刚做运维那会儿,线上服务挂了,只能靠print大法定位问题。后来被坑了几次——print输出太多刷屏,关键信息反而找不到。嗯,从那以后,我老老实实用上了logging模块。
4.1 为什么不用print?
你想想看,print输出到控制台,项目上线了怎么办?总不能盯着屏幕看吧。而且print没法分级,调试信息、警告信息、错误信息全混在一起,排查问题跟大海捞针一样。
logging模块就不一样了:
- 可以控制输出级别——开发时看debug,线上只看error
- 可以同时输出到文件和控制台
- 可以格式化输出内容,带上时间、文件名、行号
- 可以按大小或时间自动切割日志文件
核心观点:生产环境禁止使用print,必须用logging。这是我踩过的坑,也是团队的血泪教训。
4.2 日志分级:从DEBUG到CRITICAL
Python内置了5个日志级别,从低到高分别是:
| 级别 | 数值 | 使用场景 |
|---|---|---|
| DEBUG | 10 | 开发调试,详细到变量值、函数调用 |
| INFO | 20 | 正常流程记录,比如「服务启动成功」 |
| WARNING | 30 | 不影响运行但需要注意,比如「磁盘使用率超过80%」 |
| ERROR | 40 | 功能出错,但程序还能继续跑 |
| CRITICAL | 50 | 严重错误,程序可能崩溃 |
我个人习惯:开发环境设成DEBUG,测试环境设成INFO,生产环境设成WARNING或ERROR。这样既不会漏掉关键信息,也不会被调试日志刷屏。
小技巧:设置日志级别时,低于设定级别的日志会被忽略。比如设成WARNING,那DEBUG和INFO就不会输出。
4.3 Handler:日志去哪儿?
Handler决定了日志的「目的地」。你可以把日志同时写到文件、控制台、甚至通过邮件发出去。
常用的Handler有这些:
- StreamHandler:输出到控制台
- FileHandler:输出到文件
- RotatingFileHandler:按文件大小切割日志
- TimedRotatingFileHandler:按时间切割日志
我曾经遇到一个线上事故:日志文件涨到几十个G,磁盘直接爆了。从那以后,我强制要求所有项目必须用RotatingFileHandler,设置最大10MB,保留5个备份。
import logging
from logging.handlers import RotatingFileHandler
# 创建一个logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 文件Handler,按大小切割
file_handler = RotatingFileHandler(
'app.log',
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
file_handler.setLevel(logging.WARNING)
# 控制台Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 给logger添加Handler
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 使用
logger.debug('这是调试信息')
logger.info('这是普通信息')
logger.warning('这是警告')
logger.error('这是错误')
logger.critical('这是严重错误')
注意:每个Handler可以设置不同的级别。比如控制台输出所有信息,文件只记录WARNING以上。这样开发时看控制台,出问题查文件日志。
4.4 Formatter:日志长什么样?
Formatter就是日志的「化妆师」。你可以控制日志的格式,比如加上时间、文件名、行号。
常用的格式化字段:
%(asctime)s— 时间%(name)s— logger名称%(levelname)s— 日志级别%(message)s— 日志内容%(filename)s— 文件名%(lineno)d— 行号
# 定义格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
)
# 应用到Handler
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
输出效果大概是这样的:
2024-01-15 14:30:25,123 - my_app - WARNING - main.py:42 - 磁盘使用率超过80%
嗯,这个格式我用了好几年,信息够全,排查问题一目了然。
4.5 配置文件化:一劳永逸
每次写代码都手动配置logger,太累了。我建议用配置文件来管理日志配置。
Python支持多种配置方式,最常用的是dictConfig和fileConfig。
4.5.1 使用字典配置
import logging.config
LOGGING_CONFIG = {
'version': 1,
'formatters': {
'default': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
},
'detailed': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
'formatter': 'default'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'level': 'WARNING',
'formatter': 'detailed',
'filename': 'app.log',
'maxBytes': 10485760,
'backupCount': 5
}
},
'loggers': {
'my_app': {
'level': 'DEBUG',
'handlers': ['console', 'file']
}
}
}
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger('my_app')
4.5.2 使用INI文件配置
# logging.ini
[loggers]
keys=root,my_app
[handlers]
keys=console_handler,file_handler
[formatters]
keys=default_formatter,detailed_formatter
[logger_root]
level=DEBUG
handlers=console_handler
[logger_my_app]
level=DEBUG
handlers=console_handler,file_handler
qualname=my_app
[handler_console_handler]
class=StreamHandler
level=DEBUG
formatter=default_formatter
args=(sys.stdout,)
[handler_file_handler]
class=handlers.RotatingFileHandler
level=WARNING
formatter=detailed_formatter
args=('app.log', 'a', 10485760, 5)
[formatter_default_formatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
[formatter_detailed_formatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s
# 加载配置文件
import logging.config
logging.config.fileConfig('logging.ini')
logger = logging.getLogger('my_app')
我的建议:小项目用字典配置,大项目用INI文件。配置文件化的好处是——改日志级别不用改代码,重启服务就行。
4.6 知识体系总览
下面这张图,是我梳理的logging模块核心结构。你看一遍,心里就有谱了。
这张图把logging的脉络讲清楚了:Logger负责接收日志 → 判断级别 → 交给Handler → Handler用Formatter格式化 → 输出到目的地。配置方式在最左侧,贯穿整个流程。
一句话总结:Logger是入口,Handler是出口,Formatter是包装,配置文件是管理工具。把这四个东西搞明白,日志系统就稳了。
避坑指南:我曾经犯过一个错误——在多个模块里重复创建同名的logger,导致日志重复输出。解决办法是:在入口文件统一配置,其他地方用logging.getLogger(__name__)获取即可。
好了,logging模块的核心内容就这些。你动手写一遍,比看十遍都管用。遇到问题别慌,先看日志——这是运维人的第一反应。