无需暂停/退出程序即可捕获并打印完整的 Python 异常回溯

2024-12-20 08:37:00
admin
原创
164
摘要:问题描述:我想捕获并记录异常而不退出,例如,try: do_stuff() except Exception as err: print(Exception, err) # I want to print the entire traceback here, # not jus...

问题描述:

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

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/except 拦截异常,并且我不希望它退出我的程序。


解决方案 1:

traceback.format_exc()如果这是您想要的,将会提供更多信息。

import traceback

def do_stuff():
    raise Exception("test exception")

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

输出:

Traceback (most recent call last):
  File "main.py", line 9, in <module>
    do_stuff()
  File "main.py", line 5, in do_stuff
    raise Exception("test exception")
Exception: test exception

解决方案 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 !?!

如果您确实需要访问原始回溯,一种解决方案是缓存局部变量中返回的异常信息并使用以下命令显示它: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!")

解决方案 3:

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

traceback.print_stack()

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

解决方案 4:

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

当你不想因为错误而停止程序时,你需要使用 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!

使用哪个

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

logger.exception(error)

可以调整日志级别和输出,无需修改代码即可轻松关闭。通常,执行直接需要的操作是最有效的方法。

解决方案 5:

traceback.format_exception(exception_object)

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

import traceback

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

从 Python 3.10 开始,这可以进一步简化为:

tb_str = ''.join(traceback.format_exception(exc_obj))

完整示例:

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

或者使用更简单的 Python 3.10 界面:

tb_str = ''.join(traceback.format_exception(exc_obj))

其中任一个的输出:

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.10/library/traceback.html#traceback.format_exception

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

测试了Python 3.11.4、Ubuntu 23.04。

解决方案 6:

首先,不要使用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。它不是一个公共接口,而是一个内部接口 - 如果你确实知道自己在做什么,就可以使用它。它不只是用于打印异常。

解决方案 7:

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

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

解决方案 8:

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

尽可能远离异常发生​​点拦截异常可以使代码更简单,同时提供更多信息。

解决方案 9:

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

输出结果如下:

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

解决方案 10:

如果您已经有一个 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)。为什么你会想要打印出一个错误,以及一个不属于该错误的回溯,这让我无法理解。

解决方案 11:

import io
import traceback

try:
    call_code_that_fails()
except:

    errors = io.StringIO()
    traceback.print_exc(file=errors)  # Instead of printing directly to stdout, the result can be further processed
    contents = str(errors.getvalue())
    print(contents)
    errors.close()

解决方案 12:

要获得精确的堆栈跟踪(以字符串形式),如果没有 try/except 跨越它就会引发该跟踪,只需将其放在捕获有问题的异常的 except 块中即可。

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 终止程序。日志记录超出了问题的范围,但一个不错的选择是日志记录。sys和traceback模块的文档。

解决方案 13:

您需要将 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 中失败的语句,并尽可能将其包装在最内部的循环中。

解决方案 14:

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

回溯(最近一次调用最后一次):

文件“C:\Users\User\Desktop\test.py”,第 7 行,在

hell do_stuff()

文件“C:\Users\User\Desktop\test.py”,第 4 行,在 do_stuff

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

解决方案 15:

此答案中的这种用法是自 Python 3.10 以来的新用法,之前的任何答案都没有涉及过。要打印回溯,可以将异常提供给traceback.print_exception

例子:

import traceback

try:
    object.bad_attr
except Exception as exc:
    traceback.print_exception(exc)

输出回溯:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: type object 'object' has no attribute 'bad_attr'

提醒一下,此答案需要 Python 3.10 或更新版本才能起作用。

要将引用捕获为字符串,请参阅此答案。

解决方案 16:

您需要traceback模块。它将允许您像 Python 通常所做的那样打印堆栈转储。特别是,print_last函数将打印最后一个异常和堆栈跟踪。

解决方案 17:

python3 解决方案

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 = "
".join(list(map(str, trace)))
    trace_str = msg + "
" + trace_str
    return trace_str

解决方案 18:

traceback我们无需使用模块.error().exception()日志记录方法就可以记录异常回溯。

import logging

try:
    print(0/0)
except Exception as e:
    logging.error(e, exc_info=True)

或者

import logging

try:
    print(0/0)
except Exception as e:
    logging.exception(e)

解决方案 19:

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

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("
Date:" + 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

解决方案 20:

你可以这样做:

try:
    do_stuff()
except Exception, err:
    print(Exception, err)
    raise err
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用