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

时间:2010-09-13 17:03:31

标签: python exception-handling

我正在编写一个程序来解析10个网站,查找数据文件,保存文件,然后解析它们以生成可以在NumPy库中使用的数据。此文件通过错误链接,格式错误,缺少条目以及我尚未分类的其他内容遇到错误。我最初制作这个程序来处理这样的错误:

try:
    do_stuff()
except:
    pass

但现在我想记录错误:

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

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

16 个答案:

答案 0 :(得分:614)

traceback.format_exc()sys.exc_info()会产生更多信息,如果这就是你想要的。

import traceback
import sys

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    # or
    print(sys.exc_info()[0])

答案 1 :(得分:428)

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

请注意,对于print_exc,在某些极端情况下,您将无法获得所期望的内容。在Python 2.x中:

import traceback

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

    traceback.print_exc()

...将显示 last 例外的追溯:

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的文档:

      

    将traceback返回值分配给处理异常的函数中的局部变量将导致循环引用。这将阻止同一函数中的局部变量或回溯引用的任何内容被垃圾回收。 [...] 如果您确实需要回溯,请务必在使用后将其删除(最好使用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!")

答案 2 :(得分:197)

如果您正在调试并且只想查看当前堆栈跟踪,则只需调用:

traceback.print_stack()

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

答案 3 :(得分:74)

  

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

如果您不想在错误时暂停程序,则需要尝试使用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!

答案 4 :(得分:11)

首先,不要使用print进行日志记录,有一个稳定,可靠且经过深思熟虑的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发生混乱。它不是一个公共接口,而是一个内部接口-如果您确实知道自己在做什么,则可以 使用它。它不仅仅用于打印异常。

答案 5 :(得分:7)

除了@Aaron Hall的回答之外,如果您正在记录,但又不想使用logging.exception()(因为它以ERROR级别登录),您可以使用较低级别并传递{{1} }。 e.g。

exc_info=True

答案 6 :(得分: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中失败的语句。

答案 7 :(得分:6)

要获得精确堆栈跟踪,作为字符串,如果没有尝试/除了它,那么被引发,只需将其置于除了捕获违规例外的块。

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

以下是如何使用它(假设已定义flaky_funclog调用您最喜欢的日志记录系统):

import traceback
import sys

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

抓住并重新加注KeyboardInterrupt是一个好主意,这样你仍然可以使用Ctrl-C杀死程序。记录超出了问题的范围,但一个好的选择是loggingsystraceback模块的文档。

答案 8 :(得分:4)

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

  

回溯(最近通话最近):
   
中的文件“ C:\ Users \ User \ Desktop \ test.py”,第7行   地狱 do_stuff()
    do_stuff中的文件“ C:\ Users \ User \ Desktop \ test.py”,第4行
      1/0
  ZeroDivisionError:整数除法或以零为模
   o
  [以0.1秒完成]

所以我用:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')

答案 9 :(得分:3)

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

答案 10 :(得分:0)

使用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

另请参阅:Extract traceback info from an exception object

在Python 3.7.3中进行了测试。

答案 11 :(得分:0)

我在其他任何答案中都没有提到这一点。如果出于某种原因要传递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')

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

在离异常产生的地方越远的地方截获该异常将使代码更简单,同时还提供更多信息。

答案 12 :(得分:0)

如果您已经有一个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)。为什么您要打印出错误以及与该错误无关的回溯,这超出了我的范围。

答案 13 :(得分:0)

这是将错误写入日志文件以及控制台的解决方案:

<video controls width="400" height="300">
  <source src={Video} type="video/mp4"></source>
</video>

答案 14 :(得分:0)

python 3解决方案

stacktrace_helper.py

from linecache import getline
import sys
import 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_lineno
    ex_file = exc_tb.tb_frame.f_code.co_filename
    ex_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_str
    return trace_str

答案 15 :(得分:-1)

你可以这样做:

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