python 警告的日志堆栈跟踪

Log stack trace for python warning

我在我的 python 程序中使用的一个包发出警告,我想了解其确切原因。我已经设置 logging.captureWarning(True) 并在我的日志记录中捕获警告,但仍然不知道它来自哪里。我还如何记录堆栈跟踪,以便我可以看到警告来自我的代码中的哪个位置?我使用 traceback 吗?

如果您不知道 data/instruction 是什么导致了警告抛出,您可以使用像标准 Python Debugger.

这样的工具

文档非常好而且很详细,但是一些可能有用的快速示例应该是:

  • 没有修改源代码:将调试器作为脚本调用:

    $ python -m pdb myscript.py

  • 修改源代码:您可以使用对 pdb.set_trace() 的调用,就像断点一样工作;例如,假设我有以下 example 代码:

    x = 2
    x = x * 10 * 100
    y = x + 3 + y
    return y
    

    我想知道 xy 在 return 之前有什么值,或者堆栈包含,我会在这些语句之间添加以下行:

    pdb.set_trace()

    我将被提示到 (Pdb) 提示符,这将允许您逐行查看代码。 (Pdb) 提示符的有用命令是:

    • n:执行下一条语句。
    • q: 退出整个程序。
    • c: 退出 (Pdb) 提示并停止调试。
    • p varname: 打印 varname
    • 的值

由于您没有提供更多信息,我不知道这是否足够,但我认为至少,这可能是一个好的开始。

奖金编辑

this answer的基础上,我发现了一个友好的GUI调试工具,您可以通过以下方式安装它:

$ pip install pudb

并且 运行 调试器与您的脚本一起使用:

$ python -m pudb.run myscript.py

编辑:添加事后调试

如果我们甚至不知道代码是否会崩溃,我们可以进入 postmortem 调试是否已经发生崩溃。来自 Pbd 文档:

The typical usage to inspect a crashed program is:

>>> import pdb
>>> import mymodule
>>> mymodule.test()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "./mymodule.py", line 4, in test
    test2()
  File "./mymodule.py", line 3, in test2
    print spam
NameError: spam
>>> pdb.pm()
> ./mymodule.py(3)test2()
-> print spam
(Pdb)

postmortemsys.last_traceback,只有在有回溯(等等,警告或崩溃)时才进入:

if sys.last_traceback:
     pdb.pm()

有点老套,但你可以将 warnings.warn 方法修改为:

import traceback
import warnings

def g():
    warnings.warn("foo", Warning)

def f():
    g()
    warnings.warn("bar", Warning)

_old_warn = warnings.warn
def warn(*args, **kwargs):
    tb = traceback.extract_stack()
    _old_warn(*args, **kwargs)
    print("".join(traceback.format_list(tb)[:-1]))
warnings.warn = warn

f()
print("DONE")

这是输出:

/tmp/test.py:14: Warning: foo
  _old_warn(*args, **kwargs)
  File "/tmp/test.py", line 17, in <module>
    f()
  File "/tmp/test.py", line 8, in f
    g()
  File "/tmp/test.py", line 5, in g
    warnings.warn("foo", Warning)

/tmp/test.py:14: Warning: bar
  _old_warn(*args, **kwargs)
  File "/tmp/test.py", line 17, in <module>
    f()
  File "/tmp/test.py", line 9, in f
    warnings.warn("bar", Warning)

DONE

看到调用原始 warnings.warn 函数不会报告您想要的行,但堆栈跟踪确实正确(您可以自己打印警告消息)。

如果是我,我会选择@Lluís Vilanova 的快速和肮脏的 hack,只是为了找到一些东西。但如果那不是一个选择...

如果您真的想要 "logging" 解决方案,您可以尝试 this(完全可用的源代码)。

基本步骤是:

  • 创建自定义 logging.Formatter 子class,其中包括格式化日志记录的当前堆栈
  • 在警告 class 上使用该格式化程序

代码的核心是自定义格式化程序:

class Formatter(logging.Formatter):
    def format(self, record):
        record.stack_info = ''.join(traceback.format_stack())
        return super().format(record)

根据 docs:

New in version 3.2: The stack_info parameter was added.

我最终采用了以下方法:

import warnings
import traceback

_formatwarning = warnings.formatwarning

def formatwarning_tb(*args, **kwargs):
    s = _formatwarning(*args, **kwargs)
    tb = traceback.format_stack()
    s += ''.join(tb[:-1])
    return s

warnings.formatwarning = formatwarning_tb
logging.captureWarnings(True)

您可以将警告转为异常,这意味着您将自动获得堆栈跟踪:

warnings.filterwarnings("error")

https://docs.python.org/3.4/library/warnings.html#the-warnings-filter

对于 python 3.2 及更高版本,使用可选的 stack_info 关键字参数是获取堆栈跟踪信息以及日志消息的最简单方法。 在下面的示例中,"Server.py" 使用 "lib2.py",后者又使用 "lib.py"。 在启用 stack_info 参数时,完整的回溯与每个 logging.log() 调用一起被记录下来。这与 logging.info() 和其他便捷方法一样有效。

用法:-

logging.log(DEBUG, "RWL [{}] : acquire_read()".format(self._ownerName), stack_info=True)

输出:-

2018-10-06 10:59:55,726|DEBUG|MainThread|lib.py|acquire_read|RWL [Cache] : acquire_read()
Stack (most recent call last):
  File "./Server.py", line 41, in <module>
    logging.info("Found {} requests for simulation".format(simdata.count()))
  File "<Path>\lib2.py", line 199, in count
    with basics.ReadRWLock(self.cacheLock):
  File "<Path>\lib.py", line 89, in __enter__
    self.rwLock.acquire_read()
  File "<Path>\lib.py", line 34, in acquire_read
    logging.log(DEBUG, "RWL [{}] : acquire_read()".format(self._ownerName), stack_info=True)