Python的日志格式是否可以根据消息日志级别进行修改?

97
我正在使用Python的logging机制将输出打印到屏幕上。虽然我可以使用print语句来实现,但我想允许用户更细粒度地禁用某些类型的输出。我喜欢打印错误时的格式,但当输出级别为“info”时,我更喜欢一个更简单的格式。
例如:
  logger.error("Running cmd failed")
  logger.info("Running cmd passed")

在这个例子中,我希望错误的格式被以不同的方式打印出来:
# error
Aug 27, 2009 - ERROR: Running cmd failed
# info
Running cmd passed

是否可以在不创建多个记录对象的情况下,为不同的日志级别使用不同的格式?我希望不修改创建后的记录器来实现这一点,因为有大量的if/else语句来确定应该如何记录输出。

8个回答

94

我刚遇到了这个问题,填补上面示例中留下的“空洞”让我很困难。这是我使用的更完整、可工作的版本。希望能帮助到某些人:

# 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 的解决方案。


2
这里我可以添加如何在程序中使用MyFormatter类的方法(将每个<CR>替换为回车符):`fmt = MyFormatter()` `hdlr = logging.StreamHandler(sys.stdout)`<CR> hdlr.setFormatter(fmt)<CR> logging.root.addHandler(hdlr)<CR> logging.root.setLevel(DEBUG)`<CR> - halloleo
7
由于内部日志机制的更改,此答案在3.2版本之后将不起作用。logging.Formatter.format现在依赖于__init__方法的style参数。 - Evpok
3
Evpok是正确的。在分配self._fmt后添加以下内容:self._style = logging.PercentStyle(self._fmt) - Ross R
3
ن½؟用super()ن»£و›؟调用logging.Formatterن¼ڑن¸چن¼ڑ让ه®ƒهڈکه¾—و›´ه¥½ï¼ں - phoenix
1
@phoenix:是的。观察得很好。 - JS.
显示剩余6条评论

32

是的,你可以通过拥有一个自定义的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并在需要时更改消息。否则,它将其重置回我传递的原始字符串。谢谢! - bedwyr
16
请取消此答案的选中标记。刚才下面的那个是完整的答案。此答案中有很多代码遗漏,只有注释描述了应该做什么。请移步下面的答案。 - Utkonos

22

实现这个的一种方法

定义一个类

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")

结果 这里输入图片描述


1
这似乎是Python 3.6上唯一有效的解决方案。 - user3821178
1
我尝试在这里 https://dev59.com/wnRC5IYBdhLWcg3wMd5S#56944256 保持相同的答案更新,如果有帮助请点赞。谢谢 @user3821178 - Sergey Pleshakov
这是一个非常好的答案。颜色编码日志记录也非常有帮助。 - Akash Desarda
请注意,您可以在init中实例化格式化程序,而不是在每次调用format时都实例化。 - postelrich

18

与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)

2
由于内部日志机制的更改,在3.2之后,此答案将不起作用。logging.Formatter.format现在依赖于__init__style参数。 - Evpok

14

不要仅仅依赖样式或内部字段,您还可以创建一个格式化程序(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 ...>

10

这是对 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)

1
感谢您更新以使其与Python3兼容。我在Python3中遇到了同样的问题,并想出了类似的解决方案。您能否也请在那里发布这个答案?https://dev59.com/H2Uq5IYBdhLWcg3wC8BQ - JS.
2
在你的注释中使用新的 '{' 风格了吗? :-) - JS.

6

上述解决方案适用于3.3.3版本。但是在3.3.4中,您会遇到以下错误。

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

类型错误:'tuple'对象不可调用

在logging类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)

可能直接使用 from logging import StrFormatStyle 导入样式类型会更容易,而不是使用 logging._STYLES['{'][0] - TomDotTom

5
如果您只是想跳过某些级别的格式化,您可以像以下简单的方法一样做,不必像其他答案那样复杂:
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这样的内部变量。


我认为这是最简洁的解决方案。 - vlk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接