如何捕获和打印完整的异常回溯而不停止/退出程序?

我想捕获并记录异常而不退出,例如,

try:do_stuff()except Exception as err:print(Exception, err)# I want to print the entire traceback here,# not just the exception name and details

我想打印与异常引发时打印的完全相同的输出,而不使用try/除非拦截异常,并且我没有希望它退出我的程序。

1186007 次浏览

您将需要把try/除外放在可能发生错误的最内循环中,即:

for i in something:for j in somethingelse:for k in whatever:try:something_complex(i, j, k)except Exception, e:print etry:something_less_complex(i, j)except Exception, e:print e

…等等

换句话说,你将需要包装语句可能失败的try/除非尽可能具体,在最内循环尽可能。

您想要追溯模块。它将允许您像Python通常一样打印堆栈转储。特别是,print_last函数将打印最后一个异常和堆栈跟踪。

#0#1将产生更多的信息,如果这是你想要的。

import tracebackimport sys
try:do_stuff()except Exception:print(traceback.format_exc())# orprint(sys.exc_info()[2])

其他一些答案已经指出了追溯模块。

请注意,使用print_exc,在某些情况下,您将无法获得您期望的结果。在Python 2. x中:

import traceback
try:raise TypeError("Oups!")except Exception, err:try:raise TypeError("Again !?!")except:pass
traceback.print_exc()

…将显示最后异常的回溯:

Traceback (most recent call last):File "e.py", line 7, in <module>raise TypeError("Again !?!")TypeError: Again !?!

如果您确实需要访问原始回溯一种解决方案是将#0返回的异常信息缓存在局部变量中,并使用#1显示它:

import tracebackimport sys
try:raise TypeError("Oups!")except Exception, err:try:exc_info = sys.exc_info()
# do you usefull stuff here# (potentially raising an exception)try:raise TypeError("Again !?!")except:pass# end of useful stuff

finally:# Display the *original* exceptiontraceback.print_exception(*exc_info)del exc_info

制作:

Traceback (most recent call last):File "t.py", line 6, in <module>raise TypeError("Oups!")TypeError: Oups!

但这有几个陷阱:

  • 来自#0的文档:

    将回溯返回值分配给处理异常的函数中的局部变量将导致循环参考。这将防止同一函数中的局部变量或回溯引用的任何内容被垃圾收集。[…]如果您确实需要回溯,请确保在使用后将其删除(最好使用try…最终语句完成)

  • 但是,来自同一文档:

    从Python 2.2开始,这些循环会自动回收当启用垃圾回收机制并且它们变得不可访问时,但避免创建循环仍然更有效。


另一方面,通过允许您访问traceback异常,Python 3产生了一个不那么令人惊讶的结果:

import traceback
try:raise TypeError("Oups!")except Exception as err:try:raise TypeError("Again !?!")except:pass
traceback.print_tb(err.__traceback__)

…将显示:

  File "e3.py", line 4, in <module>raise TypeError("Oups!")

如果您正在调试并且只想查看当前堆栈跟踪,您可以简单地调用:

traceback.print_stack()

没有必要手动引发异常只是为了再次捕获它。

如何在不停止程序的情况下打印完整的回溯?

当你不想因为一个错误而停止你的程序时,你需要用try/除了来处理这个错误:

try:do_something_that_might_error()except Exception as error:handle_the_error(error)

要提取完整的回溯,我们将使用标准库中的traceback模块:

import traceback

并创建一个体面复杂的堆栈跟踪来证明我们得到了完整的堆栈跟踪:

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

印刷

打印完整的回溯,请使用traceback.print_exc方法:

try:do_something_that_might_error()except Exception as error:traceback.print_exc()

哪些打印:

Traceback (most recent call last):File "<stdin>", line 2, in <module>File "<stdin>", line 2, in do_something_that_might_errorFile "<stdin>", line 2, in raise_errorRuntimeError: something bad happened!

比打印、日志更好:

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

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

在这种情况下,您将需要logger.exception函数:

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_errorFile "<stdin>", line 2, in raise_errorRuntimeError: something bad happened!

或者你只是想要字符串,在这种情况下,你会想要traceback.format_exc函数:

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

哪些日志:

DEBUG:__main__:Traceback (most recent call last):File "<stdin>", line 2, in <module>File "<stdin>", line 2, in do_something_that_might_errorFile "<stdin>", line 2, in raise_errorRuntimeError: 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_errorFile "<stdin>", line 2, in raise_errorRuntimeError: something bad happened!

使用哪个

性能问题在这里并不重要,因为IO通常占主导地位。我更喜欢,因为它以向前兼容的方式准确地完成了请求的内容:

