23

在这里为 python 2 提出了这个问题,但是当答案不再适用于 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 稍作修改。初始化():

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 在我自己破坏它之后。

我的伐木工作很快就无法解决这个皱纹了。

谁能推荐另一种方式,或者指出我忽略了什么?

4

7 回答 7

25

经过一番挖掘,我能够修改 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 版本):

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

hdlr.setFormatter(fmt)
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.DEBUG)
于 2013-02-13T17:37:40.260 回答
5

我更喜欢这个,因为它更短、更简单,并且不需要像 'ERROR' 这样的字符串进行硬编码。无需重置._fmt,因为else:可以处理得很好。

此外, using"%(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

如果您想让 levelname 着色:

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

于 2020-06-20T16:24:35.047 回答
2

交叉发布另一个答案。它不起作用,因为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)
于 2013-05-21T20:20:55.667 回答
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
于 2016-04-14T15:53:23.323 回答
1

由于一些奇怪的原因,@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)
于 2019-02-18T02:31:51.937 回答
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__

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

于 2021-02-19T14:55:48.947 回答
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
于 2021-06-27T18:56:10.173 回答