使用新的格式字符串记录变量数据


84

我将记录工具用于python 2.7.3。此Python版本的文档说

日志记录包会早于较新的格式设置选项,例如str.format()和string.Template。这些更新的格式选项受支持...

我喜欢大括号的“新”格式。所以我正在尝试做类似的事情:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

并得到错误:

TypeError:并非在字符串格式化期间转换了所有参数

我在这里想念什么?

PS我不想使用

log.debug("format this message {0}".format(1))

因为在这种情况下,无论记录器级别如何,消息总是被格式化。


1
您可以这样做: log.debug("format this message%d" % 1)
ronak 2012年

1
您需要配置Formatter使用“{”作为风格
马塔

2
@ronak感谢您的建议,但没有。请参阅“ ps”部分。BTW log.debug(“格式化此消息%d”,1)-正常。
MajesticRa 2012年

@mata如何配置?有这样做的直接文件吗?
MajesticRa 2012年

@mata我找到了。请它的答案,所以我可以将其设置为“正确答案谢谢你一次。
MajesticRa

Answers:


38

编辑:与这个答案不同,看看StyleAdapter@Dunes答案中的方法;它允许在调用记录器的方法(debug(),info(),error()等)时使用其他格式样式而无需样板。


来自文档-使用其他格式样式

记录调用(logger.debug(),logger.info()等)仅采用实际记录消息本身的位置参数,而关键字参数仅用于确定如何处理实际记录调用的选项(例如exc_info关键字参数)表示应该记录回溯信息,或extra关键字参数指示要添加到日志的其他上下文信息。因此,您不能使用str.format()或string.Template语法直接进行日志记录调用,因为日志记录包在内部使用%-formatting来合并格式字符串和变量参数。在保留向后兼容性的同时,不会进行任何更改,因为现有代码中存在的所有日志记录调用都将使用%格式的字符串。

和:

但是,有一种方法可以使用{}-和$-格式来构造您的单个日志消息。回想一下,对于一条消息,您可以使用任意对象作为消息格式字符串,并且日志记录程序包将对该对象调用str()以获取实际的格式字符串。

复制粘贴到wherever模块:

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

然后:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

注意:实际格式化会延迟到必要时才进行,例如,如果未记录DEBUG消息,则根本不会执行格式化。


4
从Python 3.6开始,您可以像这样使用f字符串:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

11
@ P1h3r1e3d13与答案中的日志记录代码不同,f''字符串立即执行格式化。
jfs

1
对。它们之所以在这里工作是因为它们在调用log方法之前会格式化并返回常规字符串。这可能与某人无关,所以我认为值得一提。
Jacktose

3
@Jacktose我认为用户不应该使用F字符串进行日志记录,它会破坏日志聚合服务(例如哨兵)。stdlib日志记录推迟了字符串模板是有充分的理由的。
wim

31

这是另一个没有在Dunes答案中提到的关键字问题的选项。它只能处理位置({0})参数,而不能处理关键字({foo})参数。它还不需要两次格式化调用(使用下划线)。它确实具有子类化的缺点str

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

您可以这样使用它:

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

当然,您可以删除标有的复选框,# optional以强制所有消息通过适配器使用新格式。


多年后阅读此答案的人请注意:从Python 3.2开始,您可以将style参数Formatter对象一起使用:

日志记录(从3.2版本开始)为这两种其他格式设置样式提供了改进的支持。Formatter类得到了增强,可以使用名为的其他可选关键字参数style。默认为'%',但其他可能的值为'{''$',它们对应于其他两种格式样式。向后兼容性默认情况下保持不变(如您所愿),但是通过显式指定样式参数,您可以指定使用str.format()或的 格式字符串string.Template

该文档提供了示例 logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

请注意,在这种情况下,您仍然无法logger使用新格式调用。即,以下仍然无效:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either

5
您关于Python 3的说法不正确。样式参数仅适用于Formatter格式字符串,不适用于单个日志消息。您链接到的页面上明确指出:“在保持向后兼容性的同时,不会对此进行更改”。
mhsmith

1
感谢您让我诚实。第一部分现在已经不那么有用了,但是我已经用改写了它Formatter,这现在是正确的(我认为)。StyleAdapter 仍然有效,
费利佩

@falstro-感谢您指出这一点。更新的版本现在应该可以工作了。由于BraceString是字符串子类,因此可以安全地从__str__
Felipe

1
仅提及style =“ {”,+1的答案
TomS。

24

当我发现日志记录仅使用printf样式格式设置时,这就是我的解决方案。它允许记录调用保持不变-无需特殊语法如log.info(__("val is {}", "x"))。编码所需的更改是将记录器包装在StyleAdapter

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (), 
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key] 
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

用法是:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substitution", type="brace")

值得一提的是这个实现有问题,如果使用大括号替换的关键词包括levelmsgargsexc_infoextrastack_info。这些是的log方法使用的参数名称Logger。如果您需要这些名称之一,则可以修改process为排除这些名称,或者只是log_kwargs_log通话中删除。进一步请注意,此实现还默默地忽略了用于Logger的拼写错误的关键字(例如ectra)。


4
python doc,docs.python.org
howto/…

23

更简单的解决方案是使用出色的logbook模块

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)

