Windows 中的 Python 日志记录与多处理

use*_*082 5 python windows logging multiprocessing python-logging

我有一个相当大的 Python 项目,目前在 Linux 上运行,但我正在尝试扩展到 Windows。我已将代码简化为一个完整的示例,可以运行它来说明我的问题:我有两个类,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()
Run Code Online (Sandbox Code Playgroud)

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

user@ubuntu:~$ python test.py 
One
Two
Run Code Online (Sandbox Code Playgroud)

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

C:\>python test.py
<snip>
pickle.PicklingError: Can't pickle <type 'thread.lock'>: it's not found as thread.lock
Run Code Online (Sandbox Code Playgroud)

问题归结为 Windows 缺乏真正的分叉,因此在线程之间发送对象时必须进行 pickle。但是,记录器不能被腌制。我尝试使用__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)
Run Code Online (Sandbox Code Playgroud)

这在 Linux 中工作就像以前一样,现在 Windows 不会因为PicklingError. 但是,我的输出仅来自父级:

C:\>python test.py
One

C:\>
Run Code Online (Sandbox Code Playgroud)

No logger could be found for handler '__main__'尽管没有投诉消息或任何其他错误消息,但孩子似乎无法使用记录器。我环顾四周,发现有一些方法可以完全重组我登录程序的方式,但这显然是最后的手段。我希望我只是错过了一些明显的东西,希望群众的智慧能够向我指出这一点。

dan*_*ano 3

在大多数情况下,Logger对象是不可picklable的,因为它们在内部使用unpicklabletheading.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')
Run Code Online (Sandbox Code Playgroud)

或者,如果您想继续使用__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)
Run Code Online (Sandbox Code Playgroud)