python 多处理日志记录:QueueHandler 和 RotatingFileHandler "file being used by another process" 错误

python multiprocessing logging: QueueHandler with RotatingFileHandler "file being used by another process" error

我正在将一个程序转换为多处理,并且需要能够从主进程和子进程记录到单个轮换日志。我正在尝试使用 python 食谱 Logging to a single file from multiple processes 中的第二个示例,它启动 logger_thread 运行 作为主进程的一部分,从队列中获取日志消息子流程添加到。该示例按原样运行良好,如果我切换到 RotatingFileHandler 也可以运行。

但是,如果我将其更改为在子进程之前启动 logger_thread(这样我也可以从主进程进行日志记录),那么一旦日志轮换,所有后续日志记录都会生成一个回溯 WindowsError: [Error 32] The process cannot access the file because it is being used by another process.

换句话说,我更改了第二个示例中的代码

workers = []
for i in range(5):
    wp = Process(target=worker_process, name='worker %d' % (i + 1), args=(q,))
    workers.append(wp)
    wp.start()
logging.config.dictConfig(d)
lp = threading.Thread(target=logger_thread, args=(q,))
lp.start()

对此:

logging.config.dictConfig(d)
lp = threading.Thread(target=logger_thread, args=(q,))
lp.start()
workers = []
for i in range(5):
    wp = Process(target=worker_process, name='worker %d' % (i + 1), args=(q,))
    workers.append(wp)
    wp.start()

并将 logging.FileHandler 换成 logging.handlers.RotatingFileHandler(使用非常小的 maxBytes 进行测试)然后我遇到了这个错误。

我正在使用 Windows 和 python 2.7。 QueueHandler 在 python 3.2 之前不是 stdlib 的一部分,但我已经从 Gist 复制了源代码,它说这样做是安全的。

我不明白为什么首先启动侦听器会有任何不同,也不明白为什么除 main 之外的任何进程都会尝试访问该文件。

你不应该在子进程之前启动 任何 线程。当 Python 分叉时,线程和 IPC 状态将不会始终正确复制。

这方面有几个资源,只有 google 用于 fork 和 threads。有些人声称他们可以做到,但我不清楚它是否可以正常工作。

首先启动所有进程。

附加信息示例:

Status of mixing multiprocessing and threading in Python

在你的情况下,复制的打开文件句柄可能是问题所在,但你仍然应该在你的线程之前启动你的子进程(并且在你打开任何你稍后想要销毁的文件之前)。

一些经验法则,由 fantabolous 从评论中总结:

  • 子进程必须始终在同一进程创建的任何线程之前启动。

  • multiprocessing.Pool 创建子进程和线程,因此不能在第一个之后创建额外的进程或池。

  • 创建进程或池时文件不应已打开。 (这在某些情况下是可以的,但不是,例如,如果稍后将删除文件。)

  • 子进程可以创建自己的线程和进程,规则同上。

  • 首先启动所有进程是最简单的方法

因此,您可以简单地制作自己的文件日志处理程序。我还没有看到日志因多处理而出现乱码,因此文件日志轮换似乎是个大问题。只需在您的 main 中执行此操作,您不必更改任何其他日志记录

import logging
import logging.handlers
from multiprocessing import RLock

class MultiprocessRotatingFileHandler(logging.handlers.RotatingFileHandler):
    def __init__(self, *kargs, **kwargs):
        super(MultiprocessRotatingFileHandler, self).__init__(*kargs, **kwargs)
        self.lock = RLock()

    def shouldRollover(self, record):
        with self.lock:
            super(MultiprocessRotatingFileHandler, self).shouldRollover(record)

file_log_path = os.path.join('var','log', os.path.basename(__file__) + '.log')
file_log = MultiprocessRotatingFileHandler(file_log_path,
                                           maxBytes=8*1000*1024,
                                           backupCount=5,
                                           delay=True)

logging.basicConfig(level=logging.DEBUG)
logging.addHandler(file_log)

我猜测每次尝试旋转时锁定可能会减慢日志记录速度,但在这种情况下我们需要牺牲性能来换取正确性。