@app.exception_handler不能捕获所有异常,因为它仅处理路由函数内抛出的异常,而中间件错误、依赖注入失败、BaseHTTPMiddleware自身异常等均无法捕获;必须用BaseHTTPMiddleware全局包裹请求生命周期才能实现全链路兜底。

为什么@app.exception_handler不能捕获所有异常
因为 @app.exception_handler 只监听路由函数(@app.get、@router.post 等)内部抛出的异常。中间件里崩了、依赖注入失败、BaseHTTPMiddleware 自身逻辑出错,它完全看不见。
典型现象包括:
- 500 响应体为空,或返回原始 traceback(尤其 debug=True 时)
- 自定义日志中间件里
await request.body()报RuntimeError,前端收不到任何响应 -
RequestValidationError被处理了,但PydanticValidationError(v1)或 ORM 层抛的IntegrityError还是走默认 500
所以仅靠装饰器注册 handler 是不完整的兜底方案。
必须用 BaseHTTPMiddleware 实现全链路捕获
只有继承 BaseHTTPMiddleware 并重写 dispatch 方法,才能包裹整个请求生命周期——从接收请求、执行中间件、调用路由、到生成响应的全过程。
立即学习“Python免费学习笔记(深入)”;
关键点:
- 必须把
call_next(request)包在try块里 - 异常捕获顺序不能错:
HTTPException→RequestValidationError→PydanticValidationError→Exception - 生产环境务必过滤
traceback.format_exception,避免泄露路径、变量名等敏感信息
示例片段:
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from pydantic import ValidationError as PydanticValidationError
<p>class GlobalExceptionHandler(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
return await call_next(request)
except HTTPException as e:
return JSONResponse(
status_code=e.status_code,
content={"code": e.status_code, "message": e.detail},
)
except PydanticValidationError as e:
return JSONResponse(
status_code=422,
content={"code": 422, "message": "参数校验失败", "errors": e.errors()},
)
except Exception as e:</p><h1>生产环境禁用 traceback 输出</h1><pre class="brush:php;toolbar:false;"> if not request.app.debug:
return JSONResponse(
status_code=500,
content={"code": 500, "message": "服务器内部错误"},
)
# 开发环境可保留简略 traceback(非完整 format_exception)
import traceback
tb_str = traceback.format_exception_only(type(e), e)[-1].strip()
return JSONResponse(
status_code=500,
content={"code": 500, "message": "服务器内部错误", "detail": tb_str},
)自定义异常类要带 status_code 和 code 字段
只用 HTTPException 会丢失业务语义:比如同样是 400,是「用户名已存在」还是「手机号格式错误」?前端无法区分提示逻辑。
推荐定义继承 Exception 的类,并在全局 handler 中统一映射:
-
status_code控制 HTTP 状态码 -
code提供业务错误码(如"USER_EXISTS") -
message是面向用户的友好文案
例如:
class UserExistsError(Exception):
def __init__(self, message: str = "用户名已存在"):
self.status_code = 400
self.code = "USER_EXISTS"
self.message = message
对应 handler 注册:
app.add_exception_handler(UserExistsError, user_exists_handler)
app.add_exception_handler 和 @app.exception_handler 的区别
@app.exception_handler 是装饰器语法,适合静态注册;app.add_exception_handler 是运行时方法调用,支持动态插拔——比如按模块加载、插件系统、或测试时临时替换 handler。
注意:
- 两者注册的 handler 函数签名必须一致:
async def handler(request: Request, exc: Exception) -> JSONResponse - handler 内必须返回
JSONResponse,不能只写return {"detail": ...}(那样会触发 FastAPI 默认序列化,丢掉status_code) - 如果同时注册了多个 handler 处理同一异常类型,后注册的会覆盖前一个
真正容易被忽略的是:中间件崩溃、依赖注入失败、甚至 app.add_exception_handler 自己注册失败时,都可能让异常逃逸——所以最外层那个 BaseHTTPMiddleware 才是真正的最后一道防线。


















