Python全局异常捕获应优先使用sys.excepthook,早期设置并保留原行为;子线程需同步配置threading.excepthook;自定义异常应携带结构化元数据并用isinstance判断类型;Web框架中需下沉至中间件或errorhandler;日志输出前须手动过滤敏感信息。

Python中全局异常捕获的正确姿势
直接用 sys.excepthook 替换默认异常处理器,是实现统一异常处理最轻量、最可靠的方式。它能捕获所有未被 try/except 拦截的异常,包括主线程崩溃、线程内未处理异常(需额外配置)、甚至 KeyboardInterrupt 以外的信号中断。
常见错误是只依赖顶层 try/except 包裹 main() 函数——这漏掉子线程、异步任务、定时器回调里的异常;也有人误以为 logging.basicConfig() 能接管所有异常输出,其实它只影响 logging.exception() 显式调用的场景。
- 在程序启动早期(如
if __name__ == "__main__":第一行)就设置sys.excepthook - 务必保留原钩子行为(如打印 traceback),否则调试时会丢失关键上下文
- 若使用
threading,需同步设置threading.excepthook(Python 3.8+),否则子线程异常仍会静默消失
自定义异常类如何与统一处理器联动
统一处理不等于“全部吞掉”。真正有用的方案是让业务异常携带结构化元数据(如错误码、重试标记、用户提示语),再由 sys.excepthook 分类响应。
比如定义 class UserError(Exception) 和 class SystemError(Exception),前者记录到监控系统并返回友好提示,后者触发告警并保留完整 traceback。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 避免继承
BaseException—— 会拦截SystemExit、KeyboardInterrupt,导致无法正常退出 - 在自定义异常的
__init__中接受code、details等参数,并存为实例属性,方便处理器读取 -
sys.excepthook内通过isinstance(exc, UserError)判断类型,而非字符串匹配异常名
Flask/FastAPI 等 Web 框架中的异常穿透问题
Web 框架有自己的异常处理层(如 Flask 的 @app.errorhandler),它们会提前拦截异常,导致 sys.excepthook 完全收不到。这时必须把统一逻辑下沉到框架约定的钩子里。
例如 FastAPI 中,sys.excepthook 对路由函数抛出的异常无效,但中间件可以捕获;Flask 则需注册 before_first_request + 全局 errorhandler(500) 组合补漏。
- FastAPI:写一个
BaseHTTPMiddleware子类,在dispatch中try/except并调用你的统一日志函数 - Flask:用
@app.errorhandler(Exception)捕获所有 500 错误,内部再调用同一份异常处理逻辑 - 注意区分 HTTP 异常(如
HTTPException)和非 HTTP 异常——前者通常不该进统一处理器,避免重复记录
日志格式与敏感信息过滤的实际约束
统一异常处理最终往往落地为日志,但直接打 traceback.format_exc() 会泄露变量值、路径、环境配置等敏感信息。不能只靠“不打印”来规避,得在序列化前做清洗。
Python 标准库没有内置的 traceback 敏感字段过滤机制,必须手动干预。比如从 exc.__traceback__ 提取帧对象,遍历 f_locals 并剔除含 password、token、key 的键。
- 不要用正则全局替换 traceback 字符串——容易误伤(如变量名含 “pass”)或漏掉嵌套结构
- 对
f_locals做浅拷贝后 pop 掉敏感 key,再传给traceback.print_exception()的file参数写入 StringIO - 生产环境禁用
exc.__notes__和__cause__的递归展开,防止循环引用或爆炸式日志
异常处理的复杂度不在捕获动作本身,而在后续的分类、脱敏、上报链路设计。一个没过滤密码的 500 日志,比没记录异常更危险。

















