在Python中记录未捕获的异常

Logging uncaught exceptions in Python

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

我意识到这样做的最好方法是:

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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对象中。


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

作为一个稻草人的例子:

1
2
3
4
5
6
7
>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
...

覆盖sys.excepthook

1
>>> sys.excepthook = foo

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

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

有关sys.excepthook的更多信息:http://docs.python.org/library/sys.html#sys.excepthook


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

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.


为什么不:

1
2
3
4
5
6
7
8
9
10
11
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的输出:

1
2
3
4
5
$ 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注释掉的输出:

1
2
3
4
5
$ 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:


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

1
2
3
4
5
6
7
8
9
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


将您的应用程序条目调用包装在try...except块中,这样您就可以捕获并记录(并可能重新引发)所有未捕获的异常。例如。代替:

1
2
if __name__ == '__main__':
    main()

做这个:

1
2
3
4
5
6
if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise


也许你可以在模块的顶部做一些事情,将stderr重定向到一个文件,然后在底部登录该文件

1
2
3
4
5
6
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() )

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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()