在Python中记录未捕获的异常


181

您如何导致未捕获的异常通过logging模块而不是通过模块输出stderr

我意识到最好的方法是:

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

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



Answers:


143

正如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.excepthookhttp : //docs.python.org/library/sys.html#sys.excepthook


4
@Codemonkey不是保留关键字,它是一个预先存在的类型名称。type尽管IDE会抱怨隐藏全局type(就像var self = this在Javascript中使用),但您可以将其用作函数参数。除非您需要访问type函数内部的对象,否则这并不重要,在这种情况下,您可以将其type_用作参数。
Ryan P

3
这里的“每次”一词是令人误解的:: 每次引发并捕获一个异常都会调用sys.excepthook ” ……因为在程序中,可能 恰好有一个“未捕获”异常。另外,sys.excepthook“引发”异常时不调用。当程序由于未捕获的异常而终止时调用,该异常不能发生多次。
纳瓦兹

2
@Nawaz:在REPL中可能会发生多次
jfs

2
@Nawaz如果程序使用线程,它也可能发生多次。我也似乎GUI事件循环(如Qt)一直在运行,即使该异常已进入sys.excepthook
three_pineapples

1
任何尝试测试以上代码的人,请确保在测试功能时生成回溯错误。sys.excepthook不会处理SyntaxError。您可以使用print(1/0),这将调用您定义的函数以覆盖sys.excepthook
Parth Karia

177

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

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")
  • 忽略KeyboardInterrupt,以便控制台python程序可以使用Ctrl + C退出。

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

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


13
我会logger.critical()在excepthook处理程序中使用,因为未捕获的异常非常关键。
gitaarik 2015年

2
这是IMO最实用的答案。
大卫·莫拉莱斯

@gnu_lorien感谢您的代码段。您要将它放在哪个文件中?
stelios

@chefarov初始化所有其他日志记录的主文件
gnu_lorien

嗨,我们如何写到像debug.log这样的文件。我尝试添加行,logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')但没有帮助。
GurhanCagin

26

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

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


2
为什么它发送异常类?您不能总是通过调用type实例来获得该信息吗?
尼尔·G

23

为什么不:

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
radiaph '19

8

以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

2
最好使用functools.partial()而不是lambda。请参阅: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro

@MariuszJamro为什么?
davr

4

将您的应用程序入口调用包装在一个try...except块中,这样您就可以捕获和记录(甚至重新引发)所有未捕获的异常。例如:

if __name__ == '__main__':
    main()

做这个:

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

这不是在问什么问题。问题的目的是问代码未处理异常时该怎么办。
Mayank Jaiswal

1
好吧,Python是一种编程语言,这意味着它不会“自动”执行操作(如OP所希望的那样),除非您要求和何时执行。换句话说,除非您对此进行编码,否则没有办法“自动”记录所有异常-这就是我的答案。
flaviovs

1
好吧,如果您看一下Ned Batchelder的答案,就会有一种叫做异常钩子的东西。您必须在代码中的一个位置进行定义,所有未捕获的异常都将得到处理。
Mayank Jaiswal

1
异常钩子不会改变它不是“自动的”(OP想要的意思)的事实-换句话说,您仍然必须对其进行编码。内德(Ned)的答案(使用异常钩子)确实解决了最初的问题- 在我看来,这仅仅是Python语言比我的Python语言少得多的原因。
flaviovs

1
这在很大程度上取决于您自己的目标。如果您编程来取悦IDE,那么捕获所有异常的“是”可能不是一个选择。但是,如果您想优雅地处理错误并向用户显示良好的反馈,那么恐怕您将需要捕获所有异常。好吧,很讽刺:-)-如果仔细看,您会看到代码拦截了异常,然后重新引发,因此,除非您的IDE做不该做的“魔术”事情,否则它仍然会得到例外。
flaviovs

3

也许您可以在模块顶部执行一些操作,将stderr重定向到文件,然后在底部登录该文件

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

3

尽管@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()

2

为了回答已接受答案的注释部分中讨论的宙斯先生的问题,我使用它在交互式控制台中记录未捕获的异常(已通过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的答案中找到。


2

就我而言,python 3在使用@Jacinda的答案时(使用)没有打印回溯的内容。相反,它只是打印对象本身:<traceback object at 0x7f90299b7b90>

相反,我这样做:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.