首页 > 解决方案 > 为什么我在多个进程中得到相同的记录器

问题描述

从不同进程登录到单个文件时,我得到了重复的日志。为什么我在多个进程中得到相同的记录器?记录器有一些处理程序。

我的代码:

def get_queue_logger(q):
    qh = QueueHandler(q)
    root = logging.getLogger()
    print(f"Logger: {id(root)}, Handlers: {root.handlers}")
    root.addHandler(qh)
    return root


def main():
    q = Manager().Queue()

    pool = Pool()
    for i in range(10):
        pool.apply(
            func=get_queue_logger,
            args=(q,)
        )
    pool.close()
    pool.join()


if __name__ == "__main__":
    main()

安慰:

Logger: 2041926265200, Handlers: []
Logger: 2095359404400, Handlers: []
Logger: 1781876664688, Handlers: []
Logger: 3222115264880, Handlers: []
Logger: 2041926265200, Handlers: [<QueueHandler (NOTSET)>]  # The same logger
Logger: 1705732548976, Handlers: []
Logger: 2505966954864, Handlers: []
Logger: 2095359404400, Handlers: [<QueueHandler (NOTSET)>]  # The same logger
Logger: 1934246029680, Handlers: []
Logger: 1698863065456, Handlers: []

标签: pythonloggingmultiprocessing

解决方案


我也遇到了这种行为,同时使用multiprocessing.Poolconcurrent.futures.process.ProcessPoolExecutorQueueHandler handler如果您通过创建进程,它会按预期工作(即,每个进程的根记录器中添加一个) multiprocessing.Process,例如将您的更改main()为:

q = Manager().Queue()

workers = []
for i in range(10):
    worker = multiprocessing.Process(target=get_queue_logger,
                                     args=(q,))
    workers.append(worker)
    worker.start()
for w in workers:
    w.join()

我认为使用 a 时额外的(和不需要的)处理程序pool是由于一个记录器实例已经在另一个进程中为函数QueueHandler handler添加了可用的get_queue_logger,但我不明白如何(编辑 - 参见下面的 EDIT#2)。顺便说一句,当使用poolwhenmultiprocessing.set_start_method设置为“spawn”或“fork”时,我看到了这个问题。

这也意味着此处文档中的第一个代码示例:https : //docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes 没有使用此处提到的方法时无法按预期工作:https ://docs.python.org/3/howto/logging-cookbook.html#using-concurrent-futures-processpoolexecutor 。

编辑#1:请注意,这个问题(根记录器的不同且有时重复的 id,QueueHandler handler有时每个进程不止一次添加)似乎只发生在 MacOS(10.15.7)上。Ubuntu 19.10 上的相同代码会为每个记录器生成相同的id,并且QueueHandler每个进程只会添加一次。

编辑#2:事实证明,QueueHandler handler当使用池时,每个进程向根记录器添加多个是由于向池提交的任务多于可用的 cpus/线程。正如此处的文档中所述, “池中的工作进程通常在池的工作队列的整个持续时间内存在”,并且在我的 Mac 笔记本电脑(4 cpu)的情况下,这会导致一些工作进程接收多个任务,添加QueueHandler handler每次都是新的根记录器。如果您正在使用multiprocesing.Pool,则可以使用该maxtasksperchild=1参数解决此问题,但此功能似乎不适用于concurrent.futures.process.ProcessPoolExecutor. 我看到差异的原因是多重的QueueHandlermacOS 和 Ubuntu 之间的处理程序(EDIT#1)是我的 Ubuntu 机器有 128 个线程,因此对get_queue_logger()where 的 10 个调用中的每一个都被分配给不同的进程。macOS 仍然为记录器报告不同(但不再重复)的 id,因为它默认使用“spawn”而不是“fork”;如果multiprocessing.set_start_method('fork')与 一起设置multiprocesing.Pool(maxtasksperchild=1),则在所有情况下都将获得相同的记录器 ID。


推荐阅读