向sys.excepthook添加函数

5 投票
5 回答
1721 浏览
提问于 2025-04-17 07:53

假设我有这样的代码,它会把未处理的错误发送到 logging.critical()

import sys

def register_handler():
    orig_excepthook = sys.excepthook

    def error_catcher(*exc_info):
        import logging
        log = logging.getLogger(__name__)
        log.critical("Unhandled exception", exc_info=exc_info)
        orig_excepthook(*exc_info)

    sys.excepthook = error_catcher

这个方法是有效的:

import logging
logging.basicConfig()

register_handler()

undefined() # logs, then runs original excepthook

但是如果 register_handler() 被调用多次,就会导致多个 error_catcher 被依次调用,这样日志信息就会出现好几次。

我能想到一些方法来解决这个问题,但都不是特别好(比如检查 sys.excepthook 是否是 error_catcher 函数,或者在模块中使用一个 "have_registered" 属性来避免重复注册)。

有没有推荐的解决方案呢?

5 个回答

3

如果你把问题中的代码放到一个模块里,你可以多次导入这个模块,但它的代码只会在第一次导入的时候执行。

4

你可以先检查一下 sys.excepthook 是否还是一个内置函数,然后再注册你的处理程序:

>>> import sys, types
>>> isinstance(sys.excepthook, types.BuiltinFunctionType)
True
>>> sys.excepthook = lambda x: x
>>> isinstance(sys.excepthook, types.BuiltinFunctionType)
False
2

在模块级别设置一个“钩子是否已经注册”的变量,似乎是最简单、最可靠的方法。

其他可能的解决方案在某些(相对不常见的)情况下会出问题——如果一个应用程序注册了自定义的 excepthook,那么检查 sys.excepthook 是否是内置函数就会失败;在函数定义时存储原始的 excepthook 会覆盖后面注册的 excepthook 函数。

import sys

_hook_registered = False

def register_handler(force = False):
    global _hook_registered

    if _hook_registered and not force:
        return

    orig_excepthook = sys.excepthook

    def error_catcher(*exc_info):
        import logging
        log = logging.getLogger(__name__)
        log.critical("Unhandled exception", exc_info=exc_info)
        orig_excepthook(*exc_info)

    sys.excepthook = error_catcher

    _hook_registered = True

撰写回答