如何在Python中使用相同日志处理程序的不同格式化程序

23 投票
3 回答
10313 浏览
提问于 2025-04-15 16:04

有没有办法让多个记录器(比如 logging.getLogger("base.foo")logging.getLogger("base.bar"))都把日志记录到同一个地方(也就是用一个 FileHandler),而且每个记录器使用不同的格式?

我理解的情况是,每个处理器只能分配一个格式化器。也许可以把格式化器和记录器关联起来,而不是和处理器关联?

3 个回答

1

这个对你有用吗?它可以输出不同的日志级别格式,并且可以选择不同的日志去向,比如文件和标准输出(stdout),而且每个去向可以设置不同的日志级别:

    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)

    format_for_stdout = logging.Formatter('%(message)s')
    format_for_logfile = logging.Formatter('%(asctime)s: %(name)s: %(levelname)s: %(message)s')

    handler_logfile = logging.FileHandler('my_awesome_logs.log')
    handler_logfile.setLevel(logging.DEBUG)
    handler_logfile.setFormatter(format_for_logfile)

    handler_stdout = logging.StreamHandler()
    handler_stdout.setLevel(logging.INFO)
    handler_stdout.setFormatter(format_for_stdout)

    logger.addHandler(handler_logfile)
    logger.addHandler(handler_stdout)

    logging.addLevelName(logging.INFO, logging.getLevelName(logging.INFO))
    logging.addLevelName(logging.ERROR, logging.getLevelName(logging.ERROR))
6

这是对优秀的Denis解决方案的小改进。

这里有一个关于日志记录名称系统的介绍,它是基于层级结构的:

这个name可以是用点分隔的层级值,比如foo.bar.baz(当然也可以只是简单的foo)。在这个层级列表中,位于下方的日志记录器是位于上方日志记录器的子级。例如,如果有一个名为foo的日志记录器,那么名为foo.barfoo.bar.bazfoo.bam的日志记录器都是foo的后代。

举个例子,当你为某个日志记录器使用setLevel()时,这个级别也会应用到它的子级日志记录器上。这就是为什么你可能希望你的格式化器也能用于日志记录器及其子级日志记录器。例如,'one.two'的格式化器也应该应用于'one.two.three'日志记录器(如果没有为'one.two.three'设置格式化器的话)。下面是一个可以完成这个工作的DispatchingFormatter的版本(Python 3代码):

class DispatchingFormatter:
    """Dispatch formatter for logger and it's sub logger."""
    def __init__(self, formatters, default_formatter):
        self._formatters = formatters
        self._default_formatter = default_formatter

    def format(self, record):
        # Search from record's logger up to it's parents:
        logger = logging.getLogger(record.name)
        while logger:
            # Check if suitable formatter for current logger exists:
            if logger.name in self._formatters:
                formatter = self._formatters[logger.name]
                break
            else:
                logger = logger.parent
        else:
            # If no formatter found, just use default:
            formatter = self._default_formatter
        return formatter.format(record)

示例:

handler = logging.StreamHandler()
handler.setFormatter(DispatchingFormatter({
        'one': logging.Formatter('%(message)s -> one'),
        'one.two': logging.Formatter('%(message)s -> one.two'),
    },
    logging.Formatter('%(message)s -> <default>'),
))
logging.getLogger().addHandler(handler)

print('Logger used -> formatter used:')
logging.getLogger('one').error('one')
logging.getLogger('one.two').error('one.two')
logging.getLogger('one.two.three').error('one.two.three')  # parent formatter 'one.two' will be used here
logging.getLogger('other').error('other')

# OUTPUT:
# Logger used -> formatter used:
# one -> one
# one.two -> one.two
# one.two.three -> one.two
# other -> <default>
26

根据 record.name 来选择不同的格式化方式是很简单的。下面是一个概念验证的示例代码:

import logging


class DispatchingFormatter:

    def __init__(self, formatters, default_formatter):
        self._formatters = formatters
        self._default_formatter = default_formatter

    def format(self, record):
        formatter = self._formatters.get(record.name, self._default_formatter)
        return formatter.format(record)


handler = logging.StreamHandler()
handler.setFormatter(DispatchingFormatter({
        'base.foo': logging.Formatter('FOO: %(message)s'),
        'base.bar': logging.Formatter('BAR: %(message)s'),
    },
    logging.Formatter('%(message)s'),
))
logging.getLogger().addHandler(handler)

logging.getLogger('base.foo').error('Log from foo')
logging.getLogger('base.bar').error('Log from bar')
logging.getLogger('base.baz').error('Log from baz')

另一种方法是手动打开文件,然后从中创建两个流处理器,每个处理器使用不同的格式化方式。

撰写回答