【问题标题】:Logging uncaught exceptions in Python在 Python 中记录未捕获的异常
【发布时间】:2011-09-08 05:31:13
【问题描述】:

如何使未捕获的异常通过logging 模块而不是stderr 输出?

我意识到最好的方法是:

try:
    raise Exception, 'Throwing a boring exception'
except Exception, e:
    logging.exception(e)

但我的情况是,如果在没有捕获到异常时自动调用logging.exception(...),那将是非常好的

【问题讨论】:

标签: python logging exception-handling


【解决方案1】:

这是一个完整的小示例,其中还包含一些其他技巧:

import sys
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

if __name__ == "__main__":
    raise RuntimeError("Test unhandled")
  • 忽略键盘中断,以便控制台 python 程序可以使用 Ctrl + C 退出。

  • 完全依赖 python 的日志记录模块来格式化异常。

  • 使用带有示例处理程序的自定义记录器。这个将未处理的异常更改为转到 stdout 而不是 stderr,但您可以将相同样式的各种处理程序添加到记录器对象。

【讨论】:

  • 我会在异常钩子处理程序中使用logger.critical(),因为我会说未捕获的异常非常关键。
  • 这是IMO最实用的答案。
  • @chefarov 初始化所有其他日志记录的主文件
  • 嗨,我们怎样才能把这个信息写到像 debug.log 这样的文件中。我尝试添加行 logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') 但没有帮助。
  • @gnu_lorien 请注意,如果导入的模块引发异常,它可能不会使用您的新 sys.excepthook(tkinter 是这种行为的一个显着示例)。当模块定义了自己的异常钩子时,就会发生这种情况。在这些情况下,您需要覆盖此异常挂钩(对于 tkinter,请参阅 stackoverflow.com/a/44004413/10792235
【解决方案2】:

正如 Ned 指出的,sys.excepthook 每次引发异常但未被捕获时都会被调用。这样做的实际含义是,在您的代码中,您可以覆盖sys.excepthook 的默认行为来做任何您想做的事情(包括使用logging.exception)。

作为稻草人的例子:

import sys
def foo(exctype, value, tb):
    print('My Error Information')
    print('Type:', exctype)
    print('Value:', value)
    print('Traceback:', tb)

覆盖sys.excepthook:

>>> sys.excepthook = foo

提交明显的语法错误(省略冒号)并取回自定义错误信息:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

有关sys.excepthook的更多信息,请阅读the docs

【讨论】:

  • @Codemonkey 这不是保留关键字,它是预先存在的类型名称。您可以使用type 作为函数参数,尽管IDE 会抱怨隐藏全局type(很像在Javascript 中使用var self = this)。除非您需要访问函数内部的 type 对象,否则这并不重要,在这种情况下,您可以使用 type_ 作为参数。
  • 这里的短语 "every time" 具有误导性:"sys.excepthook 被调用每次引发异常且未被捕获"...因为在一个程序中,可能有 恰好一个“未捕获”异常。此外,sys.excepthook 在“引发”异常时不会被调用。当程序由于未捕获的异常而终止时调用它,该异常不能多次发生。
  • @Nawaz:它可以在 REPL 中发生不止一次
  • @Nawaz 如果程序正在使用线程,它也可能发生多次。我也似乎 GUI 事件循环(如 Qt)继续运行,即使异常已使其成为 sys.excepthook
  • 任何尝试测试上述代码的人,请确保在测试函数时生成回溯错误。 sys.excepthook 未处理 SyntaxError。您可以使用 print(1/0) 这将调用您定义的函数来覆盖 sys.excepthook
【解决方案3】:

为什么不:

import sys
import logging
import traceback

def log_except_hook(*exc_info):
    text = "".join(traceback.format_exception(*exc_info()))
    logging.error("Unhandled exception: %s", text)

sys.excepthook = log_except_hook

None()

这里是sys.excepthook 的输出,如上所示:

$ python tb.py
ERROR:root:Unhandled exception: Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

这是注释掉sys.excepthook 的输出:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

唯一的区别是前者在第一行的开头有ERROR:root:Unhandled exception:

【讨论】:

  • 另一个区别是前者将跟踪写入日志系统,因此您安装的任何处理程序和格式化程序都会被应用。后者直接写信给sys.stderr
【解决方案4】:

如果未捕获异常,将调用方法sys.excepthookhttp://docs.python.org/library/sys.html#sys.excepthook

当异常被引发但未被捕获时,解释器调用 sys.excepthook 并使用三个参数,即异常类、异常实例和回溯对象。在交互式会话中,这发生在控制返回到提示之前;在 Python 程序中,这发生在程序退出之前。可以通过为 sys.excepthook 分配另一个三参数函数来自定义对此类顶级异常的处理。

【讨论】:

  • 为什么会发送异常类?你不能总是通过在实例上调用type 来得到它吗?
  • sys.excepthook的参数是什么类型的?
【解决方案5】:

以 Jacinda 的回答为基础,但使用记录器对象:

def catchException(logger, typ, value, traceback):
    logger.critical("My Error Information")
    logger.critical("Type: %s" % typ)
    logger.critical("Value: %s" % value)
    logger.critical("Traceback: %s" % traceback)

# Use a partially applied function
func = lambda typ, value, traceback: catchException(logger, typ, value, traceback)
sys.excepthook = func

【讨论】:

【解决方案6】:

在我的情况下(使用python 3),当使用@Jacinda 的答案时,没有打印回溯的内容。相反,它只打印对象本身:&lt;traceback object at 0x7f90299b7b90&gt;

我会这样做:

import sys
import logging
import traceback

def custom_excepthook(exc_type, exc_value, exc_traceback):
    # Do not print exception when user cancels the program
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logging.error("An uncaught exception occurred:")
    logging.error("Type: %s", exc_type)
    logging.error("Value: %s", exc_value)

    if exc_traceback:
        format_exception = traceback.format_tb(exc_traceback)
        for line in format_exception:
            logging.error(repr(line))

sys.excepthook = custom_excepthook

【讨论】:

  • 你的回答,特别是traceback,format(tb(exc_traceback) 帮助我解决了我对traceback 处理程序和exc_traceback 对象之间区别的理解缺失。我试图在 exc_traceback.format_tb 这样的对象上使用处理程序的方法,这显然不起作用。一旦我知道如何使用处理程序和对象,它就会打开使用回溯的世界!谢谢!
【解决方案7】:

将您的应用入口调用包装在 try...except 块中,这样您就可以捕获并记录(并且可能重新引发)所有未捕获的异常。例如。而不是:

if __name__ == '__main__':
    main()

这样做:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise

【讨论】:

  • 这不是要问的问题。问题的意图是询问当异常不是由代码处理时该怎么办。
  • 嗯,Python 是一种编程语言,这意味着它不会“自动”做事(正如 OP 想要的那样),除了 if and when 你问它去做吧。换句话说,除非您为其编写代码,否则无法“自动”记录所有异常——这就是我的回答。
  • 好吧,如果您查看 Ned Batchelder 的回答,就会发现有一种叫做异常钩子的东西。您必须在代码中的一处进行定义,并处理所有未捕获的异常。
  • 异常钩子并没有改变它不是“自动”的事实(在 OP 想要的意义上)——换句话说,您仍然需要对其进行编码。 Ned 的回答(使用异常钩子)确实解决了最初的问题 - 只是,在我看来,它的方式比我的要少得多。
  • 这在很大程度上取决于您自己的目标。如果您编写程序来取悦 IDE,那么捕获所有异常可能不是一种选择。但是,如果您想优雅地处理错误,并向用户显示良好的反馈,那么恐怕您需要捕获所有异常。好吧,够讽刺了 :-)——如果你仔细看,你会发现代码拦截了异常,但随后又重新引发,所以除非你的 IDE 正在做一些它不应该做的“神奇”事情,它仍然会得到例外。
【解决方案8】:

虽然@gnu_lorien 的回答给了我很好的起点,但我的程序在第一次异常时崩溃了。

我提供了一个定制的(和/或)改进的解决方案,它静默记录带有@handle_error 修饰的函数的异常。

import logging

__author__ = 'ahmed'
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
    logging.critical(exc_value.message, exc_info=(exc_type, exc_value, exc_traceback))


def handle_error(func):
    import sys

    def __inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception, e:
            exc_type, exc_value, exc_tb = sys.exc_info()
            handle_exception(exc_type, exc_value, exc_tb)
        finally:
            print(e.message)
    return __inner


@handle_error
def main():
    raise RuntimeError("RuntimeError")


if __name__ == "__main__":
    for _ in xrange(1, 20):
        main()

【讨论】:

    【解决方案9】:

    也许你可以在模块顶部做一些事情,将标准错误重定向到一个文件,然后在底部记录那个文件

    sock = open('error.log', 'w')               
    sys.stderr = sock
    
    doSomething() #makes errors and they will log to error.log
    
    logging.exception(open('error.log', 'r').read() )
    

    【讨论】:

      【解决方案10】:

      为了回答 Mr.Zeus 在已接受答案的评论部分中讨论的问题,我使用它在交互式控制台中记录未捕获的异常(使用 PyCharm 2018-2019 测试)。我发现sys.excepthook 在 python shell 中不起作用,所以我更深入地研究并发现我可以使用sys.exc_info 代替。但是,sys.exc_info 不接受任何参数,而 sys.excepthook 接受 3 个参数。

      在这里,我使用sys.excepthooksys.exc_info 在交互式控制台和带有包装函数的脚本中记录这两个异常。要将钩子函数附加到两个函数,我有两个不同的接口,具体取决于是否给出参数。

      代码如下:

      def log_exception(exctype, value, traceback):
          logger.error("Uncaught exception occurred!",
                       exc_info=(exctype, value, traceback))
      
      
      def attach_hook(hook_func, run_func):
          def inner(*args, **kwargs):
              if not (args or kwargs):
                  # This condition is for sys.exc_info
                  local_args = run_func()
                  hook_func(*local_args)
              else:
                  # This condition is for sys.excepthook
                  hook_func(*args, **kwargs)
              return run_func(*args, **kwargs)
          return inner
      
      
      sys.exc_info = attach_hook(log_exception, sys.exc_info)
      sys.excepthook = attach_hook(log_exception, sys.excepthook)
      
      

      日志设置可以在 gnu_lorien 的回答中找到。

      【讨论】:

        猜你喜欢
        • 2010-11-08
        • 2015-03-01
        • 2021-10-05
        • 1970-01-01
        • 2010-09-06
        • 2023-03-19
        • 2013-06-04
        • 2019-04-28
        • 1970-01-01
        相关资源
        最近更新 更多