python多处理日志记录:带有RotatingFileHandler的QueueHandler“文件被另一个进程使用”错误

Posted

技术标签:

【中文标题】python多处理日志记录:带有RotatingFileHandler的QueueHandler“文件被另一个进程使用”错误【英文标题】:python multiprocessing logging: QueueHandler with RotatingFileHandler "file being used by another process" error 【发布时间】:2015-11-12 23:33:56 【问题描述】:

我正在将程序转换为多处理,并且需要能够从主进程和子进程记录到单个循环日志。我正在尝试使用 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 之外的任何进程都会尝试访问该文件。

【问题讨论】:

【参考方案1】:

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

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)

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

【讨论】:

【参考方案2】:

你不应该在子进程之前启动任何个线程。当 Python fork 时,线程和 IPC 状态并不总是被正确复制。

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

首先启动所有进程。

附加信息示例:

Status of mixing multiprocessing and threading in Python

https://***.com/a/6079669/4279

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

一些经验法则,由 cmets 的 fantabolous 总结:

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

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

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

子进程可以创建自己的线程和进程,应用与上述相同的规则。

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

【讨论】:

谢谢 - 不知道。我想这也意味着我不应该像现在这样从非主线程中创建进程;)您的最后一条评论“应该在...之前启动您的子进程……您打开以后要销毁的任何文件”会很棘手。这是否适用于子进程永远不会使用/触摸的文件? 只有在启动子进程时打开的文件。打开,然后立即关闭是没有问题的。但是,您也可以在子进程中关闭打开的文件句柄。也许考虑在你的主进程中少做很多,甚至将日志记录转移到子进程中。 抱歉,我不知道多处理内部机制,但是,如果它的 async processing threads 仍在运行,我不会创建额外的进程。也许主进程应该在它构建它的池之前创建一个代理进程,然后使用代理进程稍后创建任何额外的一次性进程,或者你可能将池创建本身移动到一个子进程中。 在***进程中设置“无线程”规则可能是最干净的,这意味着您不会在那里使用 multiprocessing.pool。 这是一个好的经验法则。实际上,当您启动一个进程时,您不应该运行除主线程之外的任何线程;首先启动所有进程是实现这一目标的简单方法。

以上是关于python多处理日志记录:带有RotatingFileHandler的QueueHandler“文件被另一个进程使用”错误的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows 中使用多处理进行 Python 日志记录

Python-logging详解(彩色日志扩展,多进程安全等)

Python-logging详解(彩色日志扩展,多进程安全等)

Python多进程记录到共享文件

Python 多处理返回结果,记录并在 Windows 上冻结运行

Python日志处理