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

Posted

技术标签:

【中文标题】在 Windows 中使用多处理进行 Python 日志记录【英文标题】:Python Logging with Multiprocessing in Windows 【发布时间】:2014-10-02 19:11:45 【问题描述】:

我有一个相当大的 Python 项目,目前在 Linux 上运行,但我正在尝试扩展到 Windows。我已将代码简化为一个完整的示例,可以运行它来说明我的问题:我有两个类,Parent 和 Child。 Parent首先被初始化,创建一个记录器,并产生一个Child来做工作:

import logging
import logging.config
import multiprocessing

class Parent( object ):
    def __init__(self, logconfig):
        logging.config.dictConfig(logconfig)
        self.logger = logging.getLogger(__name__)

    def spawnChild(self):
        self.logger.info('One')
        c = Child(self.logger)
        c.start()

class Child(multiprocessing.Process):
    def __init__(self, logger):
        multiprocessing.Process.__init__(self)
        self.logger = logger

    def run(self):
        self.logger.info('Two')

if __name__ == '__main__':
    p = Parent(
            'version':1, 
            "handlers": 
                "console": 
                    "class": "logging.StreamHandler",
                    "stream": "ext://sys.stdout"
                ,
            ,
            "root": 
                "level": "DEBUG",
                "handlers": [
                    "console",
                    ]
                
            
        )
    p.spawnChild()

在 linux(特别是 ubuntu 12.04)上,我得到以下(预期的)输出:

user@ubuntu:~$ python test.py 
One
Two

但是,在 Windows(特别是 Windows 7)上,它会因酸洗错误而失败:

C:\>python test.py
<snip>
pickle.PicklingError: Can't pickle <type 'thread.lock'>: it's not found as thread.lock

问题归结为 Windows 缺少真正的 fork,因此在线程之间发送对象时必须腌制。但是,记录器不能被腌制。我尝试使用 __getstate__ 和 __setstate__ 来避免酸洗,并在 Child 中按名称引用:

def __getstate__(self):
    d = self.__dict__.copy()
    if 'logger' in d.keys():
        d['logger'] = d['logger'].name
    return d

def __setstate__(self, d):
    if 'logger' in d.keys():
        d['logger'] = logging.getLogger(d['logger'])
    self.__dict__.update(d)

这在 Linux 中和以前一样有效,现在 Windows 不会因 PicklingError 而失败。但是,我的输出仅来自父级:

C:\>python test.py
One

C:\>

似乎孩子无法使用记录器,尽管没有消息抱怨“无法找到处理程序'__main__'的记录器”或任何其他错误消息。我环顾四周,有一些方法可以完全重组我登录程序的方式,但这显然是最后的手段。我希望我只是遗漏了一些明显的东西,并且群众的智慧可以向我指出。

【问题讨论】:

if key in some_dict.keys() 正是执行该检查的错误方式。在 python2 中需要 O(n) 时间。只需使用if key in some_dict。关于你的问题。子进程可能有不同的标准输出,因此您看不到输出。尝试添加一个文件处理程序并检查文件中的输出是否正确。 欣赏有关密钥的注释,该注释刚刚从另一个 SO 帖子中复制以进行测试。我所有的实际日志记录都是在文件中完成的,问题仍然存在。用于创建上述脚本的标准输出更易于测试。使用 "multi_file_handler": "class": "logging.handlers.RotatingFileHandler", "filename": "output.log" 会导致同样的问题 - Linux 上的“One\nTwo”,Windows 上的“One”跨度> 问题可能是,当解开__init__ 时,通常调用。这意味着子进程没有没有调用logging.config.dictConfig(...),因此它可能正在使用默认配置。尝试更改__setstate__ 方法,以便它使用正确的设置调用dictConfig,看看是否有变化。 上面的测试代码可以工作了,虽然我还没有让它在更大的项目中工作。这绝对是一个进步,一旦我有了真正的解决方案,我将继续工作并关闭它 【参考方案1】:

在大多数情况下,Logger 对象是不可提取的,因为它们在内部使用不可提取的 theading.Lock 和/或 file 对象。您尝试的解决方法确实避免了酸洗logger,但它最终会在子进程中创建一个完全不同的Logger,它恰好与父进程中的Logger 具有相同的名称;您拨打的logging.config 的效果会丢失。要获得您想要的行为,您需要在子进程中重新创建记录器重新调用logging.config.dictConfig

class Parent( object ):
    def __init__(self, logconfig):
        self.logconfig = logconfig
        logging.config.dictConfig(logconfig)
        self.logger = logging.getLogger(__name__)

    def spawnChild(self):
        self.logger.info('One')
        c = Child(self.logconfig)
        c.start()

class Child(multiprocessing.Process):
    def __init__(self, logconfig):
        multiprocessing.Process.__init__(self)
        self.logconfig = logconfig

    def run(self):
        # Recreate the logger in the child
        logging.config.dictConfig(self.logconfig)
        self.logger = logging.getLogger(__name__)

        self.logger.info('Two')

或者,如果你想继续使用__getstate__/__setstate__

class Parent( object ):
    def __init__(self, logconfig):
        logging.config.dictConfig(logconfig)
        self.logger = logging.getLogger(__name__)
        self.logconfig = logconfig

    def spawnChild(self):
        self.logger.info('One')
        c = Child(self.logger, self.logconfig)
        c.start()

class Child(multiprocessing.Process):
    def __init__(self, logger, logconfig):
        multiprocessing.Process.__init__(self)
        self.logger = logger
        self.logconfig = logconfig

    def run(self):
        self.logger.info('Two')

    def __getstate__(self):
        d = self.__dict__.copy()
        if 'logger' in d:
            d['logger'] = d['logger'].name
        return d

    def __setstate__(self, d):
        if 'logger' in d:
            logging.config.dictConfig(d['logconfig'])
            d['logger'] = logging.getLogger(d['logger'])
        self.__dict__.update(d)

【讨论】:

这类似于@Bakuriu 上面的评论,尽管他也指出 unpickling 不会调用 _init_ 这就是为什么我放置 logging.config.dictConfig(self _init_ 中的 .logconfig) 无法正常工作。它不在我的实际项目中工作,而是在我的测试代码中工作。如果我能从这里找到解决方案,我会继续调整并关闭它。 @user2093082 嗯,我看不到您的问题或您说您在Child__init__ 中调用logging.config.dictConfig 的任何地方。我是否遗漏了什么,或者您是否尝试过而没有在此处明确提及? 我没有说的这么明确,我先测试一下是为了回应Bakuriu的cmets 在这个答案和 Bakuriu 的 cmets 之间,我一切正常。

以上是关于在 Windows 中使用多处理进行 Python 日志记录的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows,Python3.8 上使用多处理时如何更新 tkinter 应用程序中的状态

使用共享值在 Windows 上进行 python 多处理

尝试python多处理的Windows上的RuntimeError

Windows 上的 Python 多处理运行时错误

为啥在windows上使用python多处理时打印三遍?

具有大型数组的 Windows 上的 Python 多处理