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支持多种配置方式,最常用的是dictConfigfileConfig

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模块核心结构。你看一遍,心里就有谱了。

Python logging 模块核心结构 Logger(日志器) 日志级别:DEBUG → INFO → WARNING → ERROR → CRITICAL Handler(处理器) StreamHandler FileHandler Formatter(格式化器) 输出:控制台 / 文件 / 远程 配置:dictConfig / fileConfig

这张图把logging的脉络讲清楚了:Logger负责接收日志 → 判断级别 → 交给Handler → Handler用Formatter格式化 → 输出到目的地。配置方式在最左侧,贯穿整个流程。

一句话总结:Logger是入口,Handler是出口,Formatter是包装,配置文件是管理工具。把这四个东西搞明白,日志系统就稳了。

避坑指南:我曾经犯过一个错误——在多个模块里重复创建同名的logger,导致日志重复输出。解决办法是:在入口文件统一配置,其他地方用logging.getLogger(__name__)获取即可。

好了,logging模块的核心内容就这些。你动手写一遍,比看十遍都管用。遇到问题别慌,先看日志——这是运维人的第一反应。


专注资料整理