Python: 配置日志记录,允许多行字符串:logging.info('foo \n bar')

9

到目前为止,我只对文件进行简单的日志记录。如果我记录多行字符串,则结果如下:

发出日志:

logging.info('foo\nbar') 

日志文件:

2018-03-05 10:51:53 root.main +16: INFO     [28302] foo

酒吧

到目前为止,所有不包含“INFO”或“DEBUG”的行都会向操作员报告。

这意味着行bar被报告了。这是一个误报。

环境:Linux。

如何在Python中设置日志记录,以保留INFO foo\nbar在一个字符串中并忽略整个字符串,因为它只是“INFO”?

注意:是的,您可以在解释器中过滤日志记录。不幸的是,这不是问题所在。这个问题是不同的。首先发生日志记录。然后解析日志。

以下是可复制该问题的脚本:

import sys
import logging


def set_up_logging(level=logging.INFO):
    root_logger = logging.getLogger()
    root_logger.setLevel(level)
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(
        logging.Formatter('%(asctime)s %(name)s: %(levelname)-8s [%(process)d] %(message)s', '%Y-%m-%d %H:%M:%S'))
    root_logger.addHandler(handler)


def main():
    set_up_logging()
    logging.info('foo\nbar')


if __name__ == '__main__':
    main()

经过再次思考,我认为真正的问题是:哪种日志格式是可行的?仅仅删除跨越多行的消息中的换行符会使得一些输出难以读取。另一方面,当前的 logging.info() 与日志文件中的一行之间的1:1关系容易阅读。...... 我不确定


你能展示一下你的日志设置行吗?例如 logging.basicConfig(..,同时展示不同日志级别下你期望的输出是什么? - Chris_Rands
@Chris_Rands 我添加了一个脚本来重现它。你的问题“你确切的期望输出是什么?”非常好。我不知道。到目前为止,我们解析日志文件并假设每行都是单个日志条目。我猜当前的模式已经不可行了。什么是最简单、合理、最符合Python风格的解决方案? - guettli
我在想(对我来说很少见:))- 你用什么解析日志?也许增强那部分会更容易和一致,特别是如果你不想修改Python源代码。例如,在我的解决方案中,您可能会得到2个或更多警报,其中没有一个包含完整的消息!(这取决于解析器的工作方式)。不过这需要另外一个问题... - urban
你可以使用 logging.info(json.dumps(message)),它将返回一个包含转义换行符的字符串。或者,你可以创建一个自定义的 Formatter 类来自动完成这个操作。 - georgepsarakis
3个回答

13

我通常有一个类来自定义日志记录,但您可以使用自定义的logging.Formatter来实现您想要的效果:

import logging

class NewLineFormatter(logging.Formatter):

    def __init__(self, fmt, datefmt=None):
        """
        Init given the log line format and date format
        """
        logging.Formatter.__init__(self, fmt, datefmt)


    def format(self, record):
        """
        Override format function
        """
        msg = logging.Formatter.format(self, record)

        if record.message != "":
            parts = msg.split(record.message)
            msg = msg.replace('\n', '\n' + parts[0])

        return msg
上述format()函数将每行拆分并在每行(在每个\n后)复制时间戳/日志前导。

现在,您需要将格式化程序附加到根记录器。如果构建自己的日志设置/结构,则实际上可以将其附加到任何handler

# Basic config as usual
logging.basicConfig(level=logging.DEBUG)

# Some globals/consts
DATEFORMAT = '%d-%m-%Y %H:%M:%S'
LOGFORMAT = '%(asctime)s %(process)s %(levelname)-8s %(filename)15s-%(lineno)-4s: %(message)s'

# Create a new formatter
formatter = NewLineFormatter(LOGFORMAT, datefmt=DATEFORMAT)

# Attach the formatter on the root logger
lg = logging.getLogger()

# This is a bit of a hack... might be a better way to do this
lg.handlers[0].setFormatter(formatter)


# test root logger
lg.debug("Hello\nWorld")

# test module logger + JSON
lg = logging.getLogger("mylogger")
lg.debug('{\n    "a": "Hello",\n    "b": "World2"\n}')

以上内容为您提供:

05-03-2018 08:37:34 13065 DEBUG     test_logger.py-47  : Hello
05-03-2018 08:37:34 13065 DEBUG     test_logger.py-47  : World
05-03-2018 08:37:34 13065 DEBUG     test_logger.py-51  : {
05-03-2018 08:37:34 13065 DEBUG     test_logger.py-51  :     "a": "Hello",
05-03-2018 08:37:34 13065 DEBUG     test_logger.py-51  :     "b": "World2"
05-03-2018 08:37:34 13065 DEBUG     test_logger.py-51  : }
请注意我正在访问根记录器的 .handlers[0],这有点像一个 hack,但我找不到其他方法…… 同时请注意格式化的 JSON 输出:)

1
我希望这个问题可以通过配置来解决。但是似乎你需要进行编程……不过当然,你的答案比没有解决方案要好得多 :-) - guettli
我知道了...上述的代码/解决方案是3年前的!因此,在这段时间内,logging可能已经引入了更好的方法来做到这一点。然而,当时我找不到其它可用的选项。 - urban
使用第三方库structlog以及它的处理器堆栈会使得这个过程变得更加容易。 - wim
如果日志调用在后续行中内部存在插值,那么这种方法就不起作用。 - Edward Z. Yang

3

我认为保持这种1:1的关系,即每个logging.info()调用在日志文件中只有一行,非常有必要,以使日志文件简单且易于解析。因此,如果你真的需要记录一个换行符,那么我会简单地记录字符串表示,例如:

logging.info(repr('foo\nbar'))

输出:

2018-03-05 11:34:54 root: INFO     [32418] 'foo\nbar'

一个简单的替代方案是分别记录每个部分:

log_string = 'foo\nbar'
for item in log_string.split('\n'):
    logging.info(item)

输出:

2018-03-05 15:39:44 root: INFO     [4196] foo
2018-03-05 15:39:44 root: INFO     [4196] bar

我不确定。自1996年以来,我一直使用Linux,并且非常喜欢基于简单ASCII的解决方案。但也许现在是时候转换了?这种1:1的关系已经存在很长时间了。有什么可以代替它吗?如果只删除换行符,事情可能会变得难以阅读。 - guettli

0

您可以使用:

logging.basicConfig(level=your_level)

其中 your_level 是以下之一:

  'debug': logging.DEBUG,
  'info': logging.INFO,
  'warning': logging.WARNING,
  'error': logging.ERROR,
  'critical': logging.CRITICAL

在您的情况下,您可以使用警告来忽略信息。
import logging
logging.basicConfig(filename='example.log',level=logging.WARNING)

logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

Output:
WARNING:root:And this, too

您可以尝试在记录日志之前禁用INFO。

import logging
logging.basicConfig(filename='example.log')

logging.debug('This message should go to the log file')
logging.disable(logging.INFO)
logging.info('So should this')
logging.disable(logging.NOTSET)
logging.warning('And this, too')
Output:
WARNING:root:And this, too

或者

logger.disabled = True
someOtherModule.function()
logger.disabled = False

1
可以在解释器中过滤日志。不幸的是,这不是问题所在。这个问题与此不同。首先发生记录。然后解析日志。 - guettli
@guettli 我已经编辑了我的答案,这样你就可以在生成日志之前禁用INFO。 - Omi Harjani
我希望在日志文件中包含INFO信息。这个问题涉及到对日志文件进行解析、读取和分析。 - guettli

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