为什么禁止覆盖日志记录属性?

时间:2016-11-29 09:30:16

标签: python logging

阅读Python的logging库(2.7版)的文档,我遇到了以下内容:

  

Logger.debug(msg,* args,** kwargs)

     

[...]第二个关键字参数是extra,可用于传递字典,该字典用于填充为具有用户定义属性的日志记录事件创建的LogRecord的__dict__。然后可以根据需要使用这些自定义属性。例如,它们可以合并到记录的消息中。 [...] 字典中传入的字符串中的密钥不应与日志系统使用的密钥冲突。 [emph。矿]

那为什么存在这种约束?在我看来,这没有任何理由从库中删除灵活性(由开发人员来检查哪些键是内置的,哪些不是内置的。)

想象一下,你想写一个记录函数入口和退出的装饰器:

def log_entry_exit(func):
    def wrapper(*args, **kwargs):
        logger.debug('Entry')
        result = func(*args, **kwargs)
        logger.debug('Exit')
        return result
    return wrapper

@log_entry_exit
def foo():
    pass

假设您还想记录封闭函数的名称:

format_string = '%(funcName)s: %(message)s'

糟糕!这不起作用。输出是:

>>> foo()
wrapper: Entry
wrapper: Exit

当然,函数名称的计算结果为wrapper,因为这是封闭函数。然而,这不是我想要的。我想要打印装饰函数的函数名称。因此,将我的日志记录调用修改为:

非常方便
logger.debug('<msg>', extra={'funcName': func.__name__})

但是(正如文档已经指出的那样)这不起作用:

KeyError: "Attempt to overwrite 'funcName' in LogRecord"

然而,对于给定的问题,这将是一个非常简单明了的解决方案。

再次,为什么logging阻止我为内置属性设置自定义值?

2 个答案:

答案 0 :(得分:4)

不是作者,我不能确定,但​​我有预感。

看着 https://hg.python.org/cpython/file/3.5/Lib/logging/init.py,这似乎是抛出引用错误的代码:

rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func, sinfo)
if extra is not None:
    for key in extra:
        if (key in ["message", "asctime"]) or (key in rv.__dict__):
            raise KeyError("Attempt to overwrite %r in LogRecord" % key)
        rv.__dict__[key] = extra[key]

查看该文件中的_ _ init _ _()方法,我们可以看到它设置了一长串属性列表,其中至少有一些属性用于跟踪对象状态(从其他地方借用术语,这些用于私有成员变量的目的):

self.args = args
self.levelname = getLevelName(level)
self.levelno = level
self.pathname = pathname
try:
    self.filename = os.path.basename(pathname)
    self.module = os.path.splitext(self.filename)[0]
except (TypeError, ValueError, AttributeError):
    self.filename = pathname
    self.module = "Unknown module"
self.exc_info = exc_info
self.exc_text = None      # used to cache the traceback text
self.stack_info = sinfo
self.lineno = lineno
self.funcName = func
[...]

代码在各个地方做出假设,这些属性包含它们被初始化为包含的内容;正如我们在上面看到的那样,它不是在每次使用它时都在防御性地检查该值是否仍然合理,而是阻止尝试更新它们中的任何一个。并且,它不是试图区分“安全覆盖”和“不安全覆盖”属性,而是简单地阻止任何覆盖。

在funcName的特定情况下,我怀疑你不会因为覆盖它而遭受任何不良影响(除了显示不同的funcName)。

可能的前进方式:

  • 忍受限制
  • 覆盖Logger.makeRecord()以允许更新funcName
  • 覆盖Logger以添加setFuncName()方法

当然,无论你做什么,都要仔细测试你的修改以避免意外。

答案 1 :(得分:1)

我知道这已经有几年历史了,但是没有选择的答案。如果遇到任何其他问题,我有一个解决方法,该方法应在日志记录模块进行更改时继续工作。

不幸的是,作者并没有以易于检查的方式公开可能会冲突的密钥。但是,他/她确实在文档中提示了一种方法。这一行:https://hg.python.org/cpython/file/3.5/Lib/logging/init.py#l368返回LogRecord对象的外壳:

rv = _logRecordFactory(None, None, "", 0, "", (), None, None)

...在此对象中,您可以看到所有属性,并且可以制作一个{<1>},其中包含“冲突键”

我创建了日志记录帮助程序模块:

Set

......只是在冲突的键前加上import logging clashing_keywords = {key for key in dir(logging.LogRecord(None, None, "", 0, "", (), None, None)) if "__" not in key} additional_clashing_keywords = { "message", "asctime" } clashing_keywords = clashing_keywords.union(additional_clashing_keywords) def make_safe_kwargs(kwargs): ''' Makes sure you don't have kwargs that might conflict with the logging module ''' assert isinstance(kwargs, dict) for k in kwargs: if k in clashing_keywords: kwargs['_'+k] = kwargs.pop(k) return kwargs 。可以这样使用:

_

对我来说一直很好。希望这会有所帮助!