如何将调用者包含在 Python 构造函数的调试日志记录中?

How can I include the caller in debug logging from Python constructors?

我有一个 class C ,它在大型程序中到处都被实例化了。 C.__init__() 包含调试日志记录,提供传递给新实例的参数。

这些日志条目包含实例化 C 的函数的名称通常很有用。这可以使用 traceback 来完成。但是调试日志语句的参数不应该有副作用,因为在实践中它们几乎总是空操作。那么如何实现呢?

它可以用一个单例对象来完成,它的表示是它的调用者的名字(或者它的调用者的调用者,或者...):

   logger.debug("new C instance, with a=%d, b=%d, called from %s",
        a, b, show_caller)

然后就是搜索调用堆栈多远的问题:我们将不得不绕过日志库中的几层调用,以及调用我们的构造函数。

我已经解决了这个问题,假设 show_caller 的 class 位于一个名称与调用者的包相似的包中。例如,它可能是 wombat.spong.C 呼叫 wombat.util.ShowCaller。变量 DEPTH 给出了两个人应该共享多少个名字。

这不是一个非常令人满意的解决方案,但它确实有效。

所以我们有:

import os

class _ShowCaller:
    """
    Singleton class whose representation names the caller of the caller.

    The object of this class does nothing in particular most of the time.
    But when you use it in the parameters of a logging statement, thus:

    ```
        logger.debug("ready; was called from %s", show_caller)
    ```

    it returns a string giving the name, filename, and line number
    of the caller of the method containing the logging statement.

    If this was done using a function call, the call would take place
    every time the logging statement was executed, even in production
    where it would be a no-op.

    Attributes:
        DEPTH (`int`): how many levels of directory we should consider
            to be part of our package, rather than being library modules.
    """

    DEPTH = 1

    def __init__(self):
        self.prefix = os.path.sep.join(
                __file__.split(os.path.sep)[:-self.DEPTH]
        )

    def __repr__(self):
        import traceback

        stack = reversed(list(enumerate(traceback.extract_stack()[:-1])))

        for i, caller in stack:
            if caller.filename.startswith(self.prefix):
                break

        try:
            i, caller = next(stack)
            return '%s (%s:%s)' % (
                    caller.name,
                    caller.filename[len(self.prefix)+1:],
                    caller.lineno,
                    )
        except StopIteration:
            return '???'

show_caller = _ShowCaller()