RotatingFileHandler不丢日志的关键配置是:maxBytes>单次最大日志长度(建议≥10MB)、backupCount≥5、显式设置delay=True、encoding='utf-8',且多进程下须改用concurrent_log_handler或外部logrotate。

Python内置的RotatingFileHandler怎么配才不丢日志
直接用RotatingFileHandler但没设backupCount或maxBytes,日志文件会无限增长;设了却还在启动时清空旧日志?常见原因是没关掉delay=False(默认值)导致首次写入前就打开文件,而多进程下可能触发竞态。关键配置要一起生效:
-
maxBytes必须大于单次写入最大日志行长度(比如含traceback时可能超10KB),建议至少10*1024*1024 -
backupCount设为5表示最多保留5个历史文件(.1到.5),设0等于不轮转 - 务必显式传
delay=True,避免子进程fork后重复open同一文件引发IOError - 如果程序会频繁重启,加
encoding='utf-8'防Windows下BOM写入异常
多进程场景下RotatingFileHandler为什么总报错PermissionError: [WinError 32]
Windows上多个Python进程同时访问同一个日志文件时,RotatingFileHandler原生不支持进程安全轮转——它先rename再open,中间间隙会被其他进程抢占。Linux虽靠内核原子rename缓解,但仍有小概率失败。真实可用方案只有两个:
- 改用
concurrent_log_handler.ConcurrentRotatingFileHandler(需pip install concurrent-log-handler),它用文件锁保障rename安全 - 彻底避开文件竞争:所有进程只写到
sys.stdout,由外部工具如logrotate(Linux)或LogRotateWin(Windows)统一接管切割 - 绝对不要在代码里手动
os.rename()或shutil.move()日志文件——绕过handler内部状态会导致计数错乱
按天切割且保留30天,为什么TimedRotatingFileHandler生成的文件名带.2024-05-01却没自动删旧文件
TimedRotatingFileHandler默认不做清理,backupCount只控制同名轮转序号(如app.log.2024-05-01.1),对日期后缀无效。要实现“保留N天”,得自己补逻辑:
- 继承
TimedRotatingFileHandler,重写doRollover(),在调用super().doRollover()后扫描目录,用os.path.getmtime()删超过30天的*.log.*文件 - 更轻量做法:启动时用
glob.glob('app.log.*')匹配所有带日期的备份,过滤出datetime.strptime(filename.split('.')[-1], '%Y-%m-%d')早于30天的,os.remove() - 注意时区:如果日志名用
utc=True,清理时也得用datetime.utcnow()比对,否则每天少删1小时的文件
切割后日志内容断行、乱码或时间戳错位怎么办
根本原因常是handler被多个线程/协程共用,而RotatingFileHandler和TimedRotatingFileHandler本身线程安全,但底层FileHandler.emit()中self.stream.write()不是原子操作。尤其当一行日志被两个线程截断写入,就会出现半行乱码:
立即学习“Python免费学习笔记(深入)”;
- 确保每个logger实例绑定独立handler,不要在模块级创建handler后被多个模块import共享
- 在
Formatter里用%(asctime)s %(levelname)-8s %(name)s %(message)s这种紧凑格式,避免%(pathname)s等长路径字段拉长单行长度 - 终极保险:给handler加
logging.handlers.QueueHandler+QueueListener,把所有emit塞进队列,由单一线程落盘
实际部署时最易忽略的是:日志路径所在磁盘剩余空间是否足够容纳maxBytes * (backupCount + 1),尤其当backupCount=10且maxBytes=100MB时,意味着要预留1.1GB连续空间——磁盘碎片化可能导致轮转失败静默丢日志。


















