91

我使用 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))

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

4

10 回答 10

40

编辑:看看StyleAdapter@Dunes 的答案与这个答案不同的方法;它允许在调用记录器的方法(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)

然后:

from wherever import BraceMessage as __

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

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

于 2012-10-30T00:39:53.197 回答
31

这是另一个没有沙丘回答中提到的关键字问题的选项。它只能处理位置 ( {0}) 参数,而不能处理关键字 ( {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

你像这样使用它:

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

当然,您可以取消标记# optional以强制通过适配器的所有消息使用新样式格式的检查。


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

日志记录(从 3.2 开始)为这两种附加格式样式提供了改进的支持。Formatter 类得到了增强,可以采用一个额外的可选关键字参数,名为style. 这默认为'%',但其他可能的值为'{''$',它们对应于其他两种格式样式。默认情况下保持向后兼容性(如您所料),但通过显式指定样式参数,您可以指定与str.format()or 一起使用的格式字符串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
于 2014-09-23T19:51:14.967 回答
25

当我发现日志记录仅使用 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")

值得注意的是,如果用于大括号替换的关键字包括levelmsgargs、或exc_info,则此实现存在问题。这些是 的方法使用的参数名称。如果您需要这些名称之一,请修改以排除这些名称或从呼叫中删除。进一步说明,此实现还默默地忽略了用于 Logger 的拼写错误的关键字(例如)。extrastack_infologLoggerprocesslog_kwargs_logectra

于 2014-07-10T18:01:15.660 回答
24

更简单的解决方案是使用优秀的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
于 2012-10-30T00:11:42.743 回答
12

更新:现在 PyPI 上有一个名为bracelogger的包,它实现了下面详述的解决方案。


要为日志消息启用大括号样式格式,我们可以对一些记录器代码进行猴子补丁。

以下修补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函数创建的特定记录器(不会破坏第 3 方包)。
  • 消息的格式会延迟到输出(如果日志消息被过滤,则根本不会)。
  • Args 像往常一样存储在logging.LogRecord对象上(在某些情况下对自定义日志处理程序很有用)。
  • 适用于从 2.7 到 3.10 的所有 Python 版本。
于 2016-03-29T20:59:32.707 回答
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)
于 2017-03-16T00:59:24.080 回答
1

我创建了一个名为ColorFormatter的自定义 Formatter来处理这样的问题:

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…

这使它与各种库兼容。缺点是由于可能尝试两次格式化字符串,它可能性能不佳。

于 2018-09-11T02:58:11.977 回答
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 对此进行了测试。

于 2020-07-05T14:45:40.500 回答
0

结合string.Formatter添加pprint.pformat类型转换和 from logging: setLogRecordFactory, setLoggerClass. 有一个巧妙的技巧——我argsLogger._log方法的参数创建额外的嵌套元组,然后在LogRecordinit 中解压它以省略在Logger.makeRecord. 使用log.f wraps每个属性(故意记录方法),use_format因此您不必显式编写它。该解决方案向后兼容。

from collections import namedtuple
from collections.abc import Mapping                                                     
from functools import partial                                                          
from pprint import pformat                                                              
from string import Formatter                                                        
import logging                
                                                       
                                                                                        
Logger = logging.getLoggerClass()                                                       
LogRecord = logging.getLogRecordFactory()                                               
                                                                                      
                                                                                        
class CustomFormatter(Formatter):                                                       
    def format_field(self, value, format_spec):                                         
        if format_spec.endswith('p'):                                                   
            value = pformat(value)                                                      
            format_spec = format_spec[:-1]                                              
        return super().format_field(value, format_spec)                                 
                                                                                        
                                                                                        
custom_formatter = CustomFormatter()                                                    
                                                                                        
                                                                                        
class LogWithFormat:                                                                    
    def __init__(self, obj):                                                            
        self.obj = obj                                                                  
                                                                                        
    def __getattr__(self, name):                                                        
        return partial(getattr(self.obj, name), use_format=True)    

                
ArgsSmuggler = namedtuple('ArgsSmuggler', ('args', 'smuggled'))                                                                                     
                                                                                        

class CustomLogger(Logger):                                                             
    def __init__(self, *ar, **kw):                                                      
        super().__init__(*ar, **kw)                                                     
        self.f = LogWithFormat(self)                                                    
                                                                                        
    def _log(self, level, msg, args, *ar, use_format=False, **kw):                                         
        super()._log(level, msg, ArgsSmuggler(args, use_format), *ar, **kw)                                     
                                                                                        
                                                                                        
class CustomLogRecord(LogRecord):                                                       
    def __init__(self, *ar, **kw):                                                   
        args = ar[5]
        # RootLogger use CustomLogRecord but not CustomLogger
        # then just unpack only ArgsSmuggler instance
        args, use_format = args if isinstance(args, ArgsSmuggler) else (args, False)                                       
        super().__init__(*ar[:5], args, *ar[6:], **kw)                                  
        self.use_format = use_format                                                    
                                                                                        
    def getMessage(self):                                                               
        return self.getMessageWithFormat() if self.use_format else super().getMessage() 
                                                                                        
    def getMessageWithFormat(self):                                                     
        msg = str(self.msg)                                                             
        args = self.args                                                                
        if args:                                                                        
            fmt = custom_formatter.format                                               
            msg = fmt(msg, **args) if isinstance(args, Mapping) else fmt(msg, *args)    
        return msg                                                                      
                                                                                            
                                                                                                
logging.setLogRecordFactory(CustomLogRecord)      
logging.setLoggerClass(CustomLogger)              
                                                  
log = logging.getLogger(__name__)   
log.info('%s %s', dict(a=1, b=2), 5)          
log.f.info('{:p} {:d}', dict(a=1, b=2), 5)
于 2021-03-27T21:51:29.917 回答
-1

这里有一些非常简单的方法:

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")
于 2017-05-25T22:21:26.110 回答