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


777

我正在编写一个程序,该程序可以解析10个网站,找到数据文件,保存文件,然后解析它们以生成可以在NumPy库中轻松使用的数据。有万吨通过不良链接,不好的XML,缺项,其他的事情我还没有进行分类文件遇到错误的。我最初制作该程序来处理以下错误:

try:
    do_stuff()
except:
    pass

但是现在我想记录错误:

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

请注意,这是打印到日志文件中以供以后查看。这通常会打印非常无用的数据。我想要的是在错误触发时打印完全相同的行,而没有try-except拦截异常,但是我不希望它暂停我的程序,因为它嵌套在我想要的一系列for循环中看到完成。

Answers:


579

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

请注意,使用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 !?!

如果您确实需要访问原始的追溯,一种解决方案是将异常信息exc_info本地变量中返回,并使用来显示它print_exception

import traceback
import 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* exception
        traceback.print_exception(*exc_info)
        del exc_info

生产:

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

很少有这个陷阱:

  • 从文档sys_info

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

  • 但是,根据同一文档:

    从Python 2.2开始,启用垃圾收集并使其无法访问时,会自动回收此类循环,但是避免创建循环仍然更加有效。


另一方面,通过允许您访问异常关联的回溯,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!")


258

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

traceback.print_stack()

无需为了再次捕获而手动引发异常。


9
追溯模块正是这样做的-引发并捕获异常。
pppery

3
默认情况下,输出将输出到STDERR。没有出现在我的日志中,因为它已被重定向到其他地方。
mpen

101

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

当您不想因错误而暂停程序时,需要使用try / except处理该错误:

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_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

比打印,记录更好:

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

import logging
logging.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_error
  File "<stdin>", line 2, in raise_error
RuntimeError: 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_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!

2
正如上面所说,对我来说,也traceback.print_exc()只返回最后一个调用:如何成功返回堆栈的多个级别(可能是所有level s?)
herve-guerin 17/11/22

@geekobi我不确定您在这里问什么。我证明了我们可以追溯到程序/解释器的入口点。您不清楚什么?
亚伦·霍尔

1
@geekobi的意思是,如果您捕获并重新筹集,traceback.print_exc()只会返回重新筹集的堆栈,而不是原始堆栈。
fizloki

@fizloki您如何“提高”?您是在进行裸raise链或异常链,还是在隐藏原始回溯?看到stackoverflow.com/questions/2052390/...
艾伦·霍尔

21

首先,不要用printS表示日志记录,有非稳态,证明和深思熟虑的STDLIB模块,这样做:logging。您绝对应该使用它。

其次,当存在本机且简单的方法时,不要试图将无关的工具弄得一团糟。这里是:

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的输出与解释器本身产生的回溯有些不同。如果您使用它,则会使任何人阅读您的日志感到困惑,他们会撞到他们的头。

传递exc_info=True日志记录是不合适的。但是,当捕获可恢复的错误并且您还希望使用INFO回溯记录它们(使用例如级别)时,它很有用,因为它log.exception只会生成一个级别的日志- ERROR

而且,您绝对应该尽可能避免混乱sys.exc_info。它不是一个公共接口,而是一个内部接口- 如果您完全知道自己在做什么,就可以使用它。它不仅仅用于打印例外。


4
它也不能按原样工作。不是这个。我现在还没有完成:这个答案只是浪费时间。
A. Rager

我还要补充一点,你可以做logging.exception()。除非您有特殊要求,否则无需创建日志实例。
Shital Shah

9

除了@Aaron Hall的答案外,如果您正在记录日志,但又不想使用logging.exception()(由于它记录为ERROR级别),则可以使用较低级别并通过exc_info=True。例如

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

7

为了得到精确的堆栈跟踪,作为一个字符串,已如果没有尝试/除非在那里步过它,只是把这个在除块捕获违规的异常上升。

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

这是使用方法(假设flaky_func已定义,并log调用了您喜欢的日志系统):

import traceback
import sys

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

捕获并重新引发KeyboardInterrupts 是一个好主意,这样您仍然可以使用Ctrl-C终止程序。日志记录不在问题范围之内,但是不错的选择是loggingsystraceback模块的文档。


4
这在Python 3中不起作用,需要更改为desired_trace = traceback.format_exc()sys.exc_info()作为参数传递从来都不是正确的选择,但在Python 2中却被默默地忽略了,但在Python 3中却被忽略了(无论如何还是3.6.4)。
martineau

2
KeyboardInterrupt不是从(直接或间接)派生​​的Exception。(这两个都是从派生的BaseException。)这意味着except Exception:永远不会捕获a KeyboardInterrupt,因此except KeyboardInterrupt: raise完全没有必要。
AJNeufeld

traceback.format_exc(sys.exc_info())不适用于python 3.6.10的我
Nam G VU

6

您需要将try / except放到可能发生错误的最内部循环中,即

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

... 等等

换句话说,您需要将可能在try / except中失败的语句包装在尽可能多的内部循环中,并尽可能不具体。


6

关于此答案的评论:print(traceback.format_exc())对我来说,做得更好traceback.print_exc()。对于后者,hello有时会奇怪地将其与回溯文本“混合”,例如,如果两者都想同时写入stdout或stderr,则会产生奇怪的输出(至少在文本编辑器内部进行构建并在“构建结果”面板)。

追溯(最近一次通话):
文件“ C:\ Users \ User \ Desktop \ test.py”,第7行,在
地狱 do_stuff()
文件“ C:\ Users \ User \ Desktop \ test.py”,第4行,在do_stuff
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')

5

我在其他任何答案中都没有提到这一点。如果出于某种原因要传递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:
        # raise
        return 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_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise 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 0
  File "exc.py", line 17, in <module>
    stack_lvl_1()
  File "exc.py", line 13, in stack_lvl_1
    stack_lvl_2()
  File "exc.py", line 9, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')

注意,堆栈打印不同,缺少第一行和最后一行。因为是不同的format()

截取该异常离生成点越远越好,这使得代码更简单,同时也提供了更多信息。


这比以前的方法要好很多,但是仍然令人费解地盘绕起来,只是为了打印出堆栈跟踪。Java使用更少的代码FGS。
elhefe

3

您需要回溯模块。它可以让您像Python通常一样打印堆栈转储。特别是,print_last函数将打印最后的异常和堆栈跟踪。


3

从异常对象以字符串形式获取完整的追溯 traceback.format_exception

如果只有异常对象,则可以使用以下命令从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 = 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 g
    raise Exception('asdf')
Exception: asdf

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

另请参阅:从异常对象中提取回溯信息

在Python 3.7.3中测试。


2

如果您已经有一个Error对象,并且想要打印整个内容,则需要进行以下稍微尴尬的调用:

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

是的,print_exception需要三个位置参数:异常的类型,实际的异常对象以及异常自己的内部回溯属性。

在python 3.5或更高版本中,type(err)是可选的...但是它是一个位置参数,因此您仍然必须在其位置显式传递None。

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

我不知道为什么所有这些都不是唯一的traceback.print_exception(err)。为什么您要打印出错误以及与该错误无关的回溯,这超出了我的范围。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.