logger.exception(error)

日志级别和输出可以调整,无需接触代码即可轻松关闭。通常,做直接需要的事情是最有效的方法。

要获取精确堆栈跟踪,作为一个字符串,如果没有try/除外可以跨过它,则已引发,只需将其放置在捕获违规异常的除外块中。

desired_trace = traceback.format_exc(sys.exc_info())

以下是如何使用它(假设定义了flaky_func,并且log调用您最喜欢的日志记录系统):

import tracebackimport sys
try:flaky_func()except KeyboardInterrupt:raiseexcept Exception:desired_trace = traceback.format_exc(sys.exc_info())log(desired_trace)

捕获并重新引发KeyboardInterrupt是个好主意,这样您仍然可以使用Ctrl-C杀死程序。日志记录超出了问题的范围,但一个好的选择是伐木sys追溯模块的文档。

除了Aaron Hall的回答之外,如果您正在记录,但不想使用logging.exception()(因为它在ERROR级别记录),您可以使用较低级别并传递exc_info=True

try:do_something_that_might_error()except Exception:logging.info('General exception noted.', exc_info=True)

关于这个答案的评论:print(traceback.format_exc())traceback.print_exc()做得更好。对于后者,hello有时会奇怪地与回溯文本“混合”,就像两者都想同时写入stdout或stderr一样,产生奇怪的输出(至少在从文本编辑器内部构建并在“构建结果”面板中查看输出时)。

回溯(最近一次调用):

文件"C:\User\User\Desktop\test.py",第7行地狱do_stuff()
在do_stuff
中,文件"C:\用户\桌面\test.py",第4行1/0
ZeroDivisionError:整数除法或零取模
o
[0.1s完成]

所以我使用:

import traceback, sys
def do_stuff():1/0
try:do_stuff()except Exception:print(traceback.format_exc())print('hello')

首先,不要使用print进行日志记录,有一个稳定的、经过验证的、经过深思熟虑的stdlib模块可以做到这一点:#2。你肯定会应该使用它。

其次,当有本地和简单的方法时,不要试图用不相关的工具做混乱。这里是:

log = logging.getLogger(__name__)
try:call_code_that_fails()except MyError:log.exception('Any extra info you want to see in your logs')

就这样。你现在完成了。

对任何对引擎盖下的事物如何工作感兴趣的人的解释

log.exception实际上所做的只是调用log.error(即,级别为ERROR的日志事件)然后打印回溯。

为什么会更好?

以下是一些考虑因素:

  • 只是
  • 它是直截了当的;
  • 这很简单。

为什么没有人应该使用traceback或用exc_info=True调用记录器,或者用sys.exc_info弄脏他们的手?

嗯,只是因为!它们存在的目的不同。例如,traceback.print_exc的输出与解释器本身产生的回溯有点不同。如果你使用它,你会混淆任何阅读你日志的人,他们会撞到他们。

但是,当捕获可恢复的错误并且您希望使用回溯记录它们(例如使用INFO级别)时,它很有用,因为log.exception仅生成一个级别的日志-ERROR

你绝对应该尽可能地避免弄乱sys.exc_info。它只是不是一个公共接口,它是一个内部接口-如果你肯定知道你在做什么,你可以使用它。它不仅仅用于打印异常。

traceback.format_exception(exception_object)

如果您只有异常对象,您可以从Python 3中代码的任何点以字符串形式获取回溯:

import traceback
''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

完整示例:

#!/usr/bin/env python3
import traceback
def f():g()
def g():raise Exception('asdf')
try:g()except Exception as e:exc_obj = e
tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))print(tb_str)

输出:

Traceback (most recent call last):File "./main.py", line 12, in <module>g()File "./main.py", line 9, in graise Exception('asdf')Exception: asdf

文档:https://docs.python.org/3.9/library/traceback.html#traceback.format_exception

另见:从异常对象中提取回溯信息

在Python 3.9中测试

我没有在任何其他答案中看到这一点。如果您出于任何原因传递Exception对象…

在Python 3.5+中,您可以使用traceback.TracebackException.from_exception()从Exception对象获取跟踪。例如:

import traceback

def stack_lvl_3():raise Exception('a1', 'b2', 'c3')

def stack_lvl_2():try:stack_lvl_3()except Exception as e:# raisereturn e

def stack_lvl_1():e = stack_lvl_2()return e
e = stack_lvl_1()
tb1 = traceback.TracebackException.from_exception(e)print(''.join(tb1.format()))

但是,上面的代码会导致:

Traceback (most recent call last):File "exc.py", line 10, in stack_lvl_2stack_lvl_3()File "exc.py", line 5, in stack_lvl_3raise Exception('a1', 'b2', 'c3')Exception: ('a1', 'b2', 'c3')

