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

Maj*_*cRa 76 python string-formatting python-2.7

我使用python 2.7.3的日志工具.这个Python版本的文档说:

日志包提前更新格式化选项,如str.format()和string.Template.支持这些较新的格式化选项...

我喜欢花括号的"新"格式.所以我想尝试做类似的事情:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)
Run Code Online (Sandbox Code Playgroud)

并得到错误:

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

我想念的是什么?

PS我不想用

log.debug("format this message {0}".format(1))
Run Code Online (Sandbox Code Playgroud)

因为在这种情况下,无论记录器级别如何,都始终格式化消息.

jfs*_*jfs 38

编辑:看看StyleAdapter@Dunes答案中的方法,不像这个答案; 它允许在调用logger的方法(debug(),info(),error()等)时使用没有样板的替代格式样式.


从文档 - 使用替代格式样式:

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

和:

但是,有一种方法可以使用{} - 和$ - 格式来构建单独的日志消息.回想一下,对于消息,您可以使用任意对象作为消息格式字符串,并且日志包将在该对象上调用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)
Run Code Online (Sandbox Code Playgroud)

然后:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))
Run Code Online (Sandbox Code Playgroud)

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

  • @ P1h3r1e3d13与答案中的日志代码不同,f'' - 字符串立即执行格式化. (6认同)
  • @Jacktose我认为用户不应该使用f-strings进行日志记录,它会破坏日志聚合服务(例如哨兵)。stdlib 日志记录推迟字符串模板是有充分理由的。 (6认同)
  • 从Python 3.6开始,你可以像这样使用f-strings:`num = 2; name ='占位符'; log.debug(f'Message with {num} {name}')` (4认同)

Fel*_*ipe 26

这是另一个没有Dunes回答中提到的关键字问题的选项.它只能处理positional({0})参数而不能处理keyword({foo})参数.它也不需要两次调用格式化(使用下划线).它确实具有子类化的ick因子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
Run Code Online (Sandbox Code Playgroud)

你这样使用它:

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

当然,您可以删除标记的检查# 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
Run Code Online (Sandbox Code Playgroud)

  • 您关于Python 3的陈述不正确.style参数仅适用于Formatter格式字符串,而不适用于单个日志消息.您链接到的页面明确说:"在保留向后兼容性的同时不会改变它". (4认同)

Dun*_*nes 22

当我发现日志记录仅使用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}
Run Code Online (Sandbox Code Playgroud)

用法是:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substiution", type="brace")
Run Code Online (Sandbox Code Playgroud)

值得一提的是这个实现有问题,如果使用大括号替换的关键词包括level,msg,args,exc_info,extrastack_info.这些是log方法使用的参数名称Logger.如果您需要将这些名称之一然后修改process排除这些名字或只是删除log_kwargs_log通话.另外值得注意的是,此实现还默默地忽略了针对Logger的拼写错误的关键字(例如ectra).

  • 这种方式是由python doc推荐的,https://docs.python.org/3/howto/logging-cookbook.html#use-of-alternative-formatting-styles (4认同)

Tho*_*zco 20

更简单的解决方案是使用优秀的logbook模块

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)
Run Code Online (Sandbox Code Playgroud)

或者更完整:

>>> 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
Run Code Online (Sandbox Code Playgroud)

  • @Jeff几年后 - 默认时间精度为毫秒. (5认同)

pR0*_*0Ps 10

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

从Python 3.5开始,没有很好的方法来使用大括号格式来记录消息.

但是,与Python中的大多数事情一样,有一种不太好的方式.

以下猴子修补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
Run Code Online (Sandbox Code Playgroud)

用法:

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

笔记:

  • 只会影响该logging.getLogger函数创建的特定记录器.
  • 如果从正常get_logger调用再次访问记录器,则新样式格式仍将适用
  • kwargs不受支持
  • 性能命中应该是最小的(为每个日志消息重写单个函数指针)
  • 消息的格式被延迟,直到输出为止
  • 不会阻止args存储在get_logger对象上(在某些情况下很有用)
  • 通过查看logging.getLogger()模块源代码,它似乎应该exc_info在引入时一直回到Python 2.6 (但仅在Python 3.5上进行了测试).

  • 唯一的答案是,只有在打印调试器消息时才应该计算调试字符串.谢谢! (2认同)