Flask 错误处理模式指南
Flask 生产级错误处理:错误分类、errorhandler 统一处理、abort 与自定义错误页、异步代码中的异常与错误日志集中管理。
直接回答:Flask 应用迟早要在生产环境出错——网络抖动、数据库过载、硬件故障都不是代码能控制的。正确的姿势是:区分错误类型、用 errorhandler 统一接管、abort 主动抛出语义化错误、异步代码单独小心、错误日志结构化并集中采集。
三类错误
- 运营性错误:请求不存在的资源、参数非法——预期内,优雅提示;
- 代码缺陷:空引用、类型错误——必须告警修复;
- 外部故障:数据库超时、第三方 API 异常——重试、熔断、降级。
errorhandler:统一接管
from flask import Flask, jsonify
app = Flask(__name__)
@app.errorhandler(404)
def not_found(e):
return jsonify(error="资源不存在"), 404
@app.errorhandler(500)
def server_error(e):
app.logger.exception("未捕获异常")
return jsonify(error="服务器内部错误"), 500
按状态码注册是最常用形态;也可以按异常类注册(@app.errorhandler(ValueError)),业务异常与 HTTP 响应的映射就此集中管理。API 项目建议所有错误处理器统一返回 JSON 结构,前端永远拿得到一致格式。
abort 与自定义错误页
from flask import abort, render_template
@app.get("/admin")
def admin():
if not current_user.is_admin:
abort(403) # 交给 403 处理器
...
渲染型应用给 403/404/500 各做一个模板页面,错误处理器里 render_template("errors/404.html")——错误页也是产品体验的一部分。
异步代码的坑
Flask 2.0 起安装 'flask[async]' 后支持 async 视图,但两个坑常见:
- 异步视图里调用同步阻塞库(requests、未异步化的 ORM)会卡住事件循环——用 httpx 或
asyncio.to_thread; - 标准 WSGI 部署中视图完成后,其事件循环停止,未完成的 create_task 任务会被取消。后台工作应使用任务队列,而不只是给 create_task 加 try/except。
错误日志
import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler("app.log", maxBytes=10_000_000, backupCount=3)
handler.setLevel(logging.WARNING)
app.logger.addHandler(handler)
# 视图里
app.logger.warning("支付回调验签失败: %s", order_id)
约定:4xx 记 WARNING(不用堆栈),5xx 记 ERROR/exception(带完整堆栈),请求上下文(路径、用户、request_id)一并带上。
日志集中化
多副本部署下,先汇集各实例的结构化日志,再按 request_id 查找同一请求的记录。使用观测云采集时,按 DataKit 日志采集指南核对采集范围,并用一次受控的 500 错误检查堆栈是否完整、请求标识是否能检索到。后台任务另记任务 ID,避免把请求已返回误当成任务已完成。
常见问题(FAQ)
Q:errorhandler 捕获不到某些异常?
A:检查是否注册在了 Blueprint 上(蓝图处理器只管蓝图内路由);路由阶段的 404 在确定所属 Blueprint 前发生,不能由它处理;异常匹配按状态码及类层次选择具体处理器,并非 2.2 才有的规则。
Q:生产环境想把堆栈发给前端调试可以吗?
A:绝不可以。堆栈暴露代码结构与依赖版本,是攻击者的情报库。对外统一模糊文案 + request_id,细节只进日志。
Q:Sentry 之类的错误追踪还要不要日志?
A:要。错误追踪聚合异常事件,日志补充异常前后的业务记录;两者通过一致的请求或任务标识配合使用。
官方参考
本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。