直接用 logging 模块多进程写文件会出错,因 FileHandler 的文件对象非进程安全,导致日志错乱、覆盖或 OSError;推荐 QueueHandler+单日志进程或 concurrent-log-handler。

为什么直接用 logging 模块多进程写文件会出错
因为 logging.FileHandler 内部使用普通文件对象,而 Python 的文件对象不是进程安全的。多个子进程同时调用 write()、flush() 或移动文件指针(比如 seek()),会导致日志内容错乱、覆盖、甚至写入失败——常见现象是日志行被截断、两行日志挤在同一行、或报 OSError: [Errno 9] Bad file descriptor。
用 QueueHandler + 单独日志进程是最稳方案
核心思路:所有子进程不直接操作文件,而是把日志记录发给一个共享 queue.Queue;由**唯一一个日志进程**从队列里取记录并写入文件。这样彻底规避了并发写冲突。
实操要点:
- 主进程创建
logging.handlers.QueueHandler,绑定到根 logger 或自定义 logger - 启动一个守护进程(
daemon=True),运行logging.handlers.QueueListener,传入队列和实际的FileHandler - 子进程无需额外配置,只要确保它们的 logger 使用了该
QueueHandler(通常通过logging.getLogger()共享) - 注意:子进程需调用
logging.setLoggerClass()避免QueueHandler在 fork 后失效(见下一点)
示例关键片段:
import logging
import logging.handlers
from multiprocessing import Process, Queue
import time
<p>def worker(q):</p><h1>子进程必须重新设置 logger 类,否则 QueueHandler 可能失效</h1><pre class="brush:php;toolbar:false;">logging.setLoggerClass(logging.Logger)
logger = logging.getLogger('myapp')
logger.setLevel(logging.INFO)
logger.addHandler(logging.handlers.QueueHandler(q))
logger.info(f'Worker {id} started')if name == 'main': q = Queue() listener = logging.handlers.QueueListener(q, logging.FileHandler('app.log')) listener.start()
logger = logging.getLogger('myapp')
logger.setLevel(logging.INFO)
logger.addHandler(logging.handlers.QueueHandler(q))
ps = [Process(target=worker, args=(q,)) for _ in range(3)]
for p in ps:
p.start()
for p in ps:
p.join()
listener.stop() # 必须显式 stop,否则可能丢最后几条日志
别踩 RotatingFileHandler 在多进程下的坑
即使你用了 QueueListener,如果后端 handler 是 RotatingFileHandler,仍可能出问题:rotate 操作(重命名旧文件、新建文件)不是原子的,多个进程可能同时触发 rotate,导致文件被覆盖或报 PermissionError。
解决办法只有两个:
- 禁用自动 rotate,改用外部工具(如
logrotate)按时间/大小切分日志 - 或者改用
TimedRotatingFileHandler并设置delay=True+utc=True,再配合单日志进程——但依然不推荐,因 rotate 逻辑本身未加锁
更务实的选择:用 FileHandler 写单个大文件,靠运维轮转;或改用支持多进程的日志库(如 concurrent-log-handler,它内部用文件锁)。
用 concurrent-log-handler 是最快捷的替代方案
如果你不想自己搭 QueueListener 架构,concurrent-log-handler 就是为这场景设计的:它在写入前对文件加锁(基于 flock 或 Windows 的 LockFileEx),保证同一时刻只有一个进程在写。
安装与使用极简:
pip install concurrent-log-handler
然后替换 handler:
from concurrent_log_handler import ConcurrentRotatingFileHandler <p>handler = ConcurrentRotatingFileHandler( 'app.log', maxBytes=10<em>1024</em>1024, backupCount=5, encoding='utf-8' ) logger.addHandler(handler) </p>
注意点:
- Linux/macOS 下依赖
flock,Windows 下走系统 API,行为一致 - 它支持
RotatingFileHandler所有参数,包括maxBytes和backupCount - 性能比
QueueListener方案略低(每次写都有锁开销),但对一般业务完全够用
真正容易被忽略的是:某些容器环境(如 rootless Podman)或 NFS 文件系统可能不支持 flock,此时会静默退化为普通 FileHandler,日志又变不可靠——务必在目标环境验证锁是否生效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











