17.7. cgitb — 更加详细的 Traceback 报告
本节目标:了解
cgitb
,cgitb
比traceback
给出的信息更加详细。
cgitb
是标准库中非常有价值的调试工具。它一开始被设计为在 web 应用程序中显示错误和调试信息,后来也更新到可以到普通文本中,不过最终也没重命名它。这也导致了这个包基本没怎么被用过(在需要它的时候)。
标准追踪转储
Python 的默认异常处理器会把错误发生位置的保准错误输出流打印出来。基本上也包含了足够的信息让我们理解为什么会引起错误以及随后修复它。
cgitb_basic_traceback.py
def func2(a, divisor):
return a / divisor
def func1(a, b):
c = b - 5
return func2(a, c)
func1(1, 5)
运行之后会有一个比较不容易引发的错误出现(func2()
中)。
$ python3 cgitb_basic_traceback.py
Traceback (most recent call last):
File "cgitb_basic_traceback.py", line 18, in <module>
func1(1, 5)
File "cgitb_basic_traceback.py", line 16, in func1
return func2(a, c)
File "cgitb_basic_traceback.py", line 11, in func2
return a / divisor
ZeroDivisionError: division by zero
开启更详细的追踪信息显示
基本的追踪信息已经包含了足够的错误信息,cgitb
则显示的更多。cgitb
替换了 sys.excepthook
为一个扩展了追踪的函数。
cgitb_local_vars.py
import cgitb
cgitb.enable(format='text')
运行后的错误报告会非常多。栈的每一帧都被列了出来:
- 原文件的全部路径。
- 栈中每个函数的参数值。
- 错误路径中出错行的多行上下文。
- 引起此错误的变量的值。
能访问到涉及此错误栈的变量可以帮助我们定位并不是因为此行发生错误的情况。
$ python3 cgitb_local_vars.py
ZeroDivisionError
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018
A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they
occurred.
.../cgitb_local_vars.py in <module>()
18 def func1(a, b):
19 c = b - 5
20 return func2(a, c)
21
22 func1(1, 5)
func1 = <function func1>
.../cgitb_local_vars.py in func1(a=1, b=5)
18 def func1(a, b):
19 c = b - 5
20 return func2(a, c)
21
22 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0
.../cgitb_local_vars.py in func2(a=1, divisor=0)
13
14 def func2(a, divisor):
15 return a / divisor
16
17
a = 1
divisor = 0
ZeroDivisionError: division by zero
__cause__ = None
__class__ = <class 'ZeroDivisionError'>
__context__ = None
__delattr__ = <method-wrapper '__delattr__' of
ZeroDivisionError object>
__dict__ = {}
__dir__ = <built-in method __dir__ of ZeroDivisionError
object>
__doc__ = 'Second argument to a division or modulo operation
was zero.'
__eq__ = <method-wrapper '__eq__' of ZeroDivisionError
object>
__format__ = <built-in method __format__ of
ZeroDivisionError object>
__ge__ = <method-wrapper '__ge__' of ZeroDivisionError
object>
__getattribute__ = <method-wrapper '__getattribute__' of
ZeroDivisionError object>
__gt__ = <method-wrapper '__gt__' of ZeroDivisionError
object>
__hash__ = <method-wrapper '__hash__' of ZeroDivisionError
object>
__init__ = <method-wrapper '__init__' of ZeroDivisionError
object>
__init_subclass__ = <built-in method __init_subclass__ of
type object>
__le__ = <method-wrapper '__le__' of ZeroDivisionError
object>
__lt__ = <method-wrapper '__lt__' of ZeroDivisionError
object>
__ne__ = <method-wrapper '__ne__' of ZeroDivisionError
object>
__new__ = <built-in method __new__ of type object>
__reduce__ = <built-in method __reduce__ of
ZeroDivisionError object>
__reduce_ex__ = <built-in method __reduce_ex__ of
ZeroDivisionError object>
__repr__ = <method-wrapper '__repr__' of ZeroDivisionError
object>
__setattr__ = <method-wrapper '__setattr__' of
ZeroDivisionError object>
__setstate__ = <built-in method __setstate__ of
ZeroDivisionError object>
__sizeof__ = <built-in method __sizeof__ of
ZeroDivisionError object>
__str__ = <method-wrapper '__str__' of ZeroDivisionError
object>
__subclasshook__ = <built-in method __subclasshook__ of type
object>
__suppress_context__ = False
__traceback__ = <traceback object>
args = ('division by zero',)
with_traceback = <built-in method with_traceback of
ZeroDivisionError object>
The above is a description of an error in a Python program.
Here is
the original traceback:
Traceback (most recent call last):
File "cgitb_local_vars.py", line 22, in <module>
func1(1, 5)
File "cgitb_local_vars.py", line 20, in func1
return func2(a, c)
File "cgitb_local_vars.py", line 15, in func2
return a / divisor
ZeroDivisionError: division by zero
本例中的错误是 ZeroDivisionError
,它给出的分析是此错误是因为func1()
中计算 c
时发生了错误,而不是因为 func2()
使用的值的有问题。
最终的输出还包含完整的异常对象的信息(在某些情况下我们知道它的属性而不是消息
更有利于我们调试)和标准追踪转储格式。
Traceback 中的本地变量
处在 cgitb
中的代码,如有错误发生它会检测导致此错误发生的栈中的变量,给我们提供准确的涉及其中的变量信息。
cgitb_with_classes.py
import cgitb
cgitb.enable(format='text', context=12)
class BrokenClass:
"""This class has an error.
"""
def __init__(self, a, b):
"""Be careful passing arguments in here.
"""
self.a = a
self.b = b
self.c = self.a * self.b
# Really
# long
# comment
# goes
# here.
self.d = self.a / self.b
return
o = BrokenClass(1, 0)
不过如果某函数或方法包含大量的注释,空格或其他无关代码导致它的上下文很长。默认的5行上下文可能就不够提供足够指示。所以显示在屏幕上之后很可能没有足够的上下文来让我们理解错误的发生。我们可以给 cgitb
指定更多的上下文值来解决这个问题。给 enable()
的 context
指定一个整数即可控制显示出来的上下文代码量。
我们设置为 12
之后就发现 self.a
和 self.b
也牵扯其中。
$ python3 cgitb_with_classes.py
ZeroDivisionError
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018
A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they
occurred.
.../cgitb_with_classes.py in <module>()
21 self.a = a
22 self.b = b
23 self.c = self.a * self.b
24 # Really
25 # long
26 # comment
27 # goes
28 # here.
29 self.d = self.a / self.b
30 return
31
32 o = BrokenClass(1, 0)
o undefined
BrokenClass = <class '__main__.BrokenClass'>
.../cgitb_with_classes.py in
__init__(self=<__main__.BrokenClass object>, a=1, b=0)
21 self.a = a
22 self.b = b
23 self.c = self.a * self.b
24 # Really
25 # long
26 # comment
27 # goes
28 # here.
29 self.d = self.a / self.b
30 return
31
32 o = BrokenClass(1, 0)
self = <__main__.BrokenClass object>
self.d undefined
self.a = 1
self.b = 0
ZeroDivisionError: division by zero
__cause__ = None
__class__ = <class 'ZeroDivisionError'>
__context__ = None
__delattr__ = <method-wrapper '__delattr__' of
ZeroDivisionError object>
__dict__ = {}
__dir__ = <built-in method __dir__ of ZeroDivisionError
object>
__doc__ = 'Second argument to a division or modulo operation
was zero.'
__eq__ = <method-wrapper '__eq__' of ZeroDivisionError
object>
__format__ = <built-in method __format__ of
ZeroDivisionError object>
__ge__ = <method-wrapper '__ge__' of ZeroDivisionError
object>
__getattribute__ = <method-wrapper '__getattribute__' of
ZeroDivisionError object>
__gt__ = <method-wrapper '__gt__' of ZeroDivisionError
object>
__hash__ = <method-wrapper '__hash__' of ZeroDivisionError
object>
__init__ = <method-wrapper '__init__' of ZeroDivisionError
object>
__init_subclass__ = <built-in method __init_subclass__ of
type object>
__le__ = <method-wrapper '__le__' of ZeroDivisionError
object>
__lt__ = <method-wrapper '__lt__' of ZeroDivisionError
object>
__ne__ = <method-wrapper '__ne__' of ZeroDivisionError
object>
__new__ = <built-in method __new__ of type object>
__reduce__ = <built-in method __reduce__ of
ZeroDivisionError object>
__reduce_ex__ = <built-in method __reduce_ex__ of
ZeroDivisionError object>
__repr__ = <method-wrapper '__repr__' of ZeroDivisionError
object>
__setattr__ = <method-wrapper '__setattr__' of
ZeroDivisionError object>
__setstate__ = <built-in method __setstate__ of
ZeroDivisionError object>
__sizeof__ = <built-in method __sizeof__ of
ZeroDivisionError object>
__str__ = <method-wrapper '__str__' of ZeroDivisionError
object>
__subclasshook__ = <built-in method __subclasshook__ of type
object>
__suppress_context__ = False
__traceback__ = <traceback object>
args = ('division by zero',)
with_traceback = <built-in method with_traceback of
ZeroDivisionError object>
The above is a description of an error in a Python program.
Here is
the original traceback:
Traceback (most recent call last):
File "cgitb_with_classes.py", line 32, in <module>
o = BrokenClass(1, 0)
File "cgitb_with_classes.py", line 29, in __init__
self.d = self.a / self.b
ZeroDivisionError: division by zero
异常属性
除了每个栈帧中的相关域的变量,cgitb
还会显示出异常对象中所有的属性。自定义异常中的额外属性也会打印到错误报告中。
cgitb_exception_properties.py
import cgitb
cgitb.enable(format='text')
class MyException(Exception):
"""
添加一个额外的属性。
"""
def __init__(self, message, bad_value):
self.bad_value = bad_value
Exception.__init__(self, message)
return
raise MyException('Normal message', bad_value=99)
本例中的 bad_value
属性和标准的 message
和其他参数一起被打印了出来。
$ python3 cgitb_exception_properties.py
MyException
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018
A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they
occurred.
.../cgitb_exception_properties.py in <module>()
19 self.bad_value = bad_value
20 Exception.__init__(self, message)
21 return
22
23 raise MyException('Normal message', bad_value=99)
MyException = <class '__main__.MyException'>
bad_value undefined
MyException: Normal message
__cause__ = None
__class__ = <class '__main__.MyException'>
__context__ = None
__delattr__ = <method-wrapper '__delattr__' of MyException
object>
__dict__ = {'bad_value': 99}
__dir__ = <built-in method __dir__ of MyException object>
__doc__ = 'Add extra properties to a special exception\n
'
__eq__ = <method-wrapper '__eq__' of MyException object>
__format__ = <built-in method __format__ of MyException
object>
__ge__ = <method-wrapper '__ge__' of MyException object>
__getattribute__ = <method-wrapper '__getattribute__' of
MyException object>
__gt__ = <method-wrapper '__gt__' of MyException object>
__hash__ = <method-wrapper '__hash__' of MyException object>
__init__ = <bound method MyException.__init__ of
MyException('Normal message',)>
__init_subclass__ = <built-in method __init_subclass__ of
type object>
__le__ = <method-wrapper '__le__' of MyException object>
__lt__ = <method-wrapper '__lt__' of MyException object>
__module__ = '__main__'
__ne__ = <method-wrapper '__ne__' of MyException object>
__new__ = <built-in method __new__ of type object>
__reduce__ = <built-in method __reduce__ of MyException
object>
__reduce_ex__ = <built-in method __reduce_ex__ of
MyException object>
__repr__ = <method-wrapper '__repr__' of MyException object>
__setattr__ = <method-wrapper '__setattr__' of MyException
object>
__setstate__ = <built-in method __setstate__ of MyException
object>
__sizeof__ = <built-in method __sizeof__ of MyException
object>
__str__ = <method-wrapper '__str__' of MyException object>
__subclasshook__ = <built-in method __subclasshook__ of type
object>
__suppress_context__ = False
__traceback__ = <traceback object>
__weakref__ = None
args = ('Normal message',)
bad_value = 99
with_traceback = <built-in method with_traceback of
MyException object>
The above is a description of an error in a Python program.
Here is
the original traceback:
Traceback (most recent call last):
File "cgitb_exception_properties.py", line 23, in <module>
raise MyException('Normal message', bad_value=99)
MyException: Normal message
HTML 输出
cgitb
原本就是被设计用来处理 web 应用程序中的异常的,没理由不提一下 HTML 格式输出。之前的例子我们用的全是普通文本输出。要想输出 HTML 格式,我们可以不指定 format
(或指定为 "html"
)。不过大部分现在的 web 应用程序都使用带有错误报告的框架,所以 它的 HTML 格式也基本过时了。
记录 Traceback
大多数场景中,打印出标准错误的追踪详情是很好的解决方式。不过在生产系统中,把它记录下来更加棒一些。enable()
函数有一个 logdir
参数,可以指定错误的记录目录。指定了目录后,每条异常都会被记录到给定的目录中。
cgitb_log_exception.py
import cgitb
import os
LOGDIR = os.path.join(os.path.dirname(__file__), 'LOGS')
if not os.path.exists(LOGDIR):
os.makedirs(LOGDIR)
cgitb.enable(
logdir=LOGDIR,
display=False,
format='text',
)
def func(a, divisor):
return a / divisor
func(1, 0)
即使错误被抑制,仍然会有一条描述在哪发生该错误的消息被打印。
$ python3 cgitb_log_exception.py
<p>A problem occurred in a Python script.
.../LOGS/tmpq7icvee3.txt contains the description of this error.
$ ls LOGS
tmpq7icvee3.txt
$ cat LOGS/*.txt
ZeroDivisionError
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018
A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they
occurred.
.../cgitb_log_exception.py in <module>()
24
25 def func(a, divisor):
26 return a / divisor
27
28 func(1, 0)
func = <function func>
.../cgitb_log_exception.py in func(a=1, divisor=0)
24
25 def func(a, divisor):
26 return a / divisor
27
28 func(1, 0)
a = 1
divisor = 0
ZeroDivisionError: division by zero
__cause__ = None
__class__ = <class 'ZeroDivisionError'>
__context__ = None
__delattr__ = <method-wrapper '__delattr__' of
ZeroDivisionError object>
__dict__ = {}
__dir__ = <built-in method __dir__ of ZeroDivisionError
object>
__doc__ = 'Second argument to a division or modulo operation
was zero.'
__eq__ = <method-wrapper '__eq__' of ZeroDivisionError
object>
__format__ = <built-in method __format__ of
ZeroDivisionError object>
__ge__ = <method-wrapper '__ge__' of ZeroDivisionError
object>
__getattribute__ = <method-wrapper '__getattribute__' of
ZeroDivisionError object>
__gt__ = <method-wrapper '__gt__' of ZeroDivisionError
object>
__hash__ = <method-wrapper '__hash__' of ZeroDivisionError
object>
__init__ = <method-wrapper '__init__' of ZeroDivisionError
object>
__init_subclass__ = <built-in method __init_subclass__ of
type object>
__le__ = <method-wrapper '__le__' of ZeroDivisionError
object>
__lt__ = <method-wrapper '__lt__' of ZeroDivisionError
object>
__ne__ = <method-wrapper '__ne__' of ZeroDivisionError
object>
__new__ = <built-in method __new__ of type object>
__reduce__ = <built-in method __reduce__ of
ZeroDivisionError object>
__reduce_ex__ = <built-in method __reduce_ex__ of
ZeroDivisionError object>
__repr__ = <method-wrapper '__repr__' of ZeroDivisionError
object>
__setattr__ = <method-wrapper '__setattr__' of
ZeroDivisionError object>
__setstate__ = <built-in method __setstate__ of
ZeroDivisionError object>
__sizeof__ = <built-in method __sizeof__ of
ZeroDivisionError object>
__str__ = <method-wrapper '__str__' of ZeroDivisionError
object>
__subclasshook__ = <built-in method __subclasshook__ of type
object>
__suppress_context__ = False
__traceback__ = <traceback object>
args = ('division by zero',)
with_traceback = <built-in method with_traceback of
ZeroDivisionError object>
The above is a description of an error in a Python program.
Here is
the original traceback:
Traceback (most recent call last):
File "cgitb_log_exception.py", line 28, in <module>
func(1, 0)
File "cgitb_log_exception.py", line 26, in func
return a / divisor
ZeroDivisionError: division by zero
参阅
- cgitb 标准库文档
traceback
-- 追踪信息的标准库模块inspect
-- 有更多检查栈信息的函数的模块。sys
-- 访问当前异常信息和查看(修改)异常处理器(excepthook
)。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: