【问题标题】:How to catch and print the full exception traceback without halting/exiting the program?如何在不停止/退出程序的情况下捕获并打印完整的异常回溯?
【发布时间】:2011-04-11 18:54:10
【问题描述】:

我想在不退出的情况下捕获并记录异常,例如,

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 拦截异常,我确实希望它退出我的程序。我该怎么做?

【问题讨论】:

  • 不是一个完整的答案,但有人可能想知道您可以通过查看 err.__traceback__ 访问大量信息(至少在 Python 3.x 中)
  • 人们在尝试了解如何打印堆栈跟踪时查看了 825k 次。这是 Python 的另一种禅意。
  • 看来我是世界上唯一一个想要在没有错误的情况下打印堆栈的人(= 只是为了看看我是如何到达这条精确线的(这不是我的代码,它太丑了,我不知道它是怎么来的!))。

标签: python exception try-catch traceback


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • print(sys.exc_info()[0] 打印出<class 'Exception'>
      • 不要使用 exc... 回溯包含所有信息 stackoverflow.com/questions/4564559/…
      • 这可能是预期的(或不是),但这不会产生完整的回溯。它受到引发异常和处理它的点的限制(python 3.8)。你不需要try/catchdisplay the traceback
      • print(sys.exc_info()[2]) 产生<traceback object at 0x0000028A79E6B2C8>
      • print(traceback.format_exc()) 优于 traceback.print_tb(exc.__traceback__)print(sys.exc_info()) 返回整个元组,看起来像 (<class 'UnicodeDecodeError'>, UnicodeDecodeError('utf-8', b'\x81', 0, 1, 'invalid start byte'), <traceback object at 0x7f179d64ae00>) 所以确实 traceback.format_exc() 真的很棒,因为它打印 Traceback (most recent call last): File "<ipython-input-15-9e3d6e01ef04>", line 2, in <module> b"\x81".decode() UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte
      【解决方案4】:

      其他一些答案已经指出了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 !?!
      

      如果您确实需要访问原始traceback,一种解决方案是将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!")
      

      【讨论】:

        【解决方案5】:

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

        traceback.print_stack()

        无需手动引发异常来再次捕获它。

        【讨论】:

        • 回溯模块正是这样做的——引发并捕获异常。
        • 默认情况下输出到 STDERR 顺便说一句。没有出现在我的日志中,因为它被重定向到其他地方。
        • @pppery 我在 python 3.8 中看不到它。 trycatch 的问题是它不显示完整的回溯,仅从 raiseexcept
        【解决方案6】:

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

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

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

        【讨论】:

        • 如上所述,对我来说,traceback.print_exc() 仅返回最后一次调用:您如何成功返回堆栈的多个级别(可能还有所有级别?)
        • @geekobi 我不确定你在这里问什么。我演示了我们可以追溯到程序/解释器的入口点。你有什么不清楚的地方?
        • @geekobi 的意思是如果你抓住并重新加注,traceback.print_exc() 将只返回重新加注的堆栈,而不是原始堆栈。
        • @fizloki 你是如何“再融资”的?你是在做一个简单的raise 或异常链接,还是隐藏原始回溯?见stackoverflow.com/questions/2052390/…
        • 感谢您的后续问题 - 我认为 exc_info=True 参数实际上更适合记录,关键字参数比将回溯放入字符串的自定义代码更易于维护。我会尽快更新我的答案。
        【解决方案7】:

        要获得 precise 堆栈跟踪,作为一个字符串,如果没有 try/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 终止程序。日志记录超出了问题的范围,但一个不错的选择是loggingsystraceback 模块的文档。

        【讨论】:

        • 这在 Python 3 中不起作用,需要更改为 desired_trace = traceback.format_exc()。将 sys.exc_info() 作为参数传递从来都不是正确的做法,但在 Python 2 中会被默默忽略,但在 Python 3(无论如何是 3.6.4)中不会。
        • KeyboardInterrupt 不是(直接或间接)从Exception 派生的。 (两者都源自BaseException。)这意味着except Exception: 永远不会捕获KeyboardInterrupt,因此except KeyboardInterrupt: raise 是完全没有必要的。
        • traceback.format_exc(sys.exc_info()) 不适用于 python 3.6.10
        【解决方案8】:

        除了Aaron Hall's answer,如果你正在记录,但不想使用logging.exception()(因为它在ERROR级别记录),你可以使用较低的级别并通过exc_info=True。例如

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

        【讨论】:

        • 这在处理检测到的日志记录失败时也很好......即当您由于某种原因未能创建实际的 Logger 对象时。
        【解决方案9】:

        关于this answer 的cmets 的评论: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.1s完成】

        所以我使用:

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

        【讨论】:

          【解决方案10】:

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

          【讨论】:

          • 它也不能按原样工作。不是这个。我现在还没说完:这个答案只是浪费时间。
          • 我还要补充一点,你可以做logging.exception()。除非您有特殊要求,否则无需创建日志实例。
          • 我觉得这个答案有点荒谬。它充满了“仅仅因为这样做/不这样做”而没有解释原因。您在“为什么更好?”中的观点几乎所有人都在说同样的话:“因为它是。”我觉得这没有帮助。你至少解释了一点。
          • 很好的信息(我不知道logging.exception),但有点居高临下。我认为这是由于语言障碍而不是恶意。
          • 这家伙说了什么。在我的公司,我们会解雇任何使用打印记录的人。 /s
          【解决方案11】:

          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 g
              raise Exception('asdf')
          Exception: asdf
          

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

          另见:Extract traceback info from an exception object

          在 Python 3.9 中测试

          【讨论】:

            【解决方案12】:

            我在其他任何答案中都没有提到这一点。如果您出于某种原因传递 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 级。要在标准输出上打印完整的跟踪信息,我们必须在最高(最低?)级别捕获它:

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

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

            【讨论】:

            • 这比以前的方法好很多,但只是打印出堆栈跟踪仍然非常复杂。 Java 需要更少的代码 FGS。
            【解决方案13】:

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

            【讨论】:

              【解决方案14】:

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

              import logging, sys
              import traceback
              logging.basicConfig(filename='error.log', level=logging.DEBUG)
              
              def handle_exception(exc_type, exc_value, exc_traceback):
                  import sys
                  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
              

              【讨论】:

                【解决方案15】:

                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
                

                【讨论】:

                  【解决方案16】:

                  你可以这样做:

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

                  【讨论】:

                    【解决方案17】:

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

                    【讨论】:

                      【解决方案18】:
                      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()
                      

                      【讨论】:

                      • 2 cmets:traceback.print_exc() 的使用已在之前的答案中讨论过。更重要的是,当最后五行完全等同于 traceback.print_exc() 时,为什么还要对 io.StringIO 搞砸?
                      猜你喜欢
                      • 2012-05-10
                      • 2021-05-25
                      • 1970-01-01
                      • 2011-05-19
                      • 2020-10-28
                      • 1970-01-01
                      相关资源
                      最近更新 更多