Можно ли изменить формат ведения журнала в Python в зависимости от уровня журнала сообщений? - PullRequest
58 голосов
/ 27 августа 2009

Я использую механизм Python logging для печати вывода на экран. Я мог бы сделать это с помощью операторов print, но я хочу разрешить более тонкую настройку детализации для пользователя, чтобы отключить определенные типы вывода. Мне нравится формат, напечатанный для ошибок, но я бы предпочел более простой формат, когда уровень вывода - «информация».

Например:

  logger.error("Running cmd failed")
  logger.info("Running cmd passed")

В этом примере я бы хотел, чтобы формат ошибки выводился по-другому:

# error
Aug 27, 2009 - ERROR: Running cmd failed
# info
Running cmd passed

Возможно ли иметь разные форматы для разных уровней ведения журнала, не имея нескольких объектов ведения журнала? Я бы предпочел сделать это без изменения регистратора, как только он будет создан, поскольку существует большое количество операторов if / else для определения того, как вывод должен регистрироваться.

Ответы [ 7 ]

57 голосов
/ 02 декабря 2011

Я только что столкнулся с этой проблемой, и у меня были проблемы с заполнением "дыр", оставленных в приведенном выше примере. Вот более полная, рабочая версия, которую я использовал. Надеюсь, это кому-нибудь поможет:

# Custom formatter
class MyFormatter(logging.Formatter):

    err_fmt  = "ERROR: %(msg)s"
    dbg_fmt  = "DBG: %(module)s: %(lineno)d: %(msg)s"
    info_fmt = "%(msg)s"


    def __init__(self, fmt="%(levelno)s: %(msg)s"):
        logging.Formatter.__init__(self, fmt)


    def format(self, record):

        # Save the original format configured by the user
        # when the logger formatter was instantiated
        format_orig = self._fmt

        # Replace the original format with one customized by logging level
        if record.levelno == logging.DEBUG:
            self._fmt = MyFormatter.dbg_fmt

        elif record.levelno == logging.INFO:
            self._fmt = MyFormatter.info_fmt

        elif record.levelno == logging.ERROR:
            self._fmt = MyFormatter.err_fmt

        # Call the original formatter class to do the grunt work
        result = logging.Formatter.format(self, record)

        # Restore the original format configured by the user
        self._fmt = format_orig

        return result

Edit:

Комплименты Halloleo, вот пример того, как использовать вышеизложенное в вашем скрипте:

fmt = MyFormatter()
hdlr = logging.StreamHandler(sys.stdout)

hdlr.setFormatter(fmt)
logging.root.addHandler(hdlr)
logging.root.setLevel(DEBUG)

Редактировать 2:

Запись в Python3 немного изменилась. Смотрите здесь подход Python3.

30 голосов
/ 27 августа 2009

Да, вы можете сделать это с помощью пользовательского Formatter класса:

class MyFormatter(logging.Formatter):
    def format(self, record):
        #compute s according to record.levelno
        #for example, by setting self._fmt
        #according to the levelno, then calling
        #the superclass to do the actual formatting
        return s

Затем присоедините экземпляр MyFormatter к вашим обработчикам.

16 голосов
/ 25 января 2013

И снова, как ответ JS, но более компактный.

class SpecialFormatter(logging.Formatter):
    FORMATS = {logging.DEBUG :"DBG: %(module)s: %(lineno)d: %(message)s",
               logging.ERROR : "ERROR: %(message)s",
               logging.INFO : "%(message)s",
               'DEFAULT' : "%(levelname)s: %(message)s"}

    def format(self, record):
        self._fmt = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
        return logging.Formatter.format(self, record)

hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)
8 голосов
/ 08 января 2015

Вместо того, чтобы полагаться на стили или внутренние поля, вы также можете создать модуль форматирования, который делегирует другим форматерам в зависимости от record.levelno (или других критериев). Это немного более чистое решение по моему скромному мнению. Код ниже должен работать для любой версии Python> = 2.7:

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

