日志文件管理:按大小轮转与按时间轮转
日志文件如果不加管理,用不了多久就能把磁盘撑爆。我在生产环境见过太多次了——一个 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 容器里挂载了宿主机日志目录,权限不对导致重命名失败。
解决方案其实不复杂:
- 每个进程写独立的日志文件,文件名带上进程 ID 或实例名。
- 使用
logging.handlers.WatchedFileHandler,配合外部logrotate工具。 - 容器环境里,把日志输出到 stdout/stderr,让容器运行时去处理。
我的习惯:生产环境我更喜欢用 WatchedFileHandler + 系统 logrotate。Python 的轮转 handler 在单进程场景下没问题,但一旦涉及多进程、容器、共享存储,还是系统工具更靠谱。
知识体系总览
下面这张图帮你理清两种轮转方式的核心逻辑和适用场景:
生产环境最佳实践
最后总结几条我踩过坑之后沉淀下来的经验:
- 日志文件用绝对路径。别用相对路径,不然
cd到不同目录日志就丢了。 - 编码统一用
utf-8。中文日志乱码的问题,我见过太多次了。 - 备份数量别设太大。5~10 个足够,除非你有合规要求必须保留更久。
- 监控日志目录磁盘使用率。轮转不是万能的,日志量暴增时备份文件也会撑爆磁盘。
- 测试环境验证轮转逻辑。把
maxBytes设成 1KB,写几条日志看看轮转是否正常。
重要提醒:轮转 handler 不会自动压缩旧日志。如果你需要压缩,要么自己写一个 rotator 回调,要么用系统 logrotate 的 compress 选项。我一般选择后者,省心。