Flask 的 @app.errorhandler(Exception) 捕获不到 500 错误,因常见异常(如 ValueError)被自动包装为 HTTPException 子类 InternalServerError,走 @app.errorhandler(500) 分支;需同时注册两者并关闭 debug、禁用 PROPAGATE_EXCEPTIONS 才能统一返回 JSON 响应。

Flask @app.errorhandler(Exception) 为什么捕获不到 500 错误?
因为 Flask 默认只把未被视图函数处理的 Exception(且非 HTTP 异常子类)交给 @app.errorhandler(Exception),但很多常见崩溃(比如视图里抛 ValueError、TypeError)会被 Flask 自动包装成 InternalServerError,而它属于 HTTPException 子类——走的是 @app.errorhandler(500) 分支,不是 Exception。
- 真正能被
@app.errorhandler(Exception)捕获的,是那些绕过 Flask HTTP 异常体系的“原始异常”,比如在before_request中直接raise KeyError - 想统一兜底所有服务端错误,必须同时注册
@app.errorhandler(500)和@app.errorhandler(Exception),否则 500 页面或空响应很常见 - 别依赖
Exception单一装饰器——它不覆盖HTTPException及其子类(包括BadRequest、NotFound等),这些走的是状态码 handler
怎么让所有错误都返回 JSON,包括 4xx/5xx 和未处理异常?
靠一个 handler 不够,得按错误类型分层拦截:先用 @app.errorhandler(400) 到 @app.errorhandler(500) 覆盖标准 HTTP 状态码,再用 @app.errorhandler(Exception) 收尾非 HTTP 异常。所有 handler 都返回 jsonify() 响应,并显式设 status。
- 每个 handler 函数必须返回一个
Response对象(如jsonify()的返回值),不能只写return {"error": "xxx"}—— 这会触发默认 HTML 响应 - 注意
HTTPException实例有.code和.description属性,可直接用:return jsonify(error=str(e), code=e.code), e.code - 对
Exception类型,建议记录日志(app.logger.exception("Uncaught exception")),再返回通用 500 JSON,避免泄露堆栈
@app.errorhandler(404)
def not_found(e):
return jsonify(error="Resource not found"), 404
@app.errorhandler(500)
def internal_error(e):
return jsonify(error="Internal server error"), 500
@app.errorhandler(Exception)
def unhandled_exception(e):
app.logger.exception("Unhandled Exception")
return jsonify(error="Something went wrong"), 500
为什么开发时能看到详细错误页,上线后却变空白或 HTML?
因为 app.debug = True 时,Flask 会禁用所有自定义 errorhandler,直接渲染调试页面;上线必须关掉 debug,否则你的 JSON handler 完全不生效。
-
app.config["DEBUG"] = False和app.run(debug=False)都要确认,光改 config 不够,run 参数优先级更高 - 用 gunicorn/uwsgi 部署时,确保没在启动命令里加
--reload或debug=True参数 - 检查是否启用了
PROPAGATE_EXCEPTIONS = True—— 它会让异常冒泡出 Flask,跳过 handler,务必设为False(默认就是 False,但显式设更稳妥)
JSON 错误响应里要不要带 traceback?
绝对不要在生产环境返回 traceback。它暴露代码路径、依赖版本、变量名,是典型安全风险。开发阶段可用 app.config["TRAP_HTTP_EXCEPTIONS"] = True 配合调试器看详情,但上线前必须关掉。
- 如果真需要排查,把完整异常写进日志(用
app.logger.error(..., exc_info=True)),而不是塞进响应体 - 客户端只需要知道 “发生了什么” 和 “该怎么做”,比如
{"error": "invalid_token", "message": "Token expired or malformed"},不是 Python 的KeyError: 'user_id' - 别为了“方便前端”返回
__cause__或__traceback__属性——这些字段名本身就在泄漏运行时细节
最麻烦的其实是中间件或扩展(比如 Flask-SQLAlchemy、Flask-JWT-Extended)抛出的异常,它们未必走标准 handler 流程。遇到漏捕获的情况,先查对应扩展文档看是否提供了自己的 error handler 钩子,而不是硬塞进全局 Exception。










