Python中的多行日志记录

28 投票
4 回答
48148 浏览
提问于 2025-04-18 01:51

我正在使用Python 3.3.5和日志模块,将信息记录到本地文件中(来自不同的线程)。有时候,我想输出一些额外的信息,但我并不知道具体会是什么信息(比如可能是一行简单的文本,或者是一个字典)。

我希望在日志记录写入后,把这些额外的信息添加到我的日志文件中。而且,这些额外的信息只有在日志级别是错误(或者更高)时才需要。

理想情况下,它的样子应该是这样的:

2014-04-08 12:24:01 - INFO     - CPU load not exceeded
2014-04-08 12:24:26 - INFO     - Service is running
2014-04-08 12:24:34 - ERROR    - Could not find any active server processes
Additional information, might be several lines.
Dict structured information would be written as follows:
key1=value1
key2=value2
2014-04-08 12:25:16 - INFO     - Database is responding

除了写一个自定义的日志格式器,我找不到什么能满足我需求的东西。我了解过过滤器和上下文,但这些似乎也不太合适。

另外,我可以直接使用标准输入输出写入文件,但大部分功能在日志模块中已经存在,而且它是线程安全的。

任何建议都将非常感激。如果确实需要一个自定义的日志格式器,任何开始的指引都会很棒。

4 个回答

1

看起来我在定义我的 LogFormatter 字符串时犯了个小错误:我不小心把换行符搞错了,以为不能把多行内容写入日志文件。

感谢 @Barafu 指出这个问题(这也是我把正确答案给他的原因)。

下面是示例代码:

import logging
lf = logging.Formatter('%(levelname)-8s - %(message)s\n%(detail)s')
lh = logging.FileHandler(filename=r'c:\temp\test.log')
lh.setFormatter(lf)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
log.addHandler(lh)
log.debug('test', extra={'detail': 'This is a multi-line\ncomment to test the formatter'})

最终的输出会是这样的:

DEBUG    - test
This is a multi-line
comment to test the formatter

注意事项:

如果没有详细的信息要记录,而你传了一个空字符串,日志记录器仍然会输出一个换行符。所以,接下来的问题是:我们怎么才能让这个输出变得有条件呢?

一种方法是在实际记录信息之前更新日志格式器,具体可以参考 这里

4

我在一些小应用中使用了一个简单的行分割器:

for line in logmessage.splitlines():
            writemessage = logtime + " - " + line + "\n"
            logging.info(str(writemessage))

请注意,这个方法不是线程安全的,最好只在日志量不大的情况下使用。

不过,你几乎可以把任何东西输出到日志中,因为它会保持你的格式。我曾经用它来输出格式化的JSON API响应,使用的代码是:json.dumps(parsed, indent=4, sort_keys=True)

24

很多人认为多行日志信息是一种不好的做法,这个观点是可以理解的。因为如果你使用像DataDog或Splunk这样的日志处理工具,它们更擅长处理单行日志,而多行日志就会很难解析。不过,你可以利用extra这个参数,配合自定义格式化器,把一些额外的信息添加到要显示的消息中(你可以查看日志包中extra的用法,具体可以参考文档)。

import logging

class CustomFilter(logging.Filter):
    def filter(self, record):
        if hasattr(record, 'dct') and len(record.dct) > 0:
            for k, v in record.dct.iteritems():
                record.msg = record.msg + '\n\t' + k + ': ' + v
        return super(CustomFilter, self).filter(record)
        
if __name__ == "__main__":
    logging.getLogger().setLevel(logging.DEBUG)
    extra_logger = logging.getLogger('extra_logger')
    extra_logger.setLevel(logging.INFO)
    extra_logger.addFilter(CustomFilter())
    logging.debug("Nothing special here... Keep walking")
    extra_logger.info("This shows extra",
                      extra={'dct': {"foo": "bar", "baz": "loren"}})
    extra_logger.debug("You shouldn't be seeing this in the output")
    extra_logger.setLevel(logging.DEBUG)
    extra_logger.debug("Now you should be seeing it!")
    

这段代码的输出是:

DEBUG:root:Nothing special here... Keep walking
INFO:extra_logger:This shows extra
        foo: bar
        baz: loren
DEBUG:extra_logger:Now you should be seeing it!

我还是建议在你自定义的过滤器中调用superfilter函数,主要是因为这个函数决定了是否显示消息(比如,如果你的日志记录器的级别设置为logging.INFO,而你用extra_logger.debug记录了一条信息,那么这条消息就不应该被看到,正如上面的例子所示)。

10

我只是给输出的文本加上了\n符号。

撰写回答