这只是堆栈的两个级别,而不是在stack_lvl_2()中引发异常并且没有被拦截(取消注释# raise行)时在屏幕上打印的内容。

据我所知,那是因为异常在引发时只记录堆栈的当前级别,在这种情况下是stack_lvl_3()。当它通过堆栈向上传递时,更多的级别被添加到它的__traceback__。但是我们在stack_lvl_2()中拦截了它,这意味着它只需要记录级别3和2。为了获得打印在stdout上的完整跟踪,我们必须在最高(最低?)级别捕获它:

import traceback

def stack_lvl_3():raise Exception('a1', 'b2', 'c3')

def stack_lvl_2():stack_lvl_3()

def stack_lvl_1():stack_lvl_2()

try:stack_lvl_1()except Exception as exc:tb = traceback.TracebackException.from_exception(exc)
print('Handled at stack lvl 0')print(''.join(tb.stack.format()))

其结果是:

Handled at stack lvl 0File "exc.py", line 17, in <module>stack_lvl_1()File "exc.py", line 13, in stack_lvl_1stack_lvl_2()File "exc.py", line 9, in stack_lvl_2stack_lvl_3()File "exc.py", line 5, in stack_lvl_3raise Exception('a1', 'b2', 'c3')

请注意,堆栈打印不同,第一行和最后一行缺失。因为它是不同的#0

尽可能远离异常引发点拦截异常可以简化代码,同时还可以提供更多信息。

如果你已经有一个Error对象,并且你想打印整个东西,你需要做这个稍微尴尬的调用:

import tracebacktraceback.print_exception(type(err), err, err.__traceback__)

没错,print_exception采用的位置参数:异常的类型、实际的异常对象和异常自己的内部回溯属性。

在python 3.5或更高版本中,type(err)是可选的……但它是一个位置参数,所以你仍然必须显式地传递无。

traceback.print_exception(None, err, err.__traceback__)

我不知道为什么这一切不仅仅是traceback.print_exception(err)。为什么你会想要打印出一个错误,以及一个不属于该错误的追溯,这超出了我的范围。

这是我在日志文件和控制台上写入错误的解决方案:

import logging, sysimport tracebacklogging.basicConfig(filename='error.log', level=logging.DEBUG)
def handle_exception(exc_type, exc_value, exc_traceback):if issubclass(exc_type, KeyboardInterrupt):sys.__excepthook__(exc_type, exc_value, exc_traceback)returnexc_info=(exc_type, exc_value, exc_traceback)logging.critical("\nDate:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))print("An error occured, check error.log to see the error details")traceback.print_exception(*exc_info)

sys.excepthook = handle_exception

python3解决方案

stacktrace_helper.py

from linecache import getlineimport sysimport traceback

def get_stack_trace():exc_type, exc_value, exc_tb = sys.exc_info()trace = traceback.format_stack()trace = list(filter(lambda x: ("\\lib\\" not in x and "/lib/" not in x and "stacktrace_helper.py" not in x), trace))ex_type = exc_type.__name__ex_line = exc_tb.tb_linenoex_file = exc_tb.tb_frame.f_code.co_filenameex_message = str(exc_value)line_code = ""try:line_code = getline(ex_file, ex_line).strip()except:pass
trace.insert(0, f'File "{ex_file}", line {ex_line}, line_code: {line_code} , ex: {ex_type} {ex_message}',)return trace

def get_stack_trace_str(msg: str = ""):trace = list(get_stack_trace())trace_str = "\n".join(list(map(str, trace)))trace_str = msg + "\n" + trace_strreturn trace_str

你可以这样做:

try:do_stuff()except Exception, err:print(Exception, err)raise err

在python3(在3.9中工作)中,我们可以定义一个函数,并且可以在我们想要打印细节的地方使用它。

import traceback
def get_traceback(e):lines = traceback.format_exception(type(e), e, e.__traceback__)return ''.join(lines)
try:1/0except Exception as e:print('------Start--------')print(get_traceback(e))print('------End--------')
try:spam(1,2)except Exception as e:print('------Start--------')print(get_traceback(e))print('------End--------')

输出将是这样的:

bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py------Start--------Traceback (most recent call last):File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>1/0ZeroDivisionError: division by zero
------End--------------Start--------Traceback (most recent call last):File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>spam(1,2)NameError: name 'spam' is not defined
------End--------
import ioimport traceback
try:call_code_that_fails()except:
errors = io.StringIO()traceback.print_exc(file=errors)contents = str(errors.getvalue())print(contents)errors.close()