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

时间:2015-03-12 17:03:24

标签: python logging multiprocessing 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()所有(文件)处理程序吗?

3 个答案:

答案 0 :(得分:4)

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

我认为您使用游泳池初始化程序处于正确的轨道上。但是,不要试图破解东西,而是让日志包执行其设计的目标。让日志包重新配置工作进程中的日志记录层次结构。

以下是一个例子:

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。

答案 1 :(得分:2)

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

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

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

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

答案 2 :(得分:0)

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

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

然后,什么都不会被继承。

否则,就像你说的那样,由于os.fork(),事情会被继承/重复。 在这种情况下,您的选项是在创建池(请参阅我的其他答案)或其他(s)建议/答案后重新配置日志记录。