直接用RotatingFileHandler在多进程下会出问题,因其轮转依赖os.rename等非原子操作,多进程并发判断与重命名导致日志丢失、内容错乱或文件大小异常。

为什么直接用RotatingFileHandler在多进程下会出问题
因为 RotatingFileHandler 内部依赖文件系统原子操作(如 os.rename)做日志轮转,而多个进程同时检查文件大小、重命名、打开新文件时,会出现竞态:一个进程刚判断完文件没超限,另一个进程已完成轮转,前者接着写原文件就可能被覆盖或写入已 rename 的旧文件——典型现象是日志丢失、内容错乱、轮转后文件大小异常(比如 0 字节或远超 maxBytes)。
用QueueHandler + QueueListener 绕过文件竞争
核心思路是让所有子进程只往一个线程安全的 queue.Queue 发日志,由**单个监听线程**统一消费并写入磁盘。这样轮转逻辑只在单线程中执行,彻底规避多进程冲突。
实操要点:
- 每个进程初始化自己的
logging.getLogger(),但 handler 全部换成QueueHandler,指向同一个queue.Queue实例(需通过 multiprocessing.Manager() 或父子进程共享方式传递) - 主进程(或任一固定进程)启动
QueueListener,绑定RotatingFileHandler或TimedRotatingFileHandler—— 轮转动作只在这里发生 - 务必调用
listener.start()和listener.stop(),否则日志会堆积在队列里不落地 - 子进程退出前建议调用
logging.shutdown(),确保队列中残留日志被清空
示例关键片段:
立即学习“Python免费学习笔记(深入)”;
import logging
from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
import multiprocessing as mp
import queue
<p>log_queue = mp.Manager().Queue() # 跨进程安全队列</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac">macOS UI 自动化技能包 —— 用 Python 控制你的 Mac</a>
<p>让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript
系统要求
macOS 14+ (Sonoma)
Python 3.9+
需要开启:辅助功能 + 屏幕录制权限</p>
</div>
<a href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><h1>子进程内配置</h1><p>def worker():
logger = logging.getLogger("myapp")
logger.setLevel(logging.INFO)
logger.addHandler(QueueHandler(log_queue)) # 只发不写
logger.info("from worker")</p><h1>主进程启动监听器</h1><p>handler = RotatingFileHandler("app.log", maxBytes=10<em>1024</em>1024, backupCount=5)
listener = QueueListener(log_queue, handler)
listener.start()</p><h1>启动子进程...</h1><p>p = mp.Process(target=worker)
p.start()
p.join()</p><p>listener.stop() # 必须显式停止</p>注意 TimedRotatingFileHandler 的时间同步陷阱
如果用 TimedRotatingFileHandler 做按天/小时轮转,多个进程各自初始化 handler 会导致它们在不同时间点触发轮转(因进程启动时间差、时钟漂移),结果生成一堆命名混乱的备份文件(如 app.log.2024-05-20_01, app.log.2024-05-20_02)。必须只让 QueueListener 持有唯一 handler 实例。
另外:TimedRotatingFileHandler 默认使用本地时区,若部署在容器或跨时区环境,建议显式传入 utc=True 并统一用 UTC 时间轮转,避免夏令时导致的重复或跳过。
性能与可靠性权衡点
队列模式本质是把 I/O 压力集中到单一线程,吞吐量上限取决于磁盘写入速度。如果日志量极大(比如每秒万级日志),队列可能积压,建议:
- 给
mp.Manager().Queue()设置合理maxsize(如 10000),配合block=False和异常捕获,避免子进程卡死 - 监听线程里的 handler 可加
delay=True参数,延迟打开文件直到第一条日志到达,减少空文件句柄占用 - 生产环境务必监控
log_queue.qsize(),持续高位说明写入瓶颈已出现
真正难处理的是进程意外崩溃——队列中未消费的日志会永久丢失。没有银弹,只能靠外部日志采集器(如 filebeat)兜底或改用支持 WAL 的日志服务。

















