Как добавить настраиваемое поле в строку формата журнала Python?

Моя текущая строка формата:

formatter = logging.Formatter('%(asctime)s : %(message)s')

и я хочу добавить новое поле под именем app_name и которое будет иметь другое значение в каждом script, которое содержит этот форматтер.

import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

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

Я пробовал:

logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')

но никто не работает.

Ответ 1

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

import logging
extra = {'app_name':'Super App'}

logger = logging.getLogger(__name__)
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger = logging.LoggerAdapter(logger, extra)
logger.info('The sky is so blue')

логи (что-то вроде)

2013-07-09 17:39:33,596 Super App : The sky is so blue

Фильтры также можно использовать для добавления контекстной информации.

import logging

class AppFilter(logging.Filter):
    def filter(self, record):
        record.app_name = 'Super App'
        return True

logger = logging.getLogger(__name__)
logger.addFilter(AppFilter())
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger.info('The sky is so blue')

создает аналогичную запись в журнале.

Ответ 2

Вам нужно передать dict в качестве параметра для дополнительного, чтобы сделать это таким образом.

logging.info('Log message', extra={'app_name': 'myapp'})

Доказательство:

>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test 

Кроме того, в качестве примечания, если вы попытаетесь зарегистрировать сообщение без передачи dict, то он не будет выполнен.

>>> logging.warning('test')
Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 846, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 723, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 467, in format
    s = self._fmt % record.__dict__
KeyError: 'foo'
Logged from file <stdin>, line 1

Ответ 3

Другой способ - создать собственный LoggerAdapter. Это особенно полезно, когда вы не можете изменить формат ИЛИ, если ваш формат используется совместно с кодом, который не отправляет уникальный ключ (в вашем случае имя_приложения):

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

И в своем коде вы, как обычно, создаете и инициализируете свой регистратор:

    logger = logging.getLogger(__name__)
    # Add any custom handlers, formatters for this logger
    myHandler = logging.StreamHandler()
    myFormatter = logging.Formatter('%(asctime)s %(message)s')
    myHandler.setFormatter(myFormatter)
    logger.addHandler(myHandler)
    logger.setLevel(logging.INFO)

Наконец, вы должны создать адаптер оболочки, чтобы добавить префикс по мере необходимости:

    logger = LoggerAdapter(logger, 'myapp')
    logger.info('The world bores you when you are cool.')

Результат будет выглядеть примерно так:

2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.

Ответ 4

python3

Начиная с Python3.2, теперь вы можете использовать LogRecordFactory

>>> import logging
>>> logging.basicConfig(format="%(custom_attribute)s - %(message)s")
>>> old_factory = logging.getLogRecordFactory()
>>> def record_factory(*args, **kwargs):
        record = old_factory(*args, **kwargs)
        record.custom_attribute = "my-attr"
        return record

>>> logging.setLogRecordFactory(record_factory)
>>> logging.info("hello")
my-attr - hello

Конечно, record_factory можно настроить так, чтобы он вызывался любым образом, а значение custom_attribute можно обновить, если вы сохраните ссылку на заводской вызов.

Почему это лучше, чем использование адаптеров/фильтров?

  • Вам не нужно передавать логгер вокруг приложения
  • На самом деле он работает со сторонними библиотеками, которые используют собственный регистратор (просто вызывая logger = logging.getLogger(..)), теперь будет иметь тот же формат журнала. (это не относится к фильтрам/адаптерам, где необходимо использовать один и тот же объект регистратора)
  • Вы можете сложить/связать несколько заводов

Ответ 5

Используя ответ mr2ert, я пришел к этому удобному решению (хотя, думаю, это не рекомендуется) - переопределить встроенные методы ведения журнала, чтобы принять пользовательский аргумент, и создать словарь extra внутри методов:

import logging

class CustomLogger(logging.Logger):

   def debug(self, msg, foo, *args, **kwargs):
       extra = {'foo': foo}

       if self.isEnabledFor(logging.DEBUG):
            self._log(logging.DEBUG, msg, args, extra=extra, **kwargs)

   *repeat for info, warning, etc*

logger = CustomLogger('CustomLogger', logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(foo)s] %(message)s') 
handler = logging.StreamHandler()
handler.setFormatter(formatter) 
logger.addHandler(handler)

logger.debug('test', 'bar')

Выход:

2019-03-02 20:06:51,998 [bar] test

Это встроенная функция для справки:

def debug(self, msg, *args, **kwargs):
    """
    Log 'msg % args' with severity 'DEBUG'.

    To pass exception information, use the keyword argument exc_info with
    a true value, e.g.

    logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
    """
    if self.isEnabledFor(DEBUG):
        self._log(DEBUG, msg, args, **kwargs)