【问题标题】:Log exception with traceback in python在python中使用回溯记录异常
【发布时间】:2010-12-03 06:26:30
【问题描述】:

如何记录我的 Python 错误?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?

【问题讨论】:

    标签: python exception logging error-handling


    【解决方案1】:

    我就是这样做的。

    try:
        do_something()
    except:
        # How can I log my exception here, complete with its traceback?
        import traceback
        traceback.format_exc() # this will print a complete trace to stout.
    

    【讨论】:

      【解决方案2】:

      使用except: 处理程序/块中的logging.exception 记录当前异常以及跟踪信息,并附加一条消息。

      import logging
      LOG_FILENAME = '/tmp/logging_example.out'
      logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
      
      logging.debug('This message should go to the log file')
      
      try:
          run_my_stuff()
      except:
          logging.exception('Got exception on main handler')
          raise
      

      现在查看日志文件,/tmp/logging_example.out

      DEBUG:root:This message should go to the log file
      ERROR:root:Got exception on main handler
      Traceback (most recent call last):
        File "/tmp/teste.py", line 9, in <module>
          run_my_stuff()
      NameError: name 'run_my_stuff' is not defined
      

      【讨论】:

      • 为此查看了 django 代码,我假设答案是否定的,但是有没有办法将回溯限制为一定数量的字符或深度?问题是对于大型回溯,它需要很长时间。
      • 请注意,如果您使用logger = logging.getLogger('yourlogger')定义记录器,则必须编写logger.exception('...')才能使其正常工作...
      • 我们能不能修改一下,让消息打印出日志级别的INFO?
      • 请注意,对于某些外部应用程序,例如 Azure 洞察力,引用不会存储在日志中。然后有必要将它们显式传递给消息字符串,如下所示。
      • 我一直认为logging.exception(...)(或logger.exception(...),如果这样配置)会这样做。但是在异常钩子处理程序的上下文中,我对这种方法exception 有点困惑:在这种情况下,它似乎没有打印出回溯。开始怀疑您的调用代码必须在 except 块中才能正常工作。
      【解决方案3】:

      这是一个使用 sys.excepthook 的版本

      import traceback
      import sys
      
      logger = logging.getLogger()
      
      def handle_excepthook(type, message, stack):
           logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')
      
      sys.excepthook = handle_excepthook
      

      【讨论】:

      • {traceback.format_exc()}代替{traceback.format_tb(stack)}怎么样?
      【解决方案4】:

      您可以在任何级别(DEBUG、INFO、...)使用记录器获取回溯。注意使用logging.exception,级别为ERROR。

      # test_app.py
      import sys
      import logging
      
      logging.basicConfig(level="DEBUG")
      
      def do_something():
          raise ValueError(":(")
      
      try:
          do_something()
      except Exception:
          logging.debug("Something went wrong", exc_info=sys.exc_info())
      
      DEBUG:root:Something went wrong
      Traceback (most recent call last):
        File "test_app.py", line 10, in <module>
          do_something()
        File "test_app.py", line 7, in do_something
          raise ValueError(":(")
      ValueError: :(
      

      编辑:

      这也可以(使用 python 3.6)

      logging.debug("Something went wrong", exc_info=True)
      

      【讨论】:

        【解决方案5】:

        使用exc_info选项可能会更好,仍然是警告或错误标题:

        try:
            # coode in here
        except Exception as e:
            logging.error(e, exc_info=True)
        

        【讨论】:

        • 我不记得exc_info= kwarg 叫什么了;谢谢!
        • 这与 logging.exception 相同,只是该类型被冗余记录了两次。只需使用 logging.exception 除非您想要除错误以外的级别。
        • @Wyrmwood 这不一样,因为您必须向logging.exception提供消息
        • 使用logging.error,可以避免给''' '这样的尴尬消息logging.exception
        【解决方案6】:

        我在寻找什么:

        import sys
        import traceback
        
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback_in_var = traceback.format_tb(exc_traceback)
        

        见:

        【讨论】:

          【解决方案7】:

          未捕获的异常消息会发送到 STDERR,因此您可以使用任何用于运行 Python 脚本的 shell 将 STDERR 发送到文件,而不是在 Python 本身中实现日志记录。在 Bash 脚本中,您可以使用输出重定向来执行此操作,如 described in the BASH guide

          示例

          将错误附加到文件,其他输出到终端:

          ./test.py 2>> mylog.log
          

          使用交错的 STDOUT 和 STDERR 输出覆盖文件:

          ./test.py &> mylog.log
          

          【讨论】:

            【解决方案8】:

            您可以通过将处理程序分配给sys.excepthook 来记录主线程上所有未捕获的异常,也许使用exc_info parameter of Python's logging functions

            import sys
            import logging
            
            logging.basicConfig(filename='/tmp/foobar.log')
            
            def exception_hook(exc_type, exc_value, exc_traceback):
                logging.error(
                    "Uncaught exception",
                    exc_info=(exc_type, exc_value, exc_traceback)
                )
            
            sys.excepthook = exception_hook
            
            raise Exception('Boom')
            

            但是,如果您的程序使用线程,请注意使用threading.Thread 创建的线程不会在其中发生未捕获的异常时触发sys.excepthook,如Issue 1230540 在 Python 问题中所述跟踪器。有人建议使用一些技巧来解决此限制,例如猴子修补Thread.__init__ 以使用替代run 方法覆盖run,该方法将原始方法包装在try 块中并从@ 内部调用sys.excepthook 987654335@ 块。或者,您可以自己手动将每个线程的入口点包装在 try/except 中。

            【讨论】:

            • 感谢您,这比用try: except 将所有内容包装在我的main() 函数中要优雅得多
            【解决方案9】:

            我的工作最近要求我记录应用程序中的所有回溯/异常。我尝试了许多其他人在网上发布的技术,例如上面的技术,但最终选择了另一种方法。覆盖traceback.print_exception

            我在http://www.bbarrows.com/ 写了一篇文章,那会更容易阅读,但我也会把它粘贴在这里。

            当负责记录我们的软件在野外可能遇到的所有异常时,我尝试了多种不同的技术来记录我们的 python 异常回溯。起初我认为 python 系统异常钩子 sys.excepthook 将是插入日志代码的完美位置。我正在尝试类似的东西:

            import traceback
            import StringIO
            import logging
            import os, sys
            
            def my_excepthook(excType, excValue, traceback, logger=logger):
                logger.error("Logging an uncaught exception",
                             exc_info=(excType, excValue, traceback))
            
            sys.excepthook = my_excepthook  
            

            这适用于主线程,但我很快发现我的 sys.excepthook 在我的进程启动的任何新线程中都不存在。这是一个大问题,因为大多数事情都发生在这个项目的线程中。

            在谷歌搜索和阅读大量文档后,我发现最有用的信息来自 Python 问题跟踪器。

            线程上的第一篇文章显示了sys.excepthook 不跨线程持续存在的工作示例(如下所示)。显然这是预期的行为。

            import sys, threading
            
            def log_exception(*args):
                print 'got exception %s' % (args,)
            sys.excepthook = log_exception
            
            def foo():
                a = 1 / 0
            
            threading.Thread(target=foo).start()
            

            这个 Python 问题线程上的消息确实导致了 2 个建议的黑客攻击。继承 Thread 并将 run 方法包装在我们自己的 try except 块中以捕获和记录异常,或者使用猴子补丁 threading.Thread.run 在您自己的 try except 块中运行并记录异常。

            在我看来,子类化Thread 的第一种方法在您的代码中没有那么优雅,因为您必须在任何想要拥有日志记录线程的地方导入和使用您的自定义Thread 类。这最终变得很麻烦,因为我不得不搜索我们的整个代码库并用这个自定义的Thread 替换所有正常的Threads。但是,很清楚 Thread 在做什么,并且如果自定义日志记录代码出现问题,人们会更容易诊断和调试。客户日志线程可能如下所示:

            class TracebackLoggingThread(threading.Thread):
                def run(self):
                    try:
                        super(TracebackLoggingThread, self).run()
                    except (KeyboardInterrupt, SystemExit):
                        raise
                    except Exception, e:
                        logger = logging.getLogger('')
                        logger.exception("Logging an uncaught exception")
            

            猴子修补threading.Thread.run 的第二种方法很好,因为我可以在__main__ 之后立即运行一次,并在所有异常中检测我的日志记录代码。猴子修补可能会令人讨厌,因为它会改变某些东西的预期功能。 Python 问题跟踪器建议的补丁是:

            def installThreadExcepthook():
                """
                Workaround for sys.excepthook thread bug
                From
            http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html
            
            (https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
                Call once from __main__ before creating any threads.
                If using psyco, call psyco.cannotcompile(threading.Thread.run)
                since this replaces a new-style class method.
                """
                init_old = threading.Thread.__init__
                def init(self, *args, **kwargs):
                    init_old(self, *args, **kwargs)
                    run_old = self.run
                    def run_with_except_hook(*args, **kw):
                        try:
                            run_old(*args, **kw)
                        except (KeyboardInterrupt, SystemExit):
                            raise
                        except:
                            sys.excepthook(*sys.exc_info())
                    self.run = run_with_except_hook
                threading.Thread.__init__ = init
            

            直到我开始测试我的异常日志记录,我才意识到我做错了。

            为了测试我放了一个

            raise Exception("Test")
            

            在我的代码中的某个地方。但是,包装一个调用此方法的方法是一个 try except 块,它打印出回溯并吞下异常。这非常令人沮丧,因为我看到回溯打印到 STDOUT 但没有被记录。然后我决定,一种更简单的记录回溯的方法就是对所有 python 代码用来打印回溯本身的方法进行修补,traceback.print_exception。 我最终得到了类似于以下内容:

            def add_custom_print_exception():
                old_print_exception = traceback.print_exception
                def custom_print_exception(etype, value, tb, limit=None, file=None):
                    tb_output = StringIO.StringIO()
                    traceback.print_tb(tb, limit, tb_output)
                    logger = logging.getLogger('customLogger')
                    logger.error(tb_output.getvalue())
                    tb_output.close()
                    old_print_exception(etype, value, tb, limit=None, file=None)
                traceback.print_exception = custom_print_exception
            

            此代码将回溯写入字符串缓冲区并将其记录到日志记录错误中。我有一个自定义日志处理程序设置了“customLogger”记录器,该记录器获取错误级别的日志并将它们发送回家进行分析。

            【讨论】:

            • 相当有趣的方法。一个问题 - add_custom_print_exception 似乎不在您链接到的网站上,而是那里有一些完全不同的最终代码。你会说哪个更好/更最终,为什么?谢谢!
            • 谢谢,很好的回答!
            • 有一个剪切和粘贴错字。在对 old_print_exception 的委托调用中,limit 和 file 应该传递 limit 和 file,而不是 None -- old_print_exception(etype, value, tb, limit, file)
            • 对于您的最后一个代码块,您可以调用logger.error(traceback.format_tb())(如果您也需要异常信息,也可以调用 format_exc()),而不是初始化 StringIO 并将异常打印到它。
            • 您的共享链接现在无效。我也无法使用上述方法记录回溯。我在代码中调用了raise Exception,在调用之前我调用了你定义的方法。什么都没有发生。 @Brad 和 @Christian
            【解决方案10】:

            也许没有那么时尚,但更容易:

            #!/bin/bash
            log="/var/log/yourlog"
            /path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)
            

            【讨论】:

              【解决方案11】:

              这是一个取自python 2.6 documentation的简单示例:

              import logging
              LOG_FILENAME = '/tmp/logging_example.out'
              logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)
              
              logging.debug('This message should go to the log file')
              

              【讨论】:

              • 问题是如何记录回溯
              猜你喜欢
              • 2014-09-11
              • 2010-09-18
              • 1970-01-01
              • 1970-01-01
              • 2021-07-09
              • 2019-01-29
              • 2019-04-09
              • 2010-11-23
              • 2010-09-16
              相关资源
              最近更新 更多