最有效方式是早期替换sys.excepthook,但需零阻塞、裸catch、避免logging;它仅主线程生效,多线程/asyncio需额外覆盖;atexit、信号处理等场景不触发,须提前规避。

直接替换 sys.excepthook 是最有效的方式,但必须在程序启动最早期完成,且钩子函数内部不能抛出任何新异常——否则进程会静默终止,连错误日志都来不及写。
为什么默认 sys.excepthook 在生产环境完全不可用
它只往 sys.stderr 输出无时间戳、无进程 ID、不落盘的纯文本 traceback,线上服务崩溃后根本无法定位是哪台机器、哪个时刻、哪个线程出的问题。更关键的是:子线程异常默认被吞掉、asyncio 任务异常不触发、atexit 或信号处理中抛错也完全绕过它。
如何写一个真正安全的全局异常钩子
核心原则是「最小依赖 + 零阻塞 + 裸 catch」:
- 不用
logging.error(..., exc_info=True)—— 它可能因 handler 初始化失败、磁盘满、格式化器崩溃而二次抛异常 - 改用
traceback.format_exception()生成纯字符串,再用os.write()或简单文件写入 - 所有 I/O 操作必须包裹
try/except BaseException(不是Exception),防止KeyboardInterrupt或SystemExit干扰 - 避免调用任何第三方库、网络请求、数据库连接、未加锁的全局变量
- 示例底线写法:
import sys
import traceback
import os
<p>def safe_excepthook(exc_type, exc_value, exc_tb):
try:
log_line = f"[{os.getpid()}] {os.getcwd()}\n" + "".join(
traceback.format_exception(exc_type, exc_value, exc_tb)
)
with open("error.log", "a") as f:
f.write(log_line)
except: # noqa: E722
pass # 宁可丢日志,也不能让进程死在这里</p><p>sys.excepthook = safe_excepthook立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
多线程和异步任务必须额外覆盖
sys.excepthook 只对主线程生效。其他场景需分别处理:
- Python ≥ 3.8:设置
threading.excepthook,签名与sys.excepthook一致,但注意它不共享主线程的 logger 配置 - asyncio:必须调用
loop.set_exception_handler(),否则create_task()后“放飞”的协程异常会彻底消失 - 子线程内手动
try/except包裹run()方法仍是兼容性最好的兜底方式
最容易被忽略的崩溃入口点
这些地方 sys.excepthook 根本不触发,但线上真会出问题:
-
atexit注册的函数里抛异常 - 信号处理回调(如
signal.signal(signal.SIGTERM, ...))中出错 -
fork()后子进程初始化失败 - C 扩展模块内部 segfault(此时 Python 已失去控制权)
对这些场景,唯一能做的就是提前规避:不在 atexit 做复杂操作,信号处理函数保持极简,C 扩展必须做充分的 NULL 检查和边界校验。

















