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

a_g*_*est 7 python logging

阅读 Pythonlogging库的文档(对于 2.7 版)我遇到了以下内容:

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

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

那么为什么会存在这个约束呢?在我看来,这无缘无故地从库中删除了灵活性(由开发人员检查哪些键是内置的,哪些不是)。

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

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

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

format_string = '%(funcName)s: %(message)s'
Run Code Online (Sandbox Code Playgroud)

哎呀!这不起作用。输出是:

>>> foo()
wrapper: Entry
wrapper: Exit
Run Code Online (Sandbox Code Playgroud)

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

logger.debug('<msg>', extra={'funcName': func.__name__})
Run Code Online (Sandbox Code Playgroud)

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

KeyError: "Attempt to overwrite 'funcName' in LogRecord"
Run Code Online (Sandbox Code Playgroud)

尽管如此,对于给定的问题,这将是一个非常简单和轻松的解决方案。

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

小智 6

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

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

查看__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
[...]
Run Code Online (Sandbox Code Playgroud)

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

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

可能的前进方向:

  • 生活在限制中
  • 重写 Logger.makeRecord() 以允许更新 funcName
  • 重写 Logger 以添加 setFuncName() 方法

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