获取异常描述和导致异常的堆栈跟踪,全部以字符串形式

我看过很多关于Python中的堆栈跟踪和异常的文章。但还没找到我想要的。

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

就像这样:

try:
method_that_can_raise_an_exception(params)
except Exception as e:
print_to_textbox(complete_exception_description(e))

问题是:函数complete_exception_description是什么?

342909 次浏览

请参阅traceback模块,特别是format_exc()函数。# EYZ2。

import traceback


try:
raise ValueError
except ValueError:
tb = traceback.format_exc()
else:
tb = "No error"
finally:
print tb
>>> 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>\n']
>>> 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模块中的函数对其进行格式化。 在这里是一些格式化的例子

整个异常字符串位于:

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

让我们创建一个相当复杂的stacktrace,以演示我们得到完整的stacktrace:

def raise_error():
raise RuntimeError('something bad happened!')


def do_something_that_might_error():
raise_error()

记录完整的堆栈跟踪

最佳实践是为您的模块设置一个记录器。它将知道模块的名称,并且能够更改级别(在其他属性中,例如处理程序)

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

我们可以使用这个日志来获取错误:

try:
do_something_that_might_error()
except Exception as error:
logger.exception(error)

日志:

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!

所以我们得到的输出和我们有错误时是一样的:

>>> 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函数来代替,在这里演示记录字符串:

import traceback
try:
do_something_that_might_error()
except Exception as error:
just_the_string = traceback.format_exc()
logger.debug(just_the_string)

日志:

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!

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

例如这段代码…

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)

我们得到这个异常输出…

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

我的2美分。

import sys, traceback
try:
...
except Exception, e:
T, V, TB = sys.exc_info()
print ''.join(traceback.format_exception(T,V,TB))

在Python 3中,下面的代码将格式化一个Exception对象,就像使用traceback.format_exc()所获得的一样:

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()格式化整个东西
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) + '\n  {} {}'.format(excp.__class__,excp)

一个简单的演示:

def foo():
try:
something_invalid()
except Exception as e:
print(exception_to_string(e))


def bar():
return foo()

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

  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

我定义了以下helper类:

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 = "\n".join(traceback.format_exception(etype, value, tb))
return False
return True

我以后可以这样使用:

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

之后可以像这样消耗它:

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

(背景:我很沮丧,因为使用Promises和Exceptions一起使用,不幸的是,在一个地方引发的异常传递到另一个地方的on_rejected处理程序,因此很难从原始位置获得回溯)

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

try:
...
except Exception as e:
print(traceback.print_tb(e.__traceback__))

我使用的是Python 3.7。

# EYZ0:

因此,您可以从异常中获取堆栈跟踪,就像从任何其他异常中一样。使用traceback.TracebackException(用你的异常替换ex):

print("".join(traceback.TracebackException.from_exception(ex).format()))

一个扩展的例子和其他功能来做到这一点:

import traceback


try:
1/0
except Exception as ex:
print("".join(traceback.TracebackException.from_exception(ex).format()) == traceback.format_exc() == "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))) # This is True !!
print("".join(traceback.TracebackException.from_exception(ex).format()))

输出将是这样的:

True
Traceback (most recent call last):
File "your_file_name_here.py", line 29, in <module>
1/0
ZeroDivisionError: division by zero

如果你的目标是让异常和stacktrace消息看起来与python抛出错误时完全一样,那么以下代码在python 2+3中都适用:

import sys, traceback




def format_stacktrace():
parts = ["Traceback (most recent call last):\n"]
parts.extend(traceback.format_stack(limit=25)[:-2])
parts.extend(traceback.format_exception(*sys.exc_info())[1:])
return "".join(parts)


# EXAMPLE BELOW...


def a():
b()




def b():
c()




def c():
d()




def d():
assert False, "Noooh don't do it."




print("THIS IS THE FORMATTED STRING")
print("============================\n")


try:
a()
except:
stacktrace = format_stacktrace()
print(stacktrace)


print("THIS IS HOW PYTHON DOES IT")
print("==========================\n")
a()

它的工作原理是从堆栈中删除最后一个format_stacktrace()调用,并加入其余的调用。当运行时,上面的示例给出以下输出:

THIS IS THE FORMATTED STRING
============================


Traceback (most recent call last):
File "test.py", line 31, in <module>
a()
File "test.py", line 12, in a
b()
File "test.py", line 16, in b
c()
File "test.py", line 20, in c
d()
File "test.py", line 24, in d
assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.


THIS IS HOW PYTHON DOES IT
==========================


Traceback (most recent call last):
File "test.py", line 38, in <module>
a()
File "test.py", line 12, in a
b()
File "test.py", line 16, in b
c()
File "test.py", line 20, in c
d()
File "test.py", line 24, in d
assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

如果您想将跟踪转换为dict列表(对于python >3.5):

from traceback import TracebackException




def list_traceback(exc_value: BaseException):
result = list()


# get previous fails, so errors are appended by order of execution
if exc_value.__context__:
result += list_traceback(exc_value.__context__)


# convert Exception into TracebackException
tbe = TracebackException.from_exception(exc_value)


# get stacktrace (cascade methods calls)
error_lines = list()
for frame_summary in tbe.stack:
summary_details = {
'filename': frame_summary.filename,
'method'  : frame_summary.name,
'lineno'  : frame_summary.lineno,
'code'    : frame_summary.line
}
error_lines.append(summary_details)


# append error, by order of execution
result.append({"error_lines": error_lines,
"type"       : tbe.exc_type.__name__,
"message"    : str(tbe)})


return result

这将是结果(一个例子):

[
{
"error_lines": [
{
"filename": "/home/demo/file2.py",
"method": "do_error_2",
"lineno": 18,
"code": "a=1/0"
}
],
"type": "ZeroDivisionError",
"message": "division by zero"
},
{
"error_lines": [
{
"filename": "/home/demo/file_main.py",
"method": "demo2",
"lineno": 50,
"code": "file2.DEMO().do_error_2()"
},
{
"filename": "/home/demo/file2.py",
"method": "do_error_2",
"lineno": 20,
"code": "raise AssertionError(\"Raised inside the except, after division by zero\")"
}
],
"type": "AssertionError",
"message": "Raised inside the except, after division by zero"
}
]