Как написать собственный обработчик журнала Python?

Как написать пользовательскую консольную функцию журнала для вывода только в окне консоли сообщений журнала в одну строку (не добавлять) до первой обычной записи журнала.

progress = ProgressConsoleHandler()
console  = logging.StreamHandler()  

logger = logging.getLogger('test')
logger.setLevel(logging.DEBUG) 
logger.addHandler(console)  
logger.addHandler(progress)

logger.info('test1')
for i in range(3):
    logger.progress('remaining %d seconds' % i)
    time.sleep(1)   
logger.info('test2')

Так что вывод на консоль всего три строки:

INFO: test1
remaining 0 seconds... 
INFO: test2

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

3 ответа

import logging
class ProgressConsoleHandler(logging.StreamHandler):
    """
    A handler class which allows the cursor to stay on
    one line for selected messages
    """
    on_same_line = False
    def emit(self, record):
        try:
            msg = self.format(record)
            stream = self.stream
            same_line = hasattr(record, 'same_line')
            if self.on_same_line and not same_line:
                stream.write(self.terminator)
            stream.write(msg)
            if same_line:
                stream.write('... ')
                self.on_same_line = True
            else:
                stream.write(self.terminator)
                self.on_same_line = False
            self.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)
if __name__ == '__main__':
    import time
    progress = ProgressConsoleHandler()
    console  = logging.StreamHandler()  

    logger = logging.getLogger('test')
    logger.setLevel(logging.DEBUG) 
    logger.addHandler(progress)

    logger.info('test1')
    for i in range(3):
        logger.info('remaining %d seconds', i, extra={'same_line':True})
        time.sleep(1)   
    logger.info('test2')

Обратите внимание, что регистрируется только один обработчик, и extra Аргумент ключевого слова, чтобы обработчик знал, что он должен оставаться на одной строке Есть больше логики в emit() метод для обработки изменений между сообщениями, которые должны оставаться на одной строке, и сообщениями, которые должны иметь свою собственную строку.

Отказ от ответственности 1

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

Отказ от ответственности 2

Одна из причин, по которой этот подход не рекомендуется/не рекомендуется, заключается в том, что вы можете очень легко случайно переопределить метод класса в своем классе-оболочке. Если это произойдет, это может привести к странным, трудно объяснимым ошибкам.

Если вы используете этот подход, позаботьтесь о том, чтобы убедиться, что любые имена атрибутов/методов, которые вы планируете использовать в своих собственных пользовательских методах/атрибутах, еще не существуют в классе.

Обратитесь к документации по модуль для получения дополнительной информации

Без лишних слов: Ответ

Отвечать

Я создал базовый класс для пользовательских регистраторов как таковой:

      import logging

class CustomLoggerBC():
    def __init__(self, name:str):
         self._logger = logging.getLogger(name)
         newdict = {k: getattr(self._logger, k) for k in dir(self._logger) if k not in dir(self)}
         self.__dict__.update(newdict)

Затем вы можете написать свой собственный регистратор, который наследуется от базового класса как такового:

      class MyCustomLogger(CustomLoggerBC):
    def __init__(name:str, ...rest of your arguments here):
        super().__init__(name)
        ... rest of your custom constructor here

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

      class MyCustomLogger(CustomLoggerBC):
    def __init__(name:str, ...rest of your arguments here):
        super().__init__(name)
        ... Define your handler here
        self._logger.addHandler(self._customHandler)
        self._logger.setLevel(logging.INFO)
        ... rest of your custom constructor here

Примеры

Код такой:


Может легко создать что-то вроде этого

[ ИНФО ] [19.05.2022 10:03:45] Это тест

[INFO] [19.05.2022 10:03:45] Это тоже тест

Кроме того, вы даже можете переопределить методы класса ( хотя я этого не рекомендую ) .

      class MyCustomLogger(CustomLoggerBC):
    def __init__(name:str, ...rest of your arguments here):
        super().__init__(name)
        ... rest of your custom constructor here
    def info(self, msg):
        print("My custom implementation of logging.Logger's info method!")

Код такой:

      cl = MyCustomLogger("MyCustomLogger")
cl.info("This is a test")
cl.info("This is also a test")

Теперь вместо этого выдает что-то вроде этого

Моя собственная реализация информационного метода logging.Logger!

Моя собственная реализация информационного метода logging.Logger!

Почему вы хотите это сделать?

Вы можете заранее настроить регистраторы. Например, если вы знаете, что регистратор для части A вашего приложения будет иметь определенный формат, определенный обработчик и определенный уровень. Гораздо проще сделать что-то подобное в части А

      myPartALogger = PartACustomLogger()

вместо того, чтобы выполнять всю работу по инициализации регистратора в части A.

Это также делает регистраторы более пригодными для повторного использования, если части A и части B нужны отдельные регистраторы, но они будут иметь одинаковую конфигурацию (например, тот же уровень и средство форматирования, но другой дескриптор). Вы можете создать два экземпляра вашего PartACustomLogger для каждой части с разными именами и передать разные обработчики для каждого экземпляра.

Почему это работает

По сути, вы оборачиваете класс . Базовый класс обновляет словарь экземпляра реализации подкласса всеми методами и атрибутами класса, чтобы реализация по существу функционировала как объект. Любые запросы атрибутов/методов к вашему пользовательскому классу регистратора кажутся перенаправленными экземпляру для разрешения экземпляром, но на самом деле ваш собственный класс разрешает запросы. Это делает так, что кажется, что вы подклассифицируете класс, но на самом деле вы не.

Это дополнение к:настройке обработчиков , если вы решите использовать файл json или yaml для хранения конфигурации журналирования (рекомендуется). Если вы сделаете это, вы сможете настроить обработчики без создания подклассов, и это будет выглядеть намного понятнее. Я добавлю сюда свой собственный пример, которого вместе с примером из Python Docu может быть достаточно, чтобы охватить большинство случаев:

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

      import logging, logging.config

def your_handler_creator_function(stream=None): 
#The reason for this argument is that it's the one the __init__ method in the "logging.StreamHandler" class receives
    handler = logging.StreamHandler(stream) #The type of handler I want to use
    handler.setLevel(MY_CUSTOM_LEVEL_PREVIOUSLY_DEFINED)
    return handler

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
    'default': {
        'format': '%(asctime)s %(levelname)s %(name)s %(message)s'
    },
},
'handlers': {
    "name_of_your_handler": {
        "()": "path.to.your.your_handler_creator_function",
        # This value "stream" is the one that will be passed to the function
        "stream": "ext://sys.stdout",
        # The handler will get the values below automatically
        "formatter": "default",
        "filters": [
            "filter_name"
        ]
    },
},
'root': {
    'handlers': ['file'],
    'level': 'DEBUG',
},
}

logging.config.dictConfig(LOGGING)
logger = logging.getLogger('mylogger')
logger.debug('A debug message')
Другие вопросы по тегам