简体   繁体   English

如何在不停止/退出程序的情况下捕获并打印完整的异常回溯?

[英]How to catch and print the full exception traceback without halting/exiting the program?

I want to catch and log exceptions without exiting, eg,我想在不退出的情况下捕获并记录异常,例如,

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

I want to print the exact same output that is printed when the exception is raised without the try/except intercepting the exception, and I do not want it to exit my program.我想打印在没有 try/except 拦截异常的情况下引发异常时打印的完全相同的输出,并且我希望它退出我的程序。

traceback.format_exc() or sys.exc_info() will yield more info if that's what you want.如果您想要的话, traceback.format_exc()sys.exc_info()将产生更多信息。

import traceback
import sys

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

Some other answer have already pointed out the traceback module.其他一些答案已经指出了回溯模块。

Please notice that with print_exc , in some corner cases, you will not obtain what you would expect.请注意,使用print_exc ,在某些极端情况下,您将无法获得预期的结果。 In Python 2.x:在 Python 2.x 中:

import traceback

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

    traceback.print_exc()

...will display the traceback of the last exception: ...将显示最后一个异常的回溯:

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

If you really need to access the original traceback one solution is to cache the exception infos as returned from exc_info in a local variable and display it using print_exception :如果您确实需要访问原始回溯一种解决方案是将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

Producing:生产:

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

Few pitfalls with this though:不过,这有几个陷阱:

  • From the doc of sys_info :来自sys_info的文档:

    Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference .将回溯返回值分配给处理异常的函数中的局部变量将导致循环引用 This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected.这将防止同一函数中的局部变量或回溯引用的任何内容被垃圾收集。 [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement) [...]如果您确实需要回溯,请确保在使用后将其删除(最好使用 try ... finally 语句完成)

  • but, from the same doc:但是,来自同一个文档:

    Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.从 Python 2.2 开始,当启用垃圾收集并且它们变得无法访问时,此类循环会自动回收,但避免创建循环仍然更有效。


On the other hand, by allowing you to access the traceback associated with an exception, Python 3 produce a less surprising result:另一方面,通过允许您访问与异常关联的回溯,Python 3 产生了一个不那么令人惊讶的结果:

import traceback

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

    traceback.print_tb(err.__traceback__)

... will display: ...将显示:

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

If you're debugging and just want to see the current stack trace, you can simply call:如果您正在调试并且只想查看当前堆栈跟踪,您可以简单地调用:

traceback.print_stack()

There's no need to manually raise an exception just to catch it again.无需手动引发异常来再次捕获它。

How to print the full traceback without halting the program?如何在不停止程序的情况下打印完整的回溯?

When you don't want to halt your program on an error, you need to handle that error with a try/except:当您不想因错误而停止程序时,您需要使用 try/except 来处理该错误:

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

To extract the full traceback, we'll use the traceback module from the standard library:要提取完整的回溯,我们将使用标准库中的traceback模块:

import traceback

And to create a decently complicated stacktrace to demonstrate that we get the full stacktrace:并创建一个相当复杂的堆栈跟踪来证明我们获得了完整的堆栈跟踪:

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

def do_something_that_might_error():
    raise_error()

Printing印刷

To print the full traceback, use the traceback.print_exc method:打印完整的回溯,请使用traceback.print_exc方法:

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

Which prints:哪个打印:

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!

Better than printing, logging:比打印、日志记录更好:

However, a best practice is to have a logger set up for your module.但是,最佳实践是为您的模块设置一个记录器。 It will know the name of the module and be able to change levels (among other attributes, such as handlers)它将知道模块的名称并能够更改级别(以及其他属性,例如处理程序)

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

In which case, you'll want the logger.exception function instead:在这种情况下,您将需要logger.exception函数:

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

Which logs:哪些日志:

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!

Or perhaps you just want the string, in which case, you'll want the traceback.format_exc function instead:或者您可能只需要字符串,在这种情况下,您需要使用traceback.format_exc函数:

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

Which logs:哪些日志:

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!

Conclusion结论

And for all three options, we see we get the same output as when we have an error:对于所有三个选项,我们看到我们得到与出现错误时相同的输出:

>>> 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!

Which to use使用哪个

Performance concerns aren't important here as IO usually dominates.性能问题在这里并不重要,因为 IO 通常占主导地位。 I'd prefer, since it does precisely what's being requested in a forward compatible way:我更喜欢,因为它以向前兼容的方式精确地完成了请求:

logger.exception(error)

Logging levels and outputs can be adjusted, making it easy to turn off without touching the code.可以调整记录级别和输出,无需触摸代码即可轻松关闭。 And usually doing what's directly needed is the most efficient way to do it.通常做直接需要的事情是最有效的方法。

First, do not use print s for logging, there is a stable, proven and well-thought out stdlib module to do that: logging .首先,不要使用print进行日志记录,有一个稳定、经过验证且经过深思熟虑的stdlib模块可以做到这一点: logging You definitely should use it instead.你绝对应该使用它。

