Как предотвратить наследование регистраторов и обработчиков python во время многопроцессорной обработки на основе 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.

как предотвратить наследование регистраторов и обработчиков без их удаления или удаления в исходном Родительском процессе?


EDIT: было бы хорошей идеей просто удалить все обработчики при инициализации пула?

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 ответов


вам не нужно предотвращать это, вам просто нужно перенастроить иерархию ведения журнала.

Я думаю, что вы на правильном пути с инициализатором пула. Но вместо того, чтобы пытаться взломать вещи, пусть пакет ведения журнала делает то, для чего он предназначен. Пусть пакет ведения журнала выполняет перенастройку иерархии ведения журнала в рабочих процессах.

вот пример:

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 основного процесса.


самый простой ответ заключается в том, что вам, вероятно, следует избегать изменения глобалов с помощью multiprocessing. Обратите внимание, что корневой регистратор, который вы получаете с помощью logging.getLogger(), является глобальной.

самый простой способ обойти это, просто создавая новый logging.Logger экземпляра для каждого процесса. Вы можете назвать их после процессов, или просто случайным образом:

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

вы также можете проверить как мне войти при использовании многопроцессорной обработки в python


Если вы нужно чтобы предотвратить наследование иерархии ведения журнала в рабочих процессах, просто выполните конфигурацию ведения журнала после создание пула работников. Из вашего примера:

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

тогда ничего не будет унаследовано.

в противном случае, как вы сказали, из-за ОС.fork(), вещи будут унаследованы / дублированы. В этом случае ваши параметры перенастраивают ведение журнала после создания пула (см. Мой другой ответ) или другие (ы) предложения/ответы.