RotatingFileHandler 默认不删旧日志,因其仅在新日志写入触发滚动时清理backupCount个备份,静默期无写入则旧文件残留;TimedRotatingFileHandler同理依赖滚动事件,需额外脚本按修改时间安全清理。

为什么 RotatingFileHandler 默认不删旧日志?
很多开发者发现配置了 RotatingFileHandler 后,日志文件越滚越多,backupCount 像没起作用。根本原因是:它只在「新日志写入触发滚动时」才清理旧备份,而不是定时扫描删除。如果应用长期静默、无新日志产生,旧文件就卡在那里。
- 必须有日志写入动作,才会触发轮转逻辑和清理
-
backupCount=5表示最多保留 5 个备份(如app.log.1~app.log.5),但前提是每次滚动都成功执行 - 若手动删过中间编号(比如删了
.log.3),后续滚动可能跳号或清理失效
用 TimedRotatingFileHandler 按时间滚动但清理仍不准?
TimedRotatingFileHandler 能按小时/天滚动,但它的 backupCount 清理机制同样依赖滚动事件——如果某天没写日志,当天的文件就不会被纳入清理范围,导致残留。
- 推荐搭配
when='D'+interval=1+backupCount=7实现“最多保留 7 天”,但仅限于有日志产生的日期 - 若需严格按文件修改时间清理(比如删掉 30 天前所有
*.log.*),得额外加脚本 - 注意
atTime参数在 Windows 下可能因系统时钟精度问题导致滚动延迟
手写清理逻辑:安全删除过期日志文件
最可控的方式是独立运行一个清理函数,不依赖日志模块的滚动时机。关键是避免误删正在写的文件,且兼容不同命名格式(如 app.log、app.log.2024-05-01、app.log.1)。
- 用
os.stat(filepath).st_mtime判断最后修改时间,比文件名解析更可靠 - 遍历目录前先用
os.path.basename(filepath)过滤出日志相关文件,避免匹配到.pyc或临时文件 - 删除前加
os.path.getsize(filepath) == 0判断,跳过空文件(可能是刚创建未写入的滚动目标) - 示例片段:
import os, time def cleanup_old_logs(log_dir, max_age_seconds=2592000): # 默认30天 cutoff = time.time() - max_age_seconds for fname in os.listdir(log_dir): if not fname.endswith('.log') and not fname.startswith('app.log'): continue fpath = os.path.join(log_dir, fname) try: if os.path.getsize(fpath) == 0 or os.stat(fpath).st_mtime < cutoff: os.remove(fpath) except (OSError, FileNotFoundError): pass
生产环境必须绕开的坑
多进程场景下,RotatingFileHandler 无法保证原子性,两个进程同时滚动可能生成重复编号或漏删。Windows 上还可能出现“文件正被占用”异常。
立即学习“Python免费学习笔记(深入)”;
- 不要在多个进程里共用同一个日志文件路径;改用进程 ID 或主机名打散路径,例如
app-{}.log.format(os.getpid()) - 清理脚本务必加锁(如用
os.open(..., os.O_EXCL | os.O_CREAT)创建临时锁文件),防止并发执行误删 - 容器环境里,日志目录挂载为 volume 时,宿主机清理脚本可能看不到容器内新生成的日志文件——得在容器内跑清理
TimedRotatingFileHandler 负责生成,用独立定时任务负责按 mtime 删除,中间留出 1~2 小时缓冲期,避免刚滚动完就被清掉。


















