Python警告日志堆栈跟踪

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

从发送的消息中,你能否在代码库中查找它? - vermillon
是的,我已经看过那里了,但不幸的是仍然无法确定我的代码库中哪些数据/代码导致了这个问题。 - user1507844
6个回答

8
我最终选择了以下内容:
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)

5

这个方法有点hackish,但你可以通过猴子补丁的方式修改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函数不会报告您想要的行,但堆栈跟踪确实是正确的(您可以自己打印警告消息)。

2
如果您不知道是哪个数据/指令导致了警告抛出,您可以使用像标准 Python Debugger 这样的工具。
文档非常好,并且详细说明了,但可能会有一些快速的示例可以帮助您:
  • Without modifying source code: invoking the debbugger as script:

    $ python -m pdb myscript.py

  • Modifying source code: you can make use of calls to pdb.set_trace(), that work like breakpoints; For example, consider I have the following example code:

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

    And I would like to know what value does x and y have before the return, or what does the stack contains, I would add the following line between those statements:

    pdb.set_trace()

    And I will be promted to the (Pdb) prompt, that will allow you to go through the code line by line. Useful commands for the (Pdb) prompt are:

    • n: executes the next statement.
    • q: quits the whole program.
    • c: quits the (Pdb) prompt and stops debugging.
    • p varname: prints the value of varname
由于您没有提供更多信息,我不知道这是否足够,但我认为至少这可能是一个好的开始。 奖励编辑 基于this answer,我发现有一个很好的友好GUI调试工具,你可以简单地安装: $ pip install pudb
并使用以下命令与你的脚本一起运行调试器: $ python -m pudb.run myscript.py 编辑:添加事后调试 如果我们甚至不知道代码是否会崩溃,如果发生了崩溃,我们可以进入事后调试。根据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)
作为一种“事后诊断”,sys.last_traceback 只有在出现 traceback(警告或崩溃等)时才会进入。
if sys.last_traceback:
     pdb.pm()

很遗憾,调试无法帮助识别问题。它只是偶尔发生,并且在调试时我很难抓住它。因此,我需要记录来准确地确定导致这个问题的情况。 - user1507844
1
当然可以。您可以使用Python调试器post-mortem。我已经更新了我的答案,并提供了一个示例。 - otorrillas

2

0

如果是我,我会选择@Lluís Vilanova的快速且简单的解决方案,只是为了找到一些东西。但如果这不是一个选项...

如果你真的想要一个“日志记录”解决方案,你可以尝试像this这样的东西(完全工作的源代码)。

基本步骤如下:

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

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

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

根据文档
New in version 3.2: The stack_info parameter was added.

0
对于Python 3.2及以上版本,使用可选的stack_info关键字参数是获取堆栈跟踪信息以及日志消息的最简单方法。 在下面的示例中,“Server.py”正在使用“lib2.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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接