Second, do not be tempted to do a mess with unrelated tools when there is a native and simple approach.其次,当有原生且简单的方法时,不要试图将不相关的工具弄得一团糟 Here it is:这里是:

log = logging.getLogger(__name__)

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

That's it.而已。 You are done now.你现在完成了。

Explanation for anyone who is interested in how things work under the hood任何对幕后工作方式感兴趣的人的解释

What log.exception is actually doing is just a call to log.error (that is, log event with level ERROR ) and print traceback then. log.exception实际上所做的只是调用log.error (即记录级别为ERROR的事件)然后打印回溯。

Why is it better?为什么更好?

Well, here are some considerations:好吧,这里有一些注意事项:

  • it is just right ;刚刚
  • it is straightforward;它很简单;
  • it is simple.很简单。

Why should nobody use traceback or call logger with exc_info=True or get their hands dirty with sys.exc_info ?为什么没有人应该使用traceback或使用exc_info=True调用 logger 或使用sys.exc_info弄脏他们的手?

Well, just because!好吧,只是因为! They all exist for different purposes.它们都存在于不同的目的。 For example, traceback.print_exc 's output is a little bit different from tracebacks produced by the interpreter itself.例如, traceback.print_exc的输出与解释器本身产生的回溯有点不同。 If you use it, you will confuse anyone who reads your logs, they will be banging their heads against them.如果你使用它,你会迷惑任何阅读你日志的人,他们会用头撞他们。

Passing exc_info=True to log calls is just inappropriate.传递exc_info=True来记录调用是不合适的。 But , it is useful when catching recoverable errors and you want to log them (using, eg INFO level) with tracebacks as well, because log.exception produces logs of only one level - ERROR .但是,当捕获可恢复的错误并且您还希望使用回溯记录它们(使用例如INFO级别)时,它很有用,因为log.exception只生成一个级别的日志 - ERROR

And you definitely should avoid messing with sys.exc_info as much as you can.而且您绝对应该尽可能避免弄乱sys.exc_info It's just not a public interface, it's an internal one - you can use it if you definitely know what you are doing.它只是不是一个公共接口,它是一个内部接口——如果你确定自己在做什么,你就可以使用它。 It is not intended for just printing exceptions.它不仅仅用于打印异常。

traceback.format_exception(exception_object)

If you only have the exception object, you can get the traceback as a string from any point of the code in Python 3 with:如果您只有异常对象,则可以从 Python 3 中的任何代码点获取作为字符串的回溯:

import traceback

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

Full example:完整示例:

#!/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)

Output:输出:

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

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

See also: Extract traceback info from an exception object另请参阅: 从异常对象中提取回溯信息

Tested in Python 3.9在 Python 3.9 中测试

In addition to Aaron Hall's answer , if you are logging, but don't want to use logging.exception() (since it logs at the ERROR level), you can use a lower level and pass exc_info=True .除了Aaron Hall 的回答之外,如果您正在记录,但不想使用logging.exception() (因为它在 ERROR 级别记录),您可以使用较低级别并传递exc_info=True eg例如

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

I don't see this mentioned in any of the other answers.我没有在任何其他答案中看到这一点。 If you're passing around an Exception object for whatever reason...如果您出于某种原因传递 Exception 对象...

In Python 3.5+ you can get a trace from an Exception object using traceback.TracebackException.from_exception() .在 Python 3.5+ 中,您可以使用traceback.TracebackException.from_exception()从 Exception 对象获取跟踪 For example:例如:

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()))

However, the above code results in:但是,上面的代码导致:

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')

This is just two levels of the stack, as opposed to what would have been printed on screen had the exception been raised in stack_lvl_2() and not intercepted (uncomment the # raise line).这只是堆栈的两个级别,与如果在stack_lvl_2()中引发异常并且没有被拦截(取消注释# raise行)会在屏幕上打印的内容相反。

As I understand it, that's because an exception records only the current level of the stack when it is raised, stack_lvl_3() in this case.据我了解,这是因为异常只记录堆栈的当前级别,在这种情况下是stack_lvl_3() As it's passed back up through the stack, more levels are being added to its __traceback__ .当它通过堆栈向上传递时,更多的级别被添加到它的__traceback__中。 But we intercepted it in stack_lvl_2() , meaning all it got to record was levels 3 and 2. To get the full trace as printed on stdout we'd have to catch it at the highest (lowest?) level:但是我们在stack_lvl_2()中截获了它,这意味着它要记录的只是级别 3 和级别 2。要获得打印在标准输出上的完整跟踪,我们必须在最高(最低?)级别捕获它:

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()))

Which results in:结果是:

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')

Notice that the stack print is different, the first and last lines are missing.请注意堆栈打印不同,缺少第一行和最后一行。 Because it's a different format() .因为它是不同的format()

Intercepting the exception as far away from the point where it was raised as possible makes for simpler code while also giving more information.尽可能远离引发异常的地方截取异常可以简化代码,同时提供更多信息。

