Python logging模块TimedRotatingFileHandler文件旋转失败:系统进程(PID=4)锁定日志文件的问题求助
我之前帮朋友排查过一模一样的问题,PID=4的System进程是Windows内核级的系统进程,一般是杀毒软件实时扫描、系统自动备份或者某些文件系统过滤驱动在后台访问你的日志文件时触发的锁定,导致TimedRotatingFileHandler没办法重命名/替换日志文件。给你几个不用重启系统的解决方案,按优先级排序:
1. 避开系统扫描高峰调整日志旋转时间
默认的午夜(0点)通常是很多系统工具(比如Windows备份、杀毒全盘扫描)运行的时段,你可以把日志旋转的时间改成凌晨1点或者其他低峰时段,减少冲突概率。修改代码里的TimedRotatingFileHandler参数即可:
from logging.handlers import TimedRotatingFileHandler import datetime handler = TimedRotatingFileHandler( 'app.log', when='D', # 按天旋转 interval=1, atTime=datetime.time(1, 0, 0), # 指定凌晨1点执行旋转 backupCount=7 )
2. 用QueueHandler+QueueListener异步写日志
默认的日志是同步写入的,文件句柄会被主线程长时间持有,更容易被系统进程锁定。改用异步写入的方式,让日志操作在单独线程执行,写完就立即释放文件句柄:
import logging from logging.handlers import TimedRotatingFileHandler, QueueHandler, QueueListener import queue # 创建日志队列 log_queue = queue.Queue(-1) # 初始化常规的TimedRotatingFileHandler file_handler = TimedRotatingFileHandler('app.log', when='midnight', backupCount=7) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) # 创建队列监听器,绑定队列和文件处理器 listener = QueueListener(log_queue, file_handler) # 配置根日志器 root_logger = logging.getLogger() root_logger.addHandler(QueueHandler(log_queue)) root_logger.setLevel(logging.INFO) # 启动监听器线程 listener.start() # 正常使用日志器 root_logger.info('测试异步日志') # 程序退出前记得停止监听器 # listener.stop()
这种方式下,文件句柄只会在写入日志的瞬间被持有,被系统锁定的概率会大幅降低。
3. 排除日志目录的系统扫描
检查你的杀毒软件、Windows Defender的实时保护设置,把日志文件所在的目录添加到排除列表;另外Windows的自动备份(比如文件历史记录)也要排除这个目录,避免系统工具在后台扫描锁定文件。
4. 自定义更健壮的日志旋转逻辑
如果上面的方法都不行,可以自定义日志旋转的逻辑,比如在旋转前先手动关闭文件句柄,或者先复制日志文件再清空原文件(而不是重命名)。比如继承TimedRotatingFileHandler重写doRollover方法:
from logging.handlers import TimedRotatingFileHandler import shutil class SafeTimedRotatingFileHandler(TimedRotatingFileHandler): def doRollover(self): # 先关闭当前文件句柄释放锁 self.stream.close() # 复制当前日志到备份文件 backup_name = self.rotation_filename(self.baseFilename + '.' + self.suffix) shutil.copyfile(self.baseFilename, backup_name) # 清空原日志文件 with open(self.baseFilename, 'w') as f: pass # 重新打开文件句柄 self.stream = self._open()
这个方法绕过了默认的重命名操作,用复制+清空的方式实现日志归档,避免了重命名时因文件被锁定失败的问题,配合QueueHandler使用会更稳妥。
5. 改用第三方日志库
比如loguru,它的文件旋转实现更健壮,默认支持异步写入和多种旋转策略,处理文件锁定的情况更智能:
from loguru import logger logger.add( 'app.log', rotation='01:00', # 凌晨1点旋转 retention='7 days', compression='zip' ) logger.info('测试loguru日志')
loguru会自动处理文件锁定的情况,甚至在文件被外部进程锁定时,会暂时缓存日志,等锁定释放后再写入。
备注:内容来源于stack exchange,提问作者Peter Baksa