或更完整的:

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1

这看起来不错,但是有没有毫秒而不是秒的方法?
杰夫

@Jeff可以肯定,日志可以让您定义自定义处理程序并使用自定义字符串格式。
Thomas Orozco

5
@Jeff几年后-默认时间精度为毫秒。
Jan Vlcinsky '01 Jan 11'20

12

如其他答案所述,Python 3.2中引入的大括号样式格式仅用于格式字符串,而不用于实际的日志消息。

要在实际的日志消息上启用大括号样式的格式,我们可以猴子修补一些记录器代码。

以下修补logging模块以创建一个get_logger函数,该函数将返回一个记录器,该记录器对其处理的每个日志记录都使用新格式。

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

用法:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

笔记:

  • 与常规日志记录方法完全兼容(只需替换logging.getLoggerget_logger
  • 仅会影响该get_logger功能创建的特定记录器(不会破坏第三方包装)。
  • 如果从常规logging.getLogger()调用中再次访问记录器,则新格式仍将适用。
  • kwargs不支持(使其无法与冲突内置exc_infostack_infostacklevelextra)。
  • 性能影响应该最小(为每个日志消息重写单个函数指针)。
  • 消息的格式被延迟,直到输出为止(如果日志消息被过滤,则根本不格式化)。
  • Arglogging.LogRecord像往常一样存储在对象上(在某些情况下,使用自定义日志处理程序很有用)。
  • 通过查看logging模块源代码,似乎它应该可以一直使用到str.format引入Python 2.6为止(但我仅在3.5及更高版本中对其进行了测试)

2
唯一考虑仅在打印调试器消息时才应计算调试字符串的答案。谢谢!
Fafaman '16

2

logging.setLogRecordFactory在Python 3.2+中尝试:

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)

它确实可以工作,但是问题是%由于记录工厂对于日志记录模块是全局的,因此您破坏了使用格式化的第三方模块。
jtaylor'1

1

我创建了一个自定义格式化程序,称为ColorFormatter,它可以解决以下问题:

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

这样可以使其与各种库兼容。缺点是,由于可能两次尝试格式化字符串,因此它可能无法执行。


0

这实际上很简单:

debug_logger: logging.Logger = logging.getLogger("app.debug")

def mydebuglog(msg: str, *args, **kwargs):
    if debug_logger.isEnabledFor(logging.DEBUG):
        debug_logger.debug(msg.format(*args, **kwargs))

然后:

mydebuglog("hello {} {val}", "Python", val="World")

0

与pR0Ps相似的解决方案,getMessage在这种情况下LogRecord通过包装makeRecord(而不是handle在其答案中)进行包装Logger应启用新格式:

def getLogger(name):
    log = logging.getLogger(name)
    def Logger_makeRecordWrapper(name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
        self = log
        record = logging.Logger.makeRecord(self, name, level, fn, lno, msg, args, exc_info, func, sinfo)
        def LogRecord_getMessageNewStyleFormatting():
            self = record
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
        record.getMessage = LogRecord_getMessageNewStyleFormatting
        return record
    log.makeRecord = Logger_makeRecordWrapper
    return log

我用Python 3.5.3进行了测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.