最有效方式是早期替换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</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" class="overflowclass">macOS UI 自动化技能包 —— 用 Python 控制你的 Mac</a>
<p class="overflowclass">让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript
系统要求
macOS 14+ (Sonoma)
Python 3.9+
需要开启:辅助功能 + 屏幕录制权限</p>
</div>
<a rel="nofollow" href="/xiazai/skill9965" title="macOS UI 自动化技能包 —— 用 Python 控制你的 Mac" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
多线程和异步任务必须额外覆盖
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 检查和边界校验。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










