在Python中记录未捕获的异常

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

我意识到最好的办法是:

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

但我的情况是,如果在未捕获异常时自动调用logging.exception(...),则它将是真的很不错

84154 次浏览

如果异常未被捕获,方法sys.excepthook将被调用

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

也许你可以在模块的顶部做一些事情,将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() )

正如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的更多信息,请阅读的文档

为什么不:

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:

以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

try...except块中包装你的应用程序入口调用,这样你就能够捕获和记录(也许重新引发)所有未捕获的异常。例如:

if __name__ == '__main__':
main()

这样做:

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

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

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,但是您可以以相同的样式向记录器对象添加各种处理程序。

虽然@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()

为了回答在已接受答案的评论部分讨论的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的回答中找到。

在我的情况下(使用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