FastAPI中需通过app.add_exception_handler()注册全局异常处理器,统一返回结构化JSON响应;自定义异常类(如BusinessError)可扩展业务字段,避免仅用HTTPException的局限性。

FastAPI 中如何用 ExceptionHandler 捕获全局异常
FastAPI 默认把未处理的异常转成 500 响应,且返回 HTML 错误页(开发模式)或空响应(生产模式),这不符合 API 的 JSON 错误约定。必须显式注册 ExceptionHandler 才能统一输出结构化 JSON。
关键点是:不能靠 try/except 包裹每个路由,而要用 app.add_exception_handler() 注册函数,让 FastAPI 自动调用它处理指定异常类型。
- 注册时传入异常类(如
HTTPException、ValueError或自定义异常)和处理函数 - 处理函数签名必须是
async def handler(request: Request, exc: Exception) -> JSONResponse - 函数内必须返回
JSONResponse,不能用return {"detail": ...}(那会触发默认序列化,可能丢 status_code)
如何定义并抛出自定义异常类
直接 raise HTTPException(status_code=400, detail="xxx") 虽然可行,但难以区分业务错误类型、不方便统一加字段(如 error_code)、也不利于日志分类。推荐定义继承自 Exception 的类,再配一个对应的 handler。
例如定义 BusinessError:
立即学习“Python免费学习笔记(深入)”;
class BusinessError(Exception):
def __init__(self, error_code: str, message: str, status_code: int = 400):
self.error_code = error_code
self.message = message
self.status_code = status_code
在路由中直接 raise:
@app.get("/user/{uid}")
def get_user(uid: str):
if not uid.isdigit():
raise BusinessError("INVALID_UID", "UID must be numeric", status_code=422)
return {"id": uid}
为什么不能只靠 HTTPException?
HTTPException 是 FastAPI 提供的快捷工具,但它只支持 status_code 和 detail 两个字段,无法扩展业务字段(比如 trace_id、error_code、hint)。一旦你需要返回类似 {"error_code": "AUTH_FAILED", "message": "Token expired", "retry_after": 60} 这样的结构,HTTPException 就不够用了。
-
HTTPException的headers和status_code可以传,但响应体固定为{"detail": "..."},无法改键名 - 若强行用
JSONResponse替换整个响应,会绕过 FastAPI 的异常传播链,导致中间件(如 Sentry 日志)收不到原始异常对象 - 混合使用
HTTPException和自定义异常会让错误处理逻辑分散,后期难维护
返回 JSON 响应时容易忽略的细节
最常踩的坑是:用 return {"error_code": "...", "message": ...} 直接返回 dict。这看似能工作,但 FastAPI 会把它当正常响应体序列化,status_code 仍为 200,前端根本收不到错误信号。
正确做法是显式构造 JSONResponse 并传入 status_code:
from fastapi.responses import JSONResponse
@app.exception_handler(BusinessError)
async def business_error_handler(request: Request, exc: BusinessError):
return JSONResponse(
status_code=exc.status_code,
content={
"error_code": exc.error_code,
"message": exc.message,
"path": request.url.path,
},
)
注意:JSONResponse 不会自动加 Content-Type: application/json 吗?会的,它默认设置;但如果你手动设置了 media_type,要确保是 "application/json",否则某些客户端可能解析失败。
另外,别在 handler 里 raise 新异常——这会导致二次崩溃,且无法捕获。


















