2. Python日志基础:logging模块核心概念
日志这东西,说白了就是程序的「黑匣子」。
我刚做运维那会儿,线上服务出了问题,第一反应就是翻日志。但那时候的日志写得乱七八糟,有的用print,有的自己写文件,查个问题跟大海捞针似的。后来我学乖了——统一用Python的logging模块,这才算真正入了门。
2.1 为什么非要用logging?
你可能会问:我直接用print不香吗?
嗯,小脚本确实可以。但生产环境里,print有几个硬伤:
- 没法控制输出级别——调试信息跟错误信息混在一起
- 没法灵活配置——想同时输出到文件和终端?得自己写两套逻辑
- 没法格式化——时间戳、进程ID、行号,这些信息print写起来太累
logging模块就是来解决这些问题的。它把日志这件事抽象成了几个核心组件,你只需要搭积木一样组合起来就行。
2.2 四大核心组件
logging模块有四个核心概念:Logger、Handler、Formatter、Filter。
我习惯把它们比作一个「日志流水线」:
| 组件 | 角色 | 一句话理解 |
|---|---|---|
| Logger | 日志记录器 | 你代码里调用的那个对象,比如 logger.info() |
| Handler | 日志处理器 | 决定日志去哪——文件、终端、网络 |
| Formatter | 日志格式化器 | 决定日志长什么样——时间戳、级别、消息 |
| Filter | 日志过滤器 | 决定哪些日志能过,哪些不能过 |
2.3 Logger:你的日志入口
Logger是你直接打交道的对象。我一般这样创建:
import logging
# 创建一个logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
这里有个坑——getLogger传的参数是logger的名字,不是日志文件路径。名字可以随便起,但建议用模块名,比如__name__。
__name__。这样日志里能清楚看到是哪个模块输出的,排查问题快很多。
2.4 Handler:日志去哪了?
Handler决定日志的「目的地」。常用的有:
StreamHandler——输出到终端(标准输出)FileHandler——输出到文件RotatingFileHandler——按文件大小滚动TimedRotatingFileHandler——按时间滚动
举个例子,我想同时输出到终端和文件:
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)
# 把处理器加到logger上
logger.addHandler(console_handler)
logger.addHandler(file_handler)
2.5 Formatter:日志长什么样?
Formatter就是日志的「化妆师」。我常用的格式:
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
常用的格式化字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| %(asctime)s | 时间戳 | 2024-01-15 10:30:45,123 |
| %(name)s | logger名字 | my_app |
| %(levelname)s | 日志级别 | INFO, ERROR |
| %(message)s | 日志内容 | 用户登录成功 |
| %(filename)s | 文件名 | app.py |
| %(lineno)d | 行号 | 42 |
Formatter要绑定到Handler上:
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
2.6 Filter:精准控制日志
Filter用得相对少,但关键时刻很管用。比如我只想记录某个模块的ERROR日志:
class MyFilter(logging.Filter):
def filter(self, record):
return record.name == 'my_app' and record.levelno == logging.ERROR
handler.addFilter(MyFilter())
你想想看,如果日志量特别大,用Filter可以精准筛选,避免磁盘被撑爆。
2.7 日志级别:从DEBUG到CRITICAL
Python定义了五个级别,从低到高:
| 级别 | 数值 | 什么时候用? |
|---|---|---|
| DEBUG | 10 | 调试信息,开发时用 |
| INFO | 20 | 正常操作信息,比如「用户登录」 |
| WARNING | 30 | 警告,不影响运行但需要注意 |
| ERROR | 40 | 错误,功能不可用 |
| CRITICAL | 50 | 严重错误,程序可能崩溃 |
级别是「向上包含」的。比如你设置级别为WARNING,那WARNING、ERROR、CRITICAL都会记录,DEBUG和INFO不会。
2.8 完整示例:一个生产级的日志配置
最后,给你看一个我常用的配置模板:
import logging
from logging.handlers import RotatingFileHandler
def setup_logger(name, log_file='app.log', level=logging.INFO):
logger = logging.getLogger(name)
logger.setLevel(level)
# 文件处理器,按10MB滚动,保留5个备份
file_handler = RotatingFileHandler(
log_file, maxBytes=10*1024*1024, backupCount=5
)
file_handler.setLevel(level)
# 终端处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)
# 格式化器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
# 使用
logger = setup_logger('my_service')
logger.info('服务启动成功')
logger.error('数据库连接失败')
这个配置里,文件记录所有INFO及以上日志,终端只显示WARNING及以上。文件按大小滚动,不会无限增长。
嗯,logging模块的基础就这些。说白了就是四个组件搭积木——Logger是入口,Handler是出口,Formatter是造型,Filter是安检。你把这四个搞明白了,日志这块就算入门了。
下一章我们聊聊怎么把这些基础应用到实际监控场景中,到时候会更有意思。