日志配置实战:从入门到优雅

说实话,很多开发者对日志配置的态度就是「能用就行」。我以前也这么想,直到有一次线上排查问题,看着满屏的 INFO 日志,愣是找不到关键错误信息。那感觉,就像在垃圾堆里找一枚硬币。

今天咱们就把日志配置这件事彻底讲透。我会从最基础的 basicConfig 开始,一步步带你写出既专业又灵活的日志系统。

一、basicConfig:快速上手的利器

Python 的 logging.basicConfig 是最简单的配置方式。我刚开始用的时候,觉得它太方便了,一行代码搞定所有。

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='app.log',
    filemode='a'
)

logger = logging.getLogger(__name__)
logger.info('系统启动成功')

这段代码做了几件事:

  • 设置日志级别为 INFO,低于 INFO 的 DEBUG 日志会被忽略
  • 定义了日志格式,包含时间、日志器名称、级别和消息
  • 将日志输出到文件 app.log,追加模式
注意:basicConfig 只能在根日志器上调用一次。如果你在多个模块里都调用了它,后面的调用会失效。我曾经因为这个坑,调试了半天才发现是配置被覆盖了。

basicConfig 有个明显的短板——它只能配置一个输出目标。你想同时输出到文件和终端?不好意思,它做不到。

二、Formatter:让日志变得好看又好用

日志格式器,说白了就是决定日志长什么样的模板。我见过太多项目用默认格式,结果日志里连时间戳都没有,排查问题全靠猜。

来看一个我常用的格式器配置:

import logging

formatter = logging.Formatter(
    fmt='%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

这里有几个关键点:

  • %(asctime)s:记录日志的时间,精确到秒
  • %(levelname)-8s:日志级别,左对齐占8个字符,对齐好看
  • %(name)s:%(lineno)d:哪个模块的哪一行,定位问题神器
  • %(message)s:你写的日志消息
小技巧:我习惯在格式里加上 %(funcName)s,这样能知道是哪个函数输出的日志。在大型项目中,这个信息能帮你省下不少排查时间。

你可能会问:「为什么不用 JSON 格式?」嗯,我个人觉得,在开发阶段用文本格式更直观。生产环境倒是可以考虑 JSON,方便日志收集系统解析。

三、Handler:灵活的输出通道

Handler 才是日志系统的灵魂。它决定了日志去哪——文件、终端、网络,甚至邮件。

我一般会这样配置:

import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler

# 创建一个日志器
logger = logging.getLogger('inventory')
logger.setLevel(logging.DEBUG)

# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_format = logging.Formatter('%(levelname)s: %(message)s')
console_handler.setFormatter(console_format)

# 文件处理器(按大小轮转)
file_handler = RotatingFileHandler(
    'inventory.log',
    maxBytes=10*1024*1024,  # 10MB
    backupCount=5
)
file_handler.setLevel(logging.DEBUG)
file_format = logging.Formatter(
    '%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s'
)
file_handler.setFormatter(file_format)

# 错误日志处理器(单独记录错误)
error_handler = RotatingFileHandler(
    'error.log',
    maxBytes=10*1024*1024,
    backupCount=3
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(file_format)

# 把所有处理器加到日志器
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.addHandler(error_handler)

logger.info('库存系统初始化完成')
logger.error('数据库连接失败,请检查配置')

你看,这样配置的好处很明显:

  • 控制台只显示 INFO 及以上级别的日志,开发时看着清爽
  • 文件记录所有 DEBUG 级别的日志,方便排查细节问题
  • 错误日志单独存一个文件,出问题时直接看 error.log
核心思路:不同的 Handler 可以设置不同的级别和格式。这就是日志系统的灵活性所在——你想怎么分,就怎么分。

四、实战中的避坑指南

我在项目中遇到过不少日志相关的坑,挑几个典型的说说:

  1. 日志重复输出:如果你不小心把同一个 Handler 加了两次,日志就会打印两遍。我曾经在重构代码时犯过这个错,排查了半天才发现是初始化了两次日志器。
  2. 日志文件无限增长:不用 RotatingFileHandler 的话,日志文件会一直膨胀。我见过一个项目,日志文件涨到了 50GB,直接把磁盘撑爆了。
  3. 忘记设置编码:中文日志在 Windows 上经常乱码。记得在 Handler 里加上 encoding='utf-8'
重要提醒:生产环境中,千万不要把日志级别设成 DEBUG。我见过有人上线后忘了改配置,结果日志量暴增,系统性能直接崩了。建议用环境变量来控制日志级别。

五、知识体系总览

下面这张图,是我对日志配置核心逻辑的总结。你看一眼就能明白各个组件之间的关系:

日志配置核心架构 Logger(日志器) Handler(处理器) StreamHandler RotatingFileHandler TimedRotatingHandler Formatter(格式器) 定义日志的输出格式:时间、级别、模块、消息等 控制台输出 文件输出 远程输出

这张图其实就讲了一件事:Logger 决定日志从哪来,Handler 决定日志去哪,Formatter 决定日志长什么样。三者配合,就能搭建出适合任何场景的日志体系。

六、写在最后

日志配置这件事,说难不难,说简单也不简单。关键在于理解它的设计思想——分离关注点。Logger、Handler、Formatter 各司其职,你只需要把它们组合起来就行。

我个人建议,每个项目一开始就把日志体系搭好。别等到出问题了再补,那时候就晚了。就像我常说的:「日志不是写给别人看的,是写给未来的自己看的。」

我的习惯:我会在项目根目录放一个 logging_config.py,专门管理日志配置。所有模块都从这个文件获取 logger,保证配置统一。这样后期要改日志格式或输出方式,改一个文件就够了。

专注资料整理