class MyFormatter(logging.Formatter):

    default_fmt = logging.Formatter('%(levelname)s in %(name)s: %(message)s')
    info_fmt = logging.Formatter('%(message)s')

    def format(self, record):
        if record.levelno == logging.INFO:
            return self.info_fmt.format(record)
        else:
            return self.default_fmt.format(record)

Но вы можете сделать его более общим:

class VarFormatter(logging.Formatter):

    default_formatter = logging.Formatter('%(levelname)s in %(name)s: %(message)s')

    def __init__(self, formats):
        """ formats is a dict { loglevel : logformat } """
        self.formatters = {}
        for loglevel in formats:
            self.formatters[loglevel] = logging.Formatter(formats[loglevel])

    def format(self, record):
        formatter = self.formatters.get(record.levelno, self.default_formatter)
        return formatter.format(record)

Я использовал в качестве ввода слова dict, но, очевидно, вы также можете использовать кортежи, ** kwargs, все, что плавает на вашей лодке. Это тогда будет использоваться как:

formatter = VarFormatter({logging.INFO: '[%(message)s]', 
                          logging.WARNING: 'warning: %(message)s'})
<... attach formatter to logger ...>
8 голосов
/ 21 мая 2013

Это адаптация ответа estani к новой реализации logging.Formatter, которая теперь использует стили форматирования. Мой опирается на стиль '{', но его можно адаптировать. Может быть уточнено, чтобы быть более общим и разрешить выбор стиля форматирования и пользовательских сообщений в качестве аргументов для __init__.

class SpecialFormatter(logging.Formatter):
    FORMATS = {logging.DEBUG : logging._STYLES['{']("{module} DEBUG: {lineno}: {message}"),
           logging.ERROR : logging._STYLES['{']("{module} ERROR: {message}"),
           logging.INFO : logging._STYLES['{']("{module}: {message}"),
           'DEFAULT' : logging._STYLES['{']("{module}: {message}")}

    def format(self, record):
        # Ugly. Should be better
        self._style = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
        return logging.Formatter.format(self, record)

hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)
6 голосов
/ 21 февраля 2014

Приведенное выше решение работает с выпуском 3.3.3. Однако с 3.3.4 вы получите следующую ошибку.

FORMATS = { logging.DEBUG : logging._STYLES['{']("{module} DEBUG: {lineno}: {message}"),

TypeError: объект 'tuple' не может быть вызван

После некоторых поисков в классе логов Lib \ logging__init __. Ру Я обнаружил, что структура данных изменилась с 3.3.3 до 3.3.4, что вызывает проблему

3.3.3

_STYLES = {
    '%': PercentStyle,
    '{': StrFormatStyle,
    '$': StringTemplateStyle
}

3.3.4

_STYLES = {
   '%': (PercentStyle, BASIC_FORMAT),
   '{': (StrFormatStyle, '{levelname}:{name}:{message} AA'),
    '$': (StringTemplateStyle, '${levelname}:${name}:${message} BB'),
}

Таким образом, обновленное решение

class SpecialFormatter(logging.Formatter):
     FORMATS = {logging.DEBUG : logging._STYLES['{'][0]("{module} DEBUG: {lineno}: {message}"),
       logging.ERROR : logging._STYLES['{'][0]("{module} ERROR: {message}"),
       logging.INFO : logging._STYLES['{'][0]("{module}: {message}"),
       'DEFAULT' : logging._STYLES['{'][0]("{module}: {message}")}

 def format(self, record):
    # Ugly. Should be better
    self._style = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
    return logging.Formatter.format(self, record)
3 голосов
/ 03 августа 2014

Если вы просто хотите пропустить форматирование определенных уровней, вы можете сделать что-то более простое, чем другие ответы, такие как:

class FormatterNotFormattingInfo(logging.Formatter):
    def __init__(self, fmt = '%(levelname)s:%(message)s'):
        logging.Formatter.__init__(self, fmt)

    def format(self, record):
        if record.levelno == logging.INFO:
            return record.getMessage()
        return logging.Formatter.format(self, record)

Это также имеет преимущество работы до и после выпуска 3.2, поскольку не используются внутренние переменные, такие как self._fmt или self._style.

...