日志文件管理:按大小轮转与按时间轮转

日志文件如果不加管理,用不了多久就能把磁盘撑爆。我在生产环境见过太多次了——一个 Java 应用跑了两周,单日志文件 20 多 GB,最后连 tail 都卡死。所以今天咱们聊聊 Python 标准库里的两个轮转工具:RotatingFileHandler 和 TimedRotatingFileHandler。

说白了,轮转就是“日志写到一定条件就换文件”。旧日志可以压缩、保留、或者删除。你想想看,没有轮转的日志系统,跟没有刹车的车一样危险。

按大小轮转:RotatingFileHandler

这个 handler 的逻辑很简单——文件超过指定大小,就自动切分。举个例子,你设了 10MB,日志写到 10MB 时,当前文件重命名为 xxx.log.1,然后新建一个 xxx.log 继续写。如果 xxx.log.1 已经存在,就依次往后推。

核心参数一览

参数 说明 我的建议值
filename 日志文件路径 绝对路径,避免工作目录变动
mode 文件打开模式,默认 'a' 别改,追加模式最安全
maxBytes 单个日志文件最大字节数 10MB(10*1024*1024)
backupCount 保留的备份文件数量 5~10,看磁盘空间

代码示例很简单,我直接贴一个生产环境常用的配置:

import logging
from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    filename='/var/log/app/app.log',
    maxBytes=10 * 1024 * 1024,  # 10MB
    backupCount=5,
    encoding='utf-8'
)

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

logger = logging.getLogger('my_app')
logger.addHandler(handler)
logger.setLevel(logging.INFO)

小技巧:maxBytes 设成 0 的话,轮转永远不会触发。我见过有人抄代码忘了改这个值,结果日志文件一直涨到几十 GB 才发现。嗯,那个人就是我。

按时间轮转:TimedRotatingFileHandler

按大小轮转有个问题——如果某天流量暴增,10MB 可能几分钟就写满了,一天能轮转几十次。反过来,如果流量很小,一个月都切不了一次。这时候按时间轮转就更合适。

TimedRotatingFileHandler 支持按秒、分、时、天、周、月来切分。我个人最常用的是“每天零点切一次”,配合 backupCount=30 保留一个月。

时间间隔参数对照表

when 值 含义 适用场景
'S' 每秒 调试用,生产别用
'M' 每分钟 高频调试
'H' 每小时 高流量服务
'D' 每天 最常用,推荐
'W0'~'W6' 每周(0=周一) 低频服务
'midnight' 每天午夜 和 'D' 类似,但更精确

来看一个每天轮转、保留 30 天的配置:

import logging
from logging.handlers import TimedRotatingFileHandler

handler = TimedRotatingFileHandler(
    filename='/var/log/app/app.log',
    when='midnight',
    interval=1,
    backupCount=30,
    encoding='utf-8'
)

formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

logger = logging.getLogger('my_app')
logger.addHandler(handler)
logger.setLevel(logging.INFO)

注意:TimedRotatingFileHandler 的轮转时间取决于日志写入的时间点,不是系统定时任务。如果某个午夜没有日志写入,轮转就不会触发。我曾经因为这个踩过坑——周末流量低,周一早上发现日志文件名还是上周五的。

两种轮转方式怎么选?

我一般这样判断:

  • 流量稳定、日志量可预估 → 按大小轮转。比如内部工具、定时任务。
  • 流量波动大、需要按天归档 → 按时间轮转。比如面向用户的 Web 服务。
  • 两者都想要 → 可以叠加使用,但要注意文件名冲突。

其实还有一种更灵活的做法——两个 handler 同时挂到同一个 logger 上。一个按大小切,一个按时间切。不过这样日志会写两份,磁盘开销翻倍。我只有在审计合规要求特别严格的项目里才这么干。

避坑指南:轮转时丢失日志

这个问题我印象特别深。有一次线上排查问题,发现某段时间的日志全没了。查了半天,原来是轮转时文件被其他进程占用了。

为什么会这样?

  • 多个进程写同一个日志文件,轮转时重命名失败。
  • 日志文件被 tail -f 或 less 打开,文件句柄没释放。
  • Docker 容器里挂载了宿主机日志目录,权限不对导致重命名失败。

解决方案其实不复杂:

  1. 每个进程写独立的日志文件,文件名带上进程 ID 或实例名。
  2. 使用 logging.handlers.WatchedFileHandler,配合外部 logrotate 工具。
  3. 容器环境里,把日志输出到 stdout/stderr,让容器运行时去处理。

我的习惯:生产环境我更喜欢用 WatchedFileHandler + 系统 logrotate。Python 的轮转 handler 在单进程场景下没问题,但一旦涉及多进程、容器、共享存储,还是系统工具更靠谱。

知识体系总览

下面这张图帮你理清两种轮转方式的核心逻辑和适用场景:

日志轮转核心逻辑 日志轮转 按大小轮转 按时间轮转 maxBytes 触发 backupCount 控制 when 参数指定 interval 间隔 选型建议 流量稳定 → 按大小轮转 流量波动大 / 需要按天归档 → 按时间轮转

生产环境最佳实践

最后总结几条我踩过坑之后沉淀下来的经验:

  • 日志文件用绝对路径。别用相对路径,不然 cd 到不同目录日志就丢了。
  • 编码统一用 utf-8。中文日志乱码的问题,我见过太多次了。
  • 备份数量别设太大。5~10 个足够,除非你有合规要求必须保留更久。
  • 监控日志目录磁盘使用率。轮转不是万能的,日志量暴增时备份文件也会撑爆磁盘。
  • 测试环境验证轮转逻辑。把 maxBytes 设成 1KB,写几条日志看看轮转是否正常。

重要提醒:轮转 handler 不会自动压缩旧日志。如果你需要压缩,要么自己写一个 rotator 回调,要么用系统 logrotate 的 compress 选项。我一般选择后者,省心。