在基于 fork 的多处理过程中,如何防止 python 记录器和处理程序的继承?

How can I prevent the inheritance of python loggers and handlers during multiprocessing based on fork?

假设我在主进程中配置了日志处理程序。主进程生成一些子进程,并且由于 os.fork()(在 Linux 中)所有记录器和处理程序都从主进程继承。在下面的示例中,'Hello World' 将被打印到控制台 100 次:

import multiprocessing as mp
import logging


def do_log(no):
    # root logger logs Hello World to stderr (StreamHandler)
    # BUT I DON'T WANT THAT!
    logging.getLogger().info('Hello world {}'.format(no))


def main():
    format = '%(processName)-10s %(name)s %(levelname)-8s %(message)s'

    # This creates a StreamHandler
    logging.basicConfig(format=format, level=logging.INFO)

    n_cores = 4
    pool = mp.Pool(n_cores)
    # Log to stdout 100 times concurrently
    pool.map(do_log, range(100))
    pool.close()
    pool.join()


if __name__ == '__main__':
    main()

这将打印如下内容:

ForkPoolWorker-1 root INFO     Hello world 0
ForkPoolWorker-3 root INFO     Hello world 14
ForkPoolWorker-3 root INFO     Hello world 15
ForkPoolWorker-3 root INFO     Hello world 16
...

但是,我不希望子进程继承父进程的所有日志记录配置。所以在上面的例子中 do_log 不应该打印任何东西到 stderr 因为不应该有 StreamHandler.

如何防止继承记录器和处理程序而不在原始父进程中移除或删除它们?


编辑:在池初始化时简单地删除所有处理程序是否是个好主意?

def init_logging():
    for logger in logging.Logger.manager.loggerDict.values():
        if hasattr(logger, 'handlers'):
            logger.handlers = []

pool = mp.Pool(n_cores, initializer=init_logging, initargs=())

此外,我还可以在初始化函数期间安全地 close() 所有(文件)处理程序吗?

最直接的答案是您应该避免使用 multiprocessing 修改全局变量。请注意,您使用 logging.getLogger() 获得的根记录器是全局的。

最简单的方法就是为每个进程创建一个新的 logging.Logger 实例。您可以根据流程命名它们,或者只是随机命名:

log= logging.getLogger(str(uuid.uuid4()))

您可能还想查看 how should I log while using multiprocessing in python

你不需要阻止它,你只需要重新配置日志层次结构。

我认为您在池初始值设定项方面走在正确的轨道上。但是与其试图破解,不如让日志包做它设计的事情。让日志包在工作进程中重新配置日志层次结构。

这是一个例子:

def main():

    def configure_logging():
        logging_config = {
            'formatters': {
                'f': {
                    'format': '%(processName)-10s %(name)s'
                              ' %(levelname)-8s %(message)s',
                },
            },
            'handlers': {
                'h': {
                    'level':'INFO',
                    'class':'logging.StreamHandler',
                    'formatter':'f',
                },
            },
            'loggers': {
                '': {
                    'handlers': ['h'],
                    'level':'INFO',
                    'propagate': True,
                },
            },
            'version': 1,
        }

        pname = mp.current_process().name
        if pname != 'MainProcess':
            logging_config['handlers'] = {
                'h': {
                    'level':'INFO',
                    'formatter':'f',
                    'class':'logging.FileHandler',
                    'filename': pname + '.log',
                },
            }

        logging.config.dictConfig(logging_config)

    configure_logging() # MainProcess
    def pool_initializer():
        configure_logging()

    n_cores = 4
    pool = mp.Pool(n_cores, initializer=pool_initializer)
    pool.map(do_log, range(100))
    pool.close()
    pool.join()

现在,工作进程将各自记录到自己的日志文件中,并且将不再使用主进程的 stderr StreamHandler。

如果您需要防止在工作进程中继承日志层次结构,只需在创建工作进程后进行日志配置水池。从你的例子:

pool = mp.Pool(n_cores)
logging.basicConfig(format=format, level=logging.INFO)

那么,什么都不会继承。

否则,就像你说的,因为os.fork(),事情会变成inherited/duplicated。 在这种情况下,您的选项是在创建池后重新配置日志记录(请参阅我的其他答案)或其他 suggestions/answers.