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 个字符宽度,这样日志级别那列看起来整整齐齐的。我个人习惯把时间、级别、位置信息放在前面,消息放最后——这样扫一眼就能定位关键信息。
%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)
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 进阶的内容就这些。从格式定制到文件输出,从日志轮转到多模块管理,每一步都是生产环境里实实在在会碰到的问题。把这些配置写进你的项目模板里,以后每次开新项目直接复制粘贴,省心又省力。
可可精品资料库