Можно ли изменить формат журнала Python в зависимости от уровня журнала сообщений?

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

Например:

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

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

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

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

Да, вы можете сделать это, создав собственный класс 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 к вашим обработчикам.

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

 # 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 

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

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

 fmt = MyFormatter() hdlr = logging.StreamHandler(sys.stdout) hdlr.setFormatter(fmt) logging.root.addHandler(hdlr) logging.root.setLevel(DEBUG) 

Изменить 2:

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

И снова, как 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) 

Это адаптация ответа estani на новую реализацию logging.Formatter которая теперь опирается на стили форматирования. Мина опирается на формат '{' style, но его можно адаптировать. Может быть уточнен, чтобы быть более общим и разрешить выбор стиля форматирования и пользовательских сообщений в качестве аргументов для __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) 

Вместо того, чтобы полагаться на стили или внутренние поля, вы также можете создать Formatter, который делегирует другие форматы в зависимости от 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) 

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

 formatter = VarFormatter({logging.INFO: '[%(message)s]', logging.WARNING: 'warning: %(message)s'}) <... attach formatter to logger ...> 

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

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

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

После некоторого поиска в классе ведения журнала Lib \ logging__init __. Py я обнаружил, что структура данных изменилась с 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) 

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

 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.