关于python:获取导致异常的异常描述和堆栈跟踪,全部作为字符串

Get exception description and stack trace which caused an exception, all as a string

我在Python中看到了很多关于堆栈跟踪和异常的帖子。 但还没找到我需要的东西。

我有一大堆Python 2.7代码可能引发异常。 我想抓住它并将一个字符串分配给它的完整描述和导致错误的堆栈跟踪(我们只是在控制台上看到的所有内容)。 我需要这个字符串将其打印到GUI中的文本框中。

像这样的东西:

1
2
3
4
try:
    method_that_can_raise_an_exception(params)
except Exception as e:
    print_to_textbox(complete_exception_description(e))

问题是:函数complete_exception_description是什么?


请参见traceback模块,特别是format_exc()函数。这里。

1
2
3
4
5
6
7
8
9
10
import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb ="No error"
finally:
    print tb

让我们创建一个相当复杂的堆栈跟踪,以证明我们获得了完整的堆栈跟踪:

1
2
3
4
5
def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

记录完整的堆栈跟踪

最佳做法是为模块设置记录器。它将知道模块的名称并能够更改级别(以及其他属性,例如处理程序)

1
2
3
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

我们可以使用此记录器来获取错误:

1
2
3
4
try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

哪些日志:

1
2
3
4
5
6
ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File"<stdin>", line 2, in <module>
  File"<stdin>", line 2, in do_something_that_might_error
  File"<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

所以我们得到的输出与出错时的输出相同:

1
2
3
4
5
6
>>> do_something_that_might_error()
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 2, in do_something_that_might_error
  File"<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

只获得字符串

如果你真的只想要字符串,请使用traceback.format_exc函数,演示在此处记录字符串:

1
2
3
4
5
6
import traceback
try:
    do_something_that_might_error()
except Exception as error:
    just_the_string = traceback.format_exc()
    logger.debug(just_the_string)

哪些日志:

1
2
3
4
5
DEBUG:__main__:Traceback (most recent call last):
  File"<stdin>", line 2, in <module>
  File"<stdin>", line 2, in do_something_that_might_error
  File"<stdin>", line 2, in raise_error
RuntimeError: something bad happened!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> import sys
>>> import traceback
>>> try:
...   5 / 0
... except ZeroDivisionError as e:
...   type_, value_, traceback_ = sys.exc_info()
>>> traceback.format_tb(traceback_)
['  File"<stdin>", line 2, in <module>
'
]
>>> value_
ZeroDivisionError('integer division or modulo by zero',)
>>> type_
<type 'exceptions.ZeroDivisionError'>
>>>
>>> 5 / 0
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

您可以使用sys.exc_info()来收集traceback模块中的信息和函数以对其进行格式化。
以下是一些格式化示例。

整个异常字符串位于:

1
2
3
4
5
6
>>> ex = traceback.format_exception(type_, value_, traceback_)
>>> ex
['Traceback (most recent call last):
'
, '  File"<stdin>", line 2, in <module>
'
, 'ZeroDivisionError: integer division or modulo by zero
'
]

使用Python 3,以下代码将格式化Exception对象,与使用traceback.format_exc()获得的格式完全相同:

1
2
3
4
5
6
import traceback

try:
    method_that_can_raise_an_exception(params)
except Exception as ex:
    print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__)))

优点是只需要Exception对象(由于记录的__traceback__属性),因此可以更容易地作为参数传递给另一个函数以进行进一步处理。


对于那些使用Python-3的人

使用traceback模块和exception.__traceback__,可以按如下方式提取堆栈跟踪:

  • 使用traceback.extract_stack()获取当前堆栈跟踪
  • 删除最后三个元素(因为这些是堆栈中的条目,让我进入我的调试功能)
  • 使用traceback.extract_tb()从异常对象追加__traceback__
  • 使用traceback.format_list()格式化整个事物
1
2
3
4
5
6
import traceback
def exception_to_string(excp):
   stack = traceback.extract_stack()[:-3] + traceback.extract_tb(excp.__traceback__)  # add limit=??
   pretty = traceback.format_list(stack)
   return ''.join(pretty) + '
  {} {}'
.format(excp.__class__,excp)

一个简单的演示:

1
2
3
4
5
6
7
8
def foo():
    try:
        something_invalid()
    except Exception as e:
        print(exception_to_string(e))

def bar():
    return foo()

当我们调用bar()时,我们得到以下输出:

1
2
3
4
5
6
7
8
  File"./test.py", line 57, in <module>
    bar()
  File"./test.py", line 55, in bar
    return foo()
  File"./test.py", line 50, in foo
    something_invalid()

  <class 'NameError'> name 'something_invalid' is not defined

您还可以考虑使用内置的Python模块cgitb来获取一些非常好的,格式良好的异常信息,包括局部变量值,源代码上下文,函数参数等。

例如这个代码......

1
2
3
4
5
6
7
8
9
10
11
import cgitb
cgitb.enable(format='text')

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    return func2(a, c)

func1(1, 5)

我们得到这个异常输出......

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
ZeroDivisionError
Python 3.4.2: C:\tools\python\python.exe
Tue Sep 22 15:29:33 2015

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 c:\TEMP\cgittest2.py in <module>()
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
func1 = <function func1>

 c:\TEMP\cgittest2.py in func1(a=1, b=5)
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 c:\TEMP\cgittest2.py in func2(a=1, divisor=0)
    3
    4 def func2(a, divisor):
    5   return a / divisor
    6
    7 def func1(a, b):
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>
    __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"cgittest2.py", line 11, in <module>
    func1(1, 5)
  File"cgittest2.py", line 9, in func1
    return func2(a, c)
  File"cgittest2.py", line 5, in func2
    return a / divisor
ZeroDivisionError: division by zero


如果您希望在未处理异常时获得相同的信息,您可以执行类似的操作。做import traceback然后:

1
2
3
4
try:
    ...
except Exception as e:
    print(traceback.print_tb(e.__traceback__))

我正在使用Python 3.7。


我的2美分:

1
2
3
4
5
6
import sys, traceback
try:
  ...
except Exception, e:
  T, V, TB = sys.exc_info()
  print ''.join(traceback.format_exception(T,V,TB))

我定义了以下助手类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import traceback
class TracedExeptions(object):
    def __init__(self):
        pass
    def __enter__(self):
        pass

    def __exit__(self, etype, value, tb):
      if value :
        if not hasattr(value, 'traceString'):
          value.traceString ="
"
.join(traceback.format_exception(etype, value, tb))
        return False
      return True

以后我可以这样使用:

1
2
with TracedExeptions():
  #some-code-which-might-throw-any-exception

以后可以像这样使用它:

1
2
3
4
5
def log_err(ex):
  if hasattr(ex, 'traceString'):
    print("ERROR:{}".format(ex.traceString));
  else:
    print("ERROR:{}".format(ex));

(背景:由于将PromiseException一起使用,我很沮丧,不幸的是,它将一个地方引发的异常传递给另一个地方的on_rejected处理程序,因此很难从原始位置获取回溯)