4. logging进阶:日志格式定制、输出到文件、日志轮转、多模块日志管理

上一章我们聊了 logging 的基础用法,说白了就是「怎么把话记下来」。但实际项目中,光记下来远远不够。你想想看,日志文件越来越大怎么办?不同模块的日志混在一起怎么查?格式乱七八糟看着头疼怎么办?

嗯,这一章我们就来解决这些「进阶」问题。我个人习惯把 logging 的进阶配置比作「装修」——基础框架搭好了,现在该贴瓷砖、走水电了。

4.1 日志格式定制:让日志「长」得好看

默认的日志格式是啥样?ERROR:root:something wrong。说实话,这在开发环境凑合能用,上了生产环境根本没法看。我刚开始写 Python 时,就因为这个被运维同事吐槽过——「你这日志连时间都没有,我怎么定位问题?」

logging 的格式定制,核心就是 Formatter 这个类。它支持一堆内置的字段,我挑几个最常用的列出来:

字段 含义 示例输出
%(asctime)s 时间戳 2025-03-21 14:30:00,123
%(levelname)s 日志级别 INFO / ERROR
%(name)s Logger 名称 myapp.module
%(filename)s 文件名 app.py
%(lineno)d 行号 42
%(message)s 日志内容 用户登录成功

举个例子,我常用的格式长这样:

import logging

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

handler = logging.StreamHandler()
handler.setFormatter(formatter)

logger = logging.getLogger('myapp')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

logger.info('服务启动成功')
# 输出:2025-03-21 14:30:00 | INFO     | myapp:app.py:12 | 服务启动成功

这里有个小技巧:%-8s 表示左对齐并占 8 个字符宽度,这样日志级别那列看起来整整齐齐的。我个人习惯把时间、级别、位置信息放在前面,消息放最后——这样扫一眼就能定位关键信息。

我的习惯:生产环境我会把时间格式改成 ISO 8601 标准(%Y-%m-%dT%H:%M:%S),方便日志分析工具解析。开发环境则用更友好的格式,比如 %m-%d %H:%M:%S,省去年份占地方。

4.2 输出到文件:日志不能只活在终端里

终端输出适合调试,但程序跑起来之后,日志得落地到文件里。不然你总不能 24 小时盯着屏幕看吧?

用 FileHandler 就能搞定:

file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

就这么简单。但注意一点:FileHandler 默认是追加模式(mode='a'),不会覆盖已有文件。如果你想每次启动都重新写,可以指定 mode='w'。

我曾经踩过一个坑:日志文件写满了磁盘,导致服务挂了。嗯,这就是为什么我们需要日志轮转。

4.3 日志轮转:别让日志撑爆磁盘

日志轮转,说白了就是「日志文件长大了就切一刀」。Python 提供了两种轮转方式:

  • 按大小轮转:文件超过指定大小就自动切割
  • 按时间轮转:每天/每小时生成一个新文件

我一般用 RotatingFileHandler:

from logging.handlers import RotatingFileHandler

# 每个文件最大 5MB,保留 3 个备份
rotating_handler = RotatingFileHandler(
    'app.log',
    maxBytes=5 * 1024 * 1024,  # 5MB
    backupCount=3,
    encoding='utf-8'
)
rotating_handler.setFormatter(formatter)
logger.addHandler(rotating_handler)

这样当 app.log 超过 5MB 时,会自动重命名为 app.log.1,然后新建一个 app.log。如果 app.log.1 也存在,就继续往后推,最多保留 3 个备份。

按时间轮转用 TimedRotatingFileHandler:

from logging.handlers import TimedRotatingFileHandler

# 每天凌晨 0 点切一次,保留 7 天的日志
time_handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',
    interval=1,
    backupCount=7,
    encoding='utf-8'
)
time_handler.setFormatter(formatter)
logger.addHandler(time_handler)
注意:轮转时如果日志文件正在被其他进程读取(比如 tail -f),可能会报错。我曾经在生产环境遇到过这个问题——运维同事正在看日志,轮转直接把文件删了。解决方案是使用 watchedFileHandler,它会在文件被删除后自动重新创建。

4.4 多模块日志管理:别让日志「打架」

项目大了,模块多了,日志管理就成了头疼的事。每个模块都自己搞一套 logger,最后日志格式不统一、级别不一致,查问题能查到崩溃。

我的做法是:在项目入口处统一配置,然后各模块通过 getLogger(__name__) 获取 logger。

来看一个完整的例子:

# config_logger.py —— 统一配置
import logging
from logging.handlers import RotatingFileHandler

def setup_logger(name='myapp', log_file='app.log'):
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)

    # 格式
    fmt = '%(asctime)s | %(levelname)-8s | %(name)s | %(message)s'
    formatter = logging.Formatter(fmt)

    # 文件处理器(轮转)
    file_handler = RotatingFileHandler(log_file, maxBytes=5*1024*1024, backupCount=3, encoding='utf-8')
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)

    # 控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)

    logger.addHandler(file_handler)
    logger.addHandler(console_handler)

    return logger
# module_a.py
import logging
logger = logging.getLogger(__name__)

def do_something():
    logger.info('模块 A 正在执行...')
# module_b.py
import logging
logger = logging.getLogger(__name__)

def do_another():
    logger.error('模块 B 出错了!')
# main.py
from config_logger import setup_logger
from module_a import do_something
from module_b import do_another

logger = setup_logger('myapp', 'myapp.log')

logger.info('应用启动')
do_something()
do_another()
logger.info('应用结束')

这样配置的好处是:所有模块的日志格式统一,级别控制灵活。文件里记录所有 DEBUG 级别的日志,控制台只显示 INFO 以上的——开发时看控制台不觉得烦,出问题时去翻文件又能找到细节。

核心思想:日志配置只做一次,各模块只管「写日志」,不管「怎么写」。这就是所谓的「关注点分离」。

4.5 知识体系总览

下面这张图总结了本章的核心内容,你可以把它当作一个「日志配置检查清单」:

logging 进阶配置体系 Logger 配置入口 格式定制 输出到文件 日志轮转 多模块管理 Formatter 字段 时间/级别/位置 FileHandler 编码/模式 RotatingFileHandler TimedRotatingFileHandler getLogger(__name__) 统一配置入口 核心原则:配置一次,到处使用 关注点分离 · 统一格式 · 自动轮转

这张图把本章的四个核心知识点串起来了。你写代码时照着这个结构来,基本不会漏掉什么。

避坑指南:我曾经在微服务项目里,每个模块都自己写了一套日志配置。结果上线后日志格式五花八门,有的带时间有的不带,有的用中文有的用英文。最后花了整整两天统一配置。所以我的建议是——项目一开始就把日志配置写好,后面所有模块都复用同一个配置函数。

好了,logging 进阶的内容就这些。从格式定制到文件输出,从日志轮转到多模块管理,每一步都是生产环境里实实在在会碰到的问题。把这些配置写进你的项目模板里,以后每次开新项目直接复制粘贴,省心又省力。

可可精品资料库