【问题标题】:Can Python's logging format be modified depending on the message log level?Python的日志格式可以根据消息日志级别进行修改吗?
【发布时间】:2010-11-23 12:21:06
【问题描述】:

我正在使用 Python 的 logging 机制将输出打印到屏幕上。我可以使用打印语句来做到这一点,但我希望允许用户更精细地调整粒度以禁用某些类型的输出。我喜欢打印错误的格式,但当输出级别为“信息”时,我更喜欢更简单的格式。

例如:

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

在本例中,我希望以不同的方式打印错误的格式:

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

是否可以在没有多个日志记录对象的情况下为不同的日志级别提供不同的格式?我宁愿在创建记录器后不对其进行修改,因为有大量 if/else 语句来确定应如何记录输出。

【问题讨论】:

    标签: python logging string-formatting


    【解决方案1】:

    一种方法

    定义一个类

    import logging
    
    class CustomFormatter(logging.Formatter):
        """Logging Formatter to add colors and count warning / errors"""
    
        grey = "\x1b[38;21m"
        yellow = "\x1b[33;21m"
        red = "\x1b[31;21m"
        bold_red = "\x1b[31;1m"
        reset = "\x1b[0m"
        format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
    
        FORMATS = {
            logging.DEBUG: grey + format + reset,
            logging.INFO: grey + format + reset,
            logging.WARNING: yellow + format + reset,
            logging.ERROR: red + format + reset,
            logging.CRITICAL: bold_red + format + reset
        }
    
        def format(self, record):
            log_fmt = self.FORMATS.get(record.levelno)
            formatter = logging.Formatter(log_fmt)
            return formatter.format(record)
    

    实例化记录器

    # create logger with 'spam_application'
    logger = logging.getLogger("My_app")
    logger.setLevel(logging.DEBUG)
    
    # create console handler with a higher log level
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    
    ch.setFormatter(CustomFormatter())
    
    logger.addHandler(ch)
    

    并使用!

    logger.debug("debug message")
    logger.info("info message")
    logger.warning("warning message")
    logger.error("error message")
    logger.critical("critical message")
    

    结果

    【讨论】:

    • 这似乎是适用于 python 3.6 的唯一答案
    • 我尝试在stackoverflow.com/a/56944256/9150146 处更新相同的答案,如果有帮助请点赞。谢谢@user3821178
    • 这是一个很好的答案。颜色编码记录也很有帮助。
    【解决方案2】:

    我刚刚遇到了这个问题,无法填补上面示例中留下的“漏洞”。这是我使用的更完整的工作版本。希望这可以帮助某人:

    # 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 方法,请参阅 here

    【讨论】:

    • 这很好用!我将引用 MyFormatter 的名称更改为 self 以保持一致性。
    • 在这里我可能会添加您可以在程序中使用 MyFormatter 类的方式(将每个 替换为回车):fmt = MyFormatter()hdlr = logging.StreamHandler(sys.stdout) @987654328 @hdlr.setFormatter(fmt)<CR> logging.root.addHandler(hdlr)<CR> logging.root.setLevel(DEBUG)`
    • 由于内部日志记录机制的变化,此答案在 3.2 之后将不起作用。 logging.Formatter.format 现在取决于 __init__style 参数。
    • Evpok 是对的。分配 self._fmt 后添加:self._style = logging.PercentStyle(self._fmt)
    • 可以通过使用super()而不是调用logging.Formatter来改进吗?
    【解决方案3】:

    除了依赖样式或内部字段,您还可以创建一个 Formatter,根据 record.levelno(或其他标准)委托给其他格式化程序。以我的拙见,这是一个稍微干净的解决方案。下面的代码应该适用于 >= 2.7 的任何 python 版本:

    简单的方法如下所示:

    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 ...>
    

    【讨论】:

      【解决方案4】:

      如果您只是想跳过某些级别的格式,您可以做一些比其他答案更简单的事情,如下所示:

      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 等内部变量。

      【讨论】:

      • 我认为这是最干净的解决方案
      【解决方案5】:

      上述解决方案适用于 3.3.3 版本。 但是在 3.3.4 中会出现以下错误。

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

      TypeError: 'tuple' 对象不可调用

      在日志类中搜索后 库\记录__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)
      

      【讨论】:

      • 直接用from logging import StrFormatStyle导入样式类型可能比logging._STYLES['{'][0]更容易
      【解决方案6】:

      这是 estani's answerlogging.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)
      

      【讨论】:

      • 感谢您更新它以使用 Python3。我在 Python3 中遇到了同样的问题,并提出了类似的解决方案。请您也将这个答案发布在那里好吗? stackoverflow.com/questions/14844970/…
      • 在您的 cmets 中使用新的 '{' 样式? :-)
      【解决方案7】:

      又像 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)
      

      【讨论】:

      • 由于内部日志记录机制的变化,此答案在 3.2 之后将不起作用。 logging.Formatter.format 现在取决于 __init__style 参数。
      【解决方案8】:

      是的,您可以通过自定义 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 实例附加到您的处理程序。

      【讨论】:

      • 出色 - 效果很好。我修改了 format() 方法来检查 levelno 并在需要时更改消息。否则,它会将其重置为我传入的原始字符串。谢谢!
      • 请去掉这个答案的复选标记。就在这下面的一个是完整的。此答案中缺少大量代码,其中只有 cmets 描述您应该做什么。
      猜你喜欢
      • 2013-01-28
      • 2016-02-20
      • 2011-03-09
      • 1970-01-01
      • 1970-01-01
      • 2015-06-03
      • 2021-11-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多