根据Python3中的消息日志记录级别修改日志消息格式

时间:2013-02-13 01:25:34

标签: logging python-3.x

我为python 2 here提出了这个问题,但是当答案不再适用于Python 3.2.3时,又遇到了问题。

这是适用于Python 2.7.3的代码:

import logging

# Attempt to set up a Python3 logger than will print custom messages
# based on each message's logging level.
# The technique recommended for Python2 does not appear to work for
# Python3

class CustomConsoleFormatter(logging.Formatter):
    """
    Modify the way DEBUG messages are displayed.

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

    def format(self, record):

        # Remember the original format
        format_orig = self._fmt

        if record.levelno == logging.DEBUG:
            self._fmt = "DEBUG: %(msg)s"

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

        # Restore the original format
        self._fmt = format_orig

        return result


# Set up a logger
my_logger = logging.getLogger("my_custom_logger")
my_logger.setLevel(logging.DEBUG)

my_formatter = CustomConsoleFormatter()

console_handler = logging.StreamHandler()
console_handler.setFormatter(my_formatter)

my_logger.addHandler(console_handler)

my_logger.debug("This is a DEBUG-level message")
my_logger.info("This is an INFO-level message")

使用Python 2.7.3运行:

tcsh-16: python demo_python_2.7.3.py 
DEBUG: This is a DEBUG-level message
20: This is an INFO-level message


据我所知,转换为Python3只需要一个微调的CustomConsoleFormatter。 init ():

def __init__(self):
    super().__init__(fmt="%(levelno)d: %(msg)s", datefmt=None, style='%')

在Python 3.2.3上:

tcsh-26: python3 demo_python_3.2.3.py
10: This is a DEBUG-level message
20: This is an INFO-level message


正如你所看到的,我想用'DEBUG'取代'10'的愿望正在被挫败。

我已经尝试在Python3源代码中进行挖掘,看起来PercentStyle实例化在我之后破坏了self._fmt,好吧,我自己克服它。

我的伐木剁停止了能够解决这种皱纹。

任何人都可以推荐另一种方式或者指出我忽略的东西吗?

7 个答案:

答案 0 :(得分:12)

通过一些挖掘,我能够修改Python 2解决方案以使用Python 3.在Python2中,有必要临时覆盖Formatter._fmt。在Python3中,对多种格式字符串类型的支持要求我们暂时覆盖Formatter._style._fmt

# 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):
        super().__init__(fmt="%(levelno)d: %(msg)s", datefmt=None, style='%')  

    def format(self, record):

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

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

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

        elif record.levelno == logging.ERROR:
            self._style._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._style._fmt = format_orig

        return result

以下是Halloleo的示例,说明如何在脚本中使用上述内容(来自Python2 version of this question):

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

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

答案 1 :(得分:3)

another answer的交叉发布。它不起作用,因为logging.Formatter的新的(3.2 +,3.4现在)实现现在依赖于格式化样式。这取决于'{'样式格式,但可以进行调整。可以改进为更一般,并允许选择格式样式和自定义消息作为__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)

答案 2 :(得分:2)

我更喜欢这样做,因为它更短,更简单,并且不需要像“ ERROR”这样的字符串进行硬编码。无需重置._fmt,因为else:可以很好地解决此问题。

此外,使用"%(msg)s"不适用于延迟日志记录!

class Formatter(logging.Formatter):
    def format(self, record):
        if record.levelno == logging.INFO:
            self._style._fmt = "%(message)s"
        else:
            self._style._fmt = "%(levelname)s: %(message)s"
        return super().format(record)

用法示例:

import logging

logger = logging.getLogger()
handler = logging.StreamHandler()
handler.setFormatter(Formatter())
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)

logger.debug('foo')
logger.info('bar %d', 4)
DEBUG: foo
bar 4

如果您要为级别名称上色:

class Formatter(logging.Formatter):
    def format(self, record):
        if record.levelno == logging.INFO:
            self._style._fmt = "%(message)s"
        else:
            color = {
                logging.WARNING: 33,
                logging.ERROR: 31,
                logging.FATAL: 31,
                logging.DEBUG: 36
            }.get(record.levelno, 0)
            self._style._fmt = f"\033[{color}m%(levelname)s\033[0m: %(message)s"
        return super().format(record)

有关颜色编号,请参见https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit

答案 3 :(得分:1)

我对这个问题迟到了,但这是我的解决方案。它遵循原始的python 2语法风格。通常,由于添加了样式支持,您应该使用三个新类。它们是:PercentStyle,StrFormatStyle和StringTemplateStyle。

from logging import Formatter, PercentStyle, ERROR, WARNING, INFO, DEBUG
class SrvLogFormat(Formatter):
    def __init__(self):
        super().__init__(fmt=env.fmt_log, datefmt=env.fmt_log_date)

    def format(self, record):
        original_style = self._style

        if record.levelno == DEBUG:
            self._style = PercentStyle(env.fmt_dflt)
        if record.levelno == INFO:
            self._style = PercentStyle(env.fmt_dflt)
        if record.levelno == WARNING:
            self._style = PercentStyle(env.fmt_dflt)
        if record.levelno == ERROR:
            self._style = PercentStyle(env.fmt_err)

        result = Formatter.format(self, record)
        self._style = original_style
        return result

答案 4 :(得分:0)

出于某些奇怪的原因,@ JS和@Evpok的解决方案引发了一些错误(我正在使用Python 3.7,这可能就是原因)。

此解决方案对我有用:

class CustomFormatter(logging.Formatter):
    """Logging Formatter to add colors and count warning / errors"""

    FORMATS = {
        logging.ERROR: "ERROR: %(msg)s",
        logging.WARNING: "WARNING: %(msg)s",
        logging.DEBUG: "DBG: %(module)s: %(lineno)d: %(msg)s",
        "DEFAULT": "%(msg)s",
    }

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

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger_ch = logging.StreamHandler()
logger_ch.setLevel(logging.INFO)
logger_ch.setFormatter(CustomFormatter())
logger.addHandler(logger_ch)

答案 5 :(得分:0)

import logging
from logging import DEBUG, INFO, WARN, ERROR

class LogFormatter(logging.Formatter):

    formats = {
        DEBUG: "DEBUG: %(msg)s",
        INFO:  "%(msg)s",
        WARN:  "WARNING: %(msg)s",
        ERROR: "ERROR: %(msg)s"
    }
    
    def format(self, record):
        return LogFormatter.formats.get(
            record.levelno, self._fmt) % record.__dict__

在logging/__init__.py的源码中,PercentStyle的_format方法很简单

    def _format(self, record):
        return self._fmt % record.__dict__

因此使用 % 运算符也可以。

答案 6 :(得分:0)

这个 Python3 问题和它的 Python2 问题的答案都存在重大缺陷:

  • 当级别不是离散的时,可以通过相等比较或字典查找来选择级别;它们存在于整数行上,应该是可排序的。
  • 当格式化程序实例发生变化时,没有锁来保护 _fmt 成员,因此这是线程不安全的。
  • 对该成员的修改至少应该用 try/finally 括起来。

确实,该成员根本不应该被修改。解决此问题的一种简单方法是实例化一个或多个不可变的代理格式化程序并根据需要遵循它们。此外,通过二分而不是相等来选择它们,以支持任意级别:

import logging
from bisect import bisect
from logging import getLogger, Formatter, LogRecord, StreamHandler
from typing import Dict


class LevelFormatter(Formatter):
    def __init__(self, formats: Dict[int, str], **kwargs):
        super().__init__()

        if 'fmt' in kwargs:
            raise ValueError(
                'Format string must be passed to level-surrogate formatters, '
                'not this one'
            )

        self.formats = sorted(
            (level, Formatter(fmt, **kwargs)) for level, fmt in formats.items()
        )

    def format(self, record: LogRecord) -> str:
        idx = bisect(self.formats, (record.levelno,), hi=len(self.formats)-1)
        level, formatter = self.formats[idx]
        return formatter.format(record)


def test():
    handler = StreamHandler()
    handler.setFormatter(
        LevelFormatter(
            {
                logging.INFO: '%(levelname)s (info): %(message)s',
                logging.WARNING: '%(levelname)s: (warning): %(message)s',
            }
        )
    )
    handler.setLevel(logging.DEBUG)

    logger = getLogger('test_logger')
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)

    logger.debug('mdebug')
    logger.info('minfo')
    logger.log(logging.INFO + 1, 'higher minfo')
    logger.warning('mwarning')
    logger.error('merror')
    logger.critical('mcritical')


test()

输出

DEBUG (info): mdebug
INFO (info): minfo
Level 21: (warning): higher minfo
WARNING: (warning): mwarning
ERROR: (warning): merror
CRITICAL: (warning): mcritical