sys.excepthook 不够用,因其是纯函数调用,完全绕过 logging 的 handler、filter、level 和格式化机制,导致日志文件无记录或格式错乱;必须手动用 logging.critical(..., exc_info=True) 封装异常并显式配置 threading.excepthook 与 asyncio 异常处理器,且需确保 logging 初始化早于 hook 设置。

为什么 sys.excepthook 不够用
直接替换 sys.excepthook 能打印异常,但默认不走 logging 配置——比如你设了文件 handler、格式化器、日志级别,它全绕过。结果就是:控制台看到堆栈,但日志文件里空空如也,或者格式对不上。
根本原因是 sys.excepthook 是纯函数调用,和 logging 的 handler、filter、level 机制完全脱节。
用 logging.critical 模拟 excepthook(推荐)
最稳的方式是写一个兼容 logging 生态的 hook 函数,手动把异常信息转成 logging.critical 或 logging.error 日志,这样所有已配置的 handler(文件、SMTP、RotatingFileHandler 等)都会生效。
- 用
traceback.format_exception获取标准字符串堆栈,避免手动拼接出错 - 日志级别建议用
CRITICAL,因为未捕获异常本质是程序崩溃点,比ERROR更重 - 务必传入
exc_info=True参数,否则 handler 无法提取exc_info元组,某些 formatter(如%(exc_text)s)会失效
import sys import logging import traceback <p>def log_uncaught_exceptions(exc_type, exc_value, exc_traceback): logging.critical( "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) )</p><p>sys.excepthook = log_uncaught_exceptions
立即学习“Python免费学习笔记(深入)”;
别漏掉 threading 和 asyncio 的异常
主线程的 sys.excepthook 对子线程无效。Python 会把未捕获异常发给 threading.excepthook(3.8+),而 asyncio 任务崩溃则走 loop.set_exception_handler。
- 子线程异常:设置
threading.excepthook,同样用logging.critical(..., exc_info=...) - asyncio 异常:在事件循环启动前调用
loop.set_exception_handler,handler 内部也走 logging - 注意:
threading.excepthook默认值是None,不是继承sys.excepthook,必须显式设置
import threading
import asyncio
<p>def thread_excepthook(args):
logging.critical(
f"Uncaught exception in thread {args.thread.name}",
exc_info=(args.exc_type, args.exc_value, args.exc_traceback)
)</p><p>threading.excepthook = thread_excepthook</p><h1>asyncio 示例(需在 loop.run_forever() 前设置)</h1><p>def async_exception_handler(loop, context):
logging.critical(
"Uncaught exception in asyncio task",
exc_info=context.get("exception")
)
loop = asyncio.get_event_loop()
loop.set_exception_handler(async_exception_handler)logging.basicConfig 之后再设 excepthook
顺序错了就白配:如果先设 sys.excepthook,再调 logging.basicConfig,那 hook 里打的日志可能因 logger 还没初始化而被丢弃(尤其没显式创建 root logger 时)。
- 确保
logging.basicConfig或logging.config.dictConfig在sys.excepthook赋值之前完成 - 如果用了自定义 logger(非 root),要在 hook 里显式获取该 logger:
logging.getLogger("myapp"),并确认它有 handler - 测试时故意触发一次
raise ValueError("test"),检查日志输出是否含完整 traceback 和正确时间戳
真正容易被忽略的是多进程场景——sys.excepthook 只管当前进程,fork 出的子进程要各自重新设置;如果用 multiprocessing,得在每个 Process 的 run() 开头手动安装 hook。


















