为什么禁止覆盖日志记录属性? [英] Why is it forbidden to override log record attributes?

查看:132
本文介绍了为什么禁止覆盖日志记录属性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在阅读Python的logging库的文档(对于2.7版)时,我遇到了以下问题:

Reading the documentation of Python's logging library (for version 2.7) I came across the following:

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

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

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

[...] The second keyword argument is extra which can be used to pass a dictionary which is used to populate the __dict__ of the LogRecord created for the logging event with user-defined attributes. These custom attributes can then be used as you like. For example, they could be incorporated into logged messages. [...] The keys in the dictionary passed in extra should not clash with the keys used by the logging system. [emph. mine]

那么为什么存在这个约束?我认为这无缘无故从库中消除了灵活性(开发人员可以检查哪些键是内置的,哪些不是内置键).

So why does this constraint exist? In my opinion this removes flexibility from the library for no good reason (it is up to the developer to check which keys are built-in and which are not).

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

Imagine you want to write a decorator which logs function entry and exit:

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

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

Suppose you also want to log the name of the enclosing function:

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

糟糕!这是行不通的.输出为:

Oops! This doesn't work. The output is:

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

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

Of course the function name evaluates to wrapper because that is the enclosing function. However this is not what I want. I want the function name of the decorated function to be printed. Therefore it would be very convenient to just modify my logging calls to:

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

但是(如文档所指出的那样),这是行不通的:

However (as the documentation already points out) this doesn't work:

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

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

Nevertheless this would be a very straightforward and light solution to the given problem.

同样,为什么logging阻止我设置内置属性的自定义值?

So again, why is logging preventing me from setting custom values for built-in attributes?

推荐答案

不是作者,我不确定,但是我有一种直觉.

Not being the author, I can't be sure, but I have a hunch.

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

Looking at https://hg.python.org/cpython/file/3.5/Lib/logging/init.py, this seems to be the code that threw the error you quoted:

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 _ _()方法,我们可以看到它设置了一长串属性,其中至少一些属性用于跟踪对象状态(从其他地方借用术语,这些用于私有成员变量):

Looking at the _ _ init _ _() method in that file, we can see that it sets a long list of attributes, at least some of which are used to keep track of object state (to borrow terminology from elsewhere, these serve the purpose of private member variables):

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
[...]

代码在各个地方都假设这些属性包含它们初始化后所包含的内容;就像我们上面看到的那样,它不是在每次使用该值时都进行防御性的检查,而是阻止了尝试更新其中任何一个的尝试.而且,它没有试图区分安全覆盖"和不安全覆盖"属性,而是简单地阻止了任何覆盖.

The code makes assumptions in various places that these attributes contain what they were initialized to contain; rather than defensively checking whether the value is still sensible every time that it's used, it blocks attempts to update any of them, as we've seen above. And, instead of trying to distinguish between "safe-to-overwrite" and "unsafe-to-overwrite" attributes, it simply blocks any overwriting.

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

In the particular case of funcName, I suspect you won't suffer any ill effects (other than having a different funcName displayed) by overwriting it.

可能的前进方式:

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

当然,无论您做什么,都要仔细测试您的修改,以免引起意外.

Of course, whatever you do, test your modification carefully to avoid surprises.

这篇关于为什么禁止覆盖日志记录属性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