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
我想知道 x 和 y 在 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)
如postmortem看sys.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)
我在我的 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
我想知道 x 和 y 在 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)
如postmortem看sys.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)