In python3 (works in 3.9) we can define a function and can use that where ever we want to print the details.在 python3(适用于 3.9)中,我们可以定义一个函数,并且可以在我们想要打印详细信息的任何地方使用它。

import traceback

def get_traceback(e):
    lines = traceback.format_exception(type(e), e, e.__traceback__)
    return ''.join(lines)

try:
    1/0
except 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--------')

The output would be like:输出如下:

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/0
ZeroDivisionError: 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--------

If you have an Error object already, and you want to print the whole thing, you need to make this slightly awkward call:如果你已经有一个 Error 对象,并且你想打印整个东西,你需要做这个有点尴尬的调用:

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

That's right, print_exception takes three positional arguments: The type of the exception, the actual exception object, and the exception's own internal traceback property.没错, print_exception接受三个位置参数:异常的类型、实际的异常对象和异常自身的内部回溯属性。

In python 3.5 or later, the type(err) is optional... but it's a positional argument, so you still have to explicitly pass None in its place.在 python 3.5 或更高版本中, type(err)是可选的......但它是一个位置参数,所以你仍然必须显式地传递 None 。

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

I have no idea why all of this isn't just traceback.print_exception(err) .我不知道为什么所有这些不仅仅是traceback.print_exception(err) Why you would ever want to print out an error, along with a traceback other than the one that belongs to that error, is beyond me.为什么你会想要打印出一个错误,以及一个不属于该错误的回溯,这超出了我的理解。

To get the precise stack trace, as a string, that would have been raised if no try/except were there to step over it, simply place this in the except block that catches the offending exception.为了获得精确的堆栈跟踪,作为一个字符串,如果没有 try/except 可以跨过它,那么它被引发,只需将它放在捕获有问题的异常的 except 块中。

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

Here's how to use it (assuming flaky_func is defined, and log calls your favorite logging system):下面是如何使用它(假设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)

It's a good idea to catch and re-raise KeyboardInterrupt s, so that you can still kill the program using Ctrl-C.捕获并重新引发KeyboardInterrupt是个好主意,这样您仍然可以使用 Ctrl-C 终止程序。 Logging is outside the scope of the question, but a good option is logging .日志记录超出了问题的范围,但一个不错的选择是logging Documentation for the sys and traceback modules. systraceback模块的文档。

You will need to put the try/except inside the most innerloop where the error may occur, ie您需要将 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

... and so on ... 等等

In other words, you will need to wrap statements that may fail in try/except as specific as possible, in the most inner-loop as possible.换句话说,您需要将可能在 try/except 中失败的语句尽可能具体地包装在尽可能最内层的循环中。

A remark about this answer 's comments: print(traceback.format_exc()) does a better job for me than traceback.print_exc() .关于这个答案的评论的评论: print(traceback.format_exc())对我来说比traceback.print_exc()做得更好。 With the latter, the hello is sometimes strangely "mixed" with the traceback text, like if both want to write to stdout or stderr at the same time, producing weird output (at least when building from inside a text editor and viewing the output in the "Build results" panel).对于后者, hello有时会奇怪地与回溯文本“混合”,例如如果两者都想同时写入 stdout 或 stderr,产生奇怪的输出(至少在从文本编辑器内部构建并查看输出时) “构建结果”面板)。

Traceback (most recent call last):回溯(最近一次通话最后):
File "C:\Users\User\Desktop\test.py", line 7, in文件“C:\Users\User\Desktop\test.py”,第 7 行,在
hell do_stuff()地狱do_stuff()
File "C:\Users\User\Desktop\test.py", line 4, in do_stuff do_stuff 中的文件“C:\Users\User\Desktop\test.py”,第 4 行
1/0 1/0
ZeroDivisionError: integer division or modulo by zero ZeroDivisionError:整数除法或以零为模
o
[Finished in 0.1s] 【0.1s完成】

So I use:所以我使用:

import traceback, sys

def do_stuff():
    1/0

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

try:
    call_code_that_fails()
except:

    errors = io.StringIO()
    traceback.print_exc(file=errors)
    contents = str(errors.getvalue())
    print(contents)
    errors.close()

You want the traceback module.你想要回溯模块。 It will let you print stack dumps like Python normally does.它将让您像 Python 通常那样打印堆栈转储。 In particular, the print_last function will print the last exception and a stack trace.特别是, print_last函数将打印最后一个异常和堆栈跟踪。

This is my solution to write the error in a log file and also on console:这是我在日志文件和控制台上写入错误的解决方案:

import logging, sys
import traceback
logging.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)
        return
    exc_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

python 3 solution蟒蛇3解决方案

stacktrace_helper.py : 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

One liner: 一个班轮:

import traceback
traceback.print_exc()

Use the traceback module which automatically grabs the current exception when inside an except: 使用traceback模块,该模块在以下内容中自动获取当前异常except:

https://stackoverflow.com/a/9555145/2217801 https://stackoverflow.com/a/9555145/2217801

You could do:你可以这样做:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM