Как временно изменить формат зарегистрированных сообщений в Python?

Каков самый простой способ временного изменения формата сообщений журнала в Python (через модуль журнала)?

Цель состоит в том, чтобы иметь некоторый стандартный формат сообщения, будучи в состоянии временно добавлять информацию о некотором прочитанном файле (например, его имя); формат сообщения должен вернуться к своему стандарту, когда файл больше не читается. Программа, которая создает сообщения, не знает, какой файл читается, поэтому было бы неплохо, если бы его сообщение автоматически включало соответствующее имя файла (сообщение об ошибке было бы: "ОШИБКА при чтении файла ***:..." вместо "ОШИБКА:..." ).

Ответ 1

Вот простое решение, которое можно вывести из Vinay Sajip HOWTO; он в основном обновляет формат записи с помощью setFormatter():

import logging

logger = logging.getLogger()  # Logger
logger_handler = logging.StreamHandler()  # Handler for the logger
logger.addHandler(logger_handler)

# First, generic formatter:
logger_handler.setFormatter(logging.Formatter('%(message)s'))
logger.error('error message')  # Test

# New formatter for the handler:
logger_handler.setFormatter(logging.Formatter('PROCESSING FILE xxx - %(message)s'))
logger.error('error message')  # Test

Это правильно производит:

error message
PROCESSING FILE xxx - error message

(где xxx может быть задано динамически обрабатываемому файлу, как задано в вопросе).

Ответ 2

Существует несколько способов. Помимо уже задокументированных (аргумент extra для протоколирования вызовов, LoggerAdapter, Filter), другим способом было бы указать пользовательский класс форматирования, экземпляр которого вы можете получать информацию о обрабатываемом файле. Например:

class FileProcessingFormatter(logging.Formatter):
    def __init__(self, fmt, datefmt=None, current_file=None):
        super(FileProcessingFormatter, self).__init__(fmt, datefmt)
        self.orig_fmt = fmt
        self.current_file = current_file

    def format(self, record):
        if self.current_file is None:
            self._fmt = self.orig_fmt.replace('__FILE_PLACEHOLDER__', '')
        else:
            self._fmt = self.orig_fmt.replace('__FILE_PLACEHOLDER__',
                            ' while processing %r' % self.current_file)
        return super(FileProcessingFormatter, self).format(record)

Создайте экземпляр форматирования...

f = FileProcessingFormatter('%(levelname)s__FILE_PLACEHOLDER__ %(message)s')
for h in relevant_handlers:
    h.setFormatter(f)

Файлы процессов...

f.current_file = fn
process_file(fn)
f.current_file = None

Это очень упрощенно - например, не для использования в потоковых средах, если обработка файлов выполняется разными потоками одновременно.

Обновление: Хотя обработчики корневого регистратора доступны через logging.getLogger().handlers, это деталь реализации, которая может измениться. Поскольку ваше требование не является таким базовым, вы можете использовать dictConfig() для настройки ведения журнала (доступного через проект logutils для более старых версий Python).

Ответ 3

Я не рекомендую это; но вы можете сказать, что первый обработчик рутов - тот, который облажался и изменил его напрямую

import logging
ROOT_LOGGER = logging.getLogger()
ROOT_LOGGER.handlers[0].setFormatter(logging.Formatter(
    '%(asctime)s:%(levelname)s:%(name)s:%(message)s\n'
))

если вы находитесь в любой системе с управляемой регистрацией; это, вероятно, собирается застрелить вашу ногу; действительно было бы лучше иметь возможность определить точную ссылку на обработчик, который вы хотите изменить, и изменить его;

но никого не волнует, насколько он сломан, если он работает правильно? /s