必须显式调用app.register_error_handler(500, handler)捕获全局500错误,它比@app.errorhandler(500)更可靠,尤其在蓝本场景下;handler需接收exception参数、返回合法响应,并在app.run()前注册,关闭DEBUG模式方可生效。

用 register_error_handler 捕获全局 500 错误
Flask 不会自动把未捕获异常转成可监听的错误响应,必须显式注册处理器。直接在应用实例上调用 register_error_handler(500, handler) 是最可靠的方式,比装饰器 @app.errorhandler(500) 更稳妥——后者在蓝本(Blueprint)场景下可能失效,尤其当异常发生在蓝本路由但 handler 定义在主 app 外时。
常见错误是只在开发环境加 debug=True,结果线上 500 完全静默;或者误以为 before_request 能拦截所有异常(它不能,异常发生时请求上下文可能已损坏)。
- handler 函数必须接收一个参数(
exception实例),返回值应为响应对象或元组 - 确保 handler 在
app.run()或 WSGI 启动前完成注册,否则无效 - 若使用 Gunicorn/uWSGI,需确认日志输出未被重定向吞掉,否则告警逻辑可能执行了但你看不见
在 handler 里提取真实异常信息而不是只看 status code
500 响应本身不带原始异常堆栈,exception 参数才是关键。Flask 会把未处理异常作为参数传入 handler,但前提是该异常确实“逃逸”到了路由函数之外——比如视图里 raise ValueError("boom") 就会进来,而 try/except 后吞掉再 return "error" 就不会触发。
容易忽略的是:某些中间件(如 Flask-Login 的 @login_required)抛出的 401 或 403 异常,如果没被对应 handler 捕获,也可能因内部 fallback 导致变成 500,这时 exception 类型就不是你预期的业务异常。
立即学习“Python免费学习笔记(深入)”;
- 用
traceback.format_exception(type(exception), exception, exception.__traceback__)获取完整堆栈 - 避免直接打
str(exception),它常为空或无意义(比如InternalServerError()实例) - 检查
hasattr(exception, '__cause__')和__context__,Python 3.8+ 的异常链可能藏有根因
发告警时别阻塞主线程,也别丢掉上下文
同步调用邮件、HTTP webhook 或 Slack bot 会拖慢响应,甚至引发级联超时。更糟的是,如果告警服务不可用,又没设 timeout 和 fallback,整个请求会卡死。
典型翻车点:在 handler 里写 requests.post(..., timeout=5) 却没包 try/except requests.RequestException,导致二次异常又进同一 handler,无限递归崩溃。
- 用线程(
threading.Thread(target=send_alert, args=(...)).start())或异步任务(Celery / asyncio.to_thread)解耦 - 至少带上
request.url、request.method、request.headers.get("User-Agent")和request.remote_addr - 记录
request.environ.get("wsgi.errors")或当前 logger 的 name,方便定位是哪个 worker 进程出的问题
本地调试时怎么验证 handler 真的生效?
别靠制造真实错误(比如除零)来测——有些 WSGI 服务器(如 Werkzeug 开发服务器)在 debug 模式下会拦截并渲染调试页,根本不会走到你的 register_error_handler。
正确做法是临时在某个路由里主动抛出异常,并关闭 debug:
@app.route("/test-500")
def trigger_500():
raise RuntimeError("test unhandled exception")
然后启动时指定 app.run(debug=False),再 curl /test-500。如果看到自定义响应(比如 JSON {"error": "internal"}),说明 handler 已接管;如果看到 Werkzeug 黄页,说明 debug 还开着或 handler 注册时机错了。
生产环境真正难搞的是异步视图(async def)和 WebSocket 路由——它们的异常传播路径不同,Flask 2.3+ 才开始稳定支持,旧版本需额外包装或改用 Quart。


















