sys.excepthook 不足以覆盖所有异常场景,因其仅捕获主线程未处理异常,无法捕获多线程、asyncio任务、信号处理及c扩展抛出的异常。

为什么 sys.excepthook 不足以覆盖所有异常场景
在生产环境里,sys.excepthook 只能捕获主线程中未被 try/except 拦截的异常,对多线程、异步任务(如 asyncio 任务)、信号处理或 C 扩展抛出的异常完全失效。比如一个 threading.Thread 中崩溃,主线程日志里根本看不到痕迹。
- 多线程需单独为每个线程设置
threading.excepthook(Python 3.8+)或重写Thread.run() -
asyncio必须监听loop.set_exception_handler(),否则Task内异常静默消失 -
atexit注册的清理函数里若出错,也不会走excepthook
如何统一捕获 threading 和 asyncio 的未处理异常
关键不是“替换钩子”,而是“补全钩子缺口”。Python 3.8 起支持 threading.excepthook,但得主动启用;asyncio 则必须显式绑定异常处理器。
- 为所有新线程设钩子:
threading.excepthook = your_log_handler(注意:仅对 Python 3.8+ 有效) - 对老版本或需兼容的场景,用
threading.Thread子类封装,重写run()方法包裹try/except - 在创建
asyncio.EventLoop后立即调用loop.set_exception_handler(your_async_handler) -
your_async_handler里别直接用logging.exception()—— 它可能阻塞事件循环,改用loop.call_soon_threadsafe()推送到主线程日志
logging.basicConfig 会干扰全局异常捕获吗
会,而且很隐蔽。logging.basicConfig() 如果在设置 sys.excepthook 前被调用,它会自动注册一个默认的 excepthook,覆盖你自定义的逻辑。更糟的是,它只记录异常,不包含线程名、协程栈或上下文标签。
- 务必在
import logging后、任何basicConfig()或getLogger()前,先设置好所有异常钩子 - 用
logging.getLogger().handlers检查是否已有 handler,避免重复初始化 - 推荐用 dictConfig 加载配置,明确控制 formatter 中包含
%(threadName)s、%(name)s和%(exc_info)s
生产环境必须加的兜底措施
即使钩子全设好了,仍有漏网之鱼:fork 后的子进程、signal 处理器里的异常、C 扩展 segfault。这时候靠操作系统级日志和进程守护更可靠。
- 用
faulthandler.enable()捕获 SIGSEGV/SIGFPE 等致命信号,并输出 Python 调用栈 - 在启动脚本里用
stdbuf -oL -eL强制行缓冲,防止日志丢失 - 配合 systemd 或 supervisord,把
StandardError重定向到文件并启用Restart=always - 不要依赖单个日志文件 —— 用
RotatingFileHandler并设置backupCount=7,避免磁盘打满
真正难的不是写几行钩子代码,而是确认每种执行路径(线程、协程、信号、子进程)都有对应出口。漏掉任意一种,线上故障就少一条关键线索。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











