Flask 错误处理
Flask 提供了灵活的错误处理机制,可以捕获并处理应用中的各种错误。
优秀的错误处理不只是「不出 Bug」,更是当 Bug 发生时,给用户一个友好的体验。
本章介绍 Flask 中如何自定义错误页面和管理日志。
自定义错误页面
默认情况下,Flask 对 404 等错误会显示简单的黑底白字页面。
使用 @app.errorhandler 装饰器可以为特定 HTTP 状态码定制错误页面:
实例
from flask import Flask, render_template
app = Flask(__name__)
# 处理 404 错误——页面未找到
@app.errorhandler(404)
def page_not_found(error):
# 注意:必须显式返回状态码 404
return render_template("404.html"), 404
# 处理 500 错误——服务器内部错误
@app.errorhandler(500)
def internal_error(error):
return render_template("500.html"), 500
# 处理 403 错误——禁止访问
@app.errorhandler(403)
def forbidden(error):
return render_template("403.html"), 403
app = Flask(__name__)
# 处理 404 错误——页面未找到
@app.errorhandler(404)
def page_not_found(error):
# 注意:必须显式返回状态码 404
return render_template("404.html"), 404
# 处理 500 错误——服务器内部错误
@app.errorhandler(500)
def internal_error(error):
return render_template("500.html"), 500
# 处理 403 错误——禁止访问
@app.errorhandler(403)
def forbidden(error):
return render_template("403.html"), 403
对应的 404 模板:
实例
<!-- 文件路径:templates/404.html -->
<!DOCTYPE html>
<html>
<head>
<title>404 - 页面不存在</title>
<style>
body { text-align: center; padding-top: 60px; font-family: Arial; }
h1 { font-size: 72px; color: #e74c3c; margin: 0; }
p { color: #666; font-size: 18px; }
a { color: #3498db; }
</style>
</head>
<body>
<h1>404</h1>
<p>抱歉,你访问的页面不存在。</p>
<p><a href="/">返回首页</a></p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>404 - 页面不存在</title>
<style>
body { text-align: center; padding-top: 60px; font-family: Arial; }
h1 { font-size: 72px; color: #e74c3c; margin: 0; }
p { color: #666; font-size: 18px; }
a { color: #3498db; }
</style>
</head>
<body>
<h1>404</h1>
<p>抱歉,你访问的页面不存在。</p>
<p><a href="/">返回首页</a></p>
</body>
</html>
在 errorhandler 装饰的函数中,return 必须显式带上状态码。如果不写 , 404,Flask 会默认返回 200 状态码,浏览器和搜索引擎不会认为这是一个错误页面。
异常类注册
除了状态码,@app.errorhandler 还可以直接注册异常类:
实例
from werkzeug.exceptions import HTTPException
@app.errorhandler(HTTPException)
def handle_http_exception(error):
"""统一处理所有 HTTP 异常(如 400, 401, 403, 404 等)"""
return f"""
<h1>HTTP 错误 {error.code}</h1>
<p>{error.description}</p>
<p><a href="/">返回 RUNOOB 首页</a></p>
""", error.code
@app.errorhandler(HTTPException)
def handle_http_exception(error):
"""统一处理所有 HTTP 异常(如 400, 401, 403, 404 等)"""
return f"""
<h1>HTTP 错误 {error.code}</h1>
<p>{error.description}</p>
<p><a href="/">返回 RUNOOB 首页</a></p>
""", error.code
使用 abort 触发错误
在你的视图函数中,使用 abort() 主动触发 HTTP 错误:
实例
from flask import abort
# 模拟一个文章数据库
articles = {
1: {"title": "Flask 入门教程"},
2: {"title": "Python 基础"},
}
@app.get("/article/<int:article_id>")
def view_article(article_id):
article = articles.get(article_id)
if article is None:
# 文章不存在,返回 404
# description 参数会被传递给 errorhandler
abort(404, description=f"文章 ID {article_id} 不存在")
return f"<h1>{article['title']}</h1>"
@app.get("/admin")
def admin_panel():
# 没有 admin 权限的用户访问管理后台
abort(403, description="你没有管理员权限")
# 模拟一个文章数据库
articles = {
1: {"title": "Flask 入门教程"},
2: {"title": "Python 基础"},
}
@app.get("/article/<int:article_id>")
def view_article(article_id):
article = articles.get(article_id)
if article is None:
# 文章不存在,返回 404
# description 参数会被传递给 errorhandler
abort(404, description=f"文章 ID {article_id} 不存在")
return f"<h1>{article['title']}</h1>"
@app.get("/admin")
def admin_panel():
# 没有 admin 权限的用户访问管理后台
abort(403, description="你没有管理员权限")
日志记录
Flask 使用 Python 标准库的 logging 模块,通过 app.logger 即可记录日志。
日志对于排查问题至关重要——不要只用 print(),日志提供了更丰富的上下文和更好的控制力。
实例
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
# 不同级别的日志
app.logger.debug("访问首页") # 调试信息,生产环境通常不输出
app.logger.info("用户来自 IP: %s", request.remote_addr) # 常规信息
app.logger.warning("检测到异常访问模式") # 警告:需要注意但不影响运行
app.logger.error("数据库连接失败") # 错误:功能受影响
app.logger.critical("磁盘空间不足,服务即将中止") # 严重错误:服务可能停止
return "OK"
app = Flask(__name__)
@app.route("/")
def index():
# 不同级别的日志
app.logger.debug("访问首页") # 调试信息,生产环境通常不输出
app.logger.info("用户来自 IP: %s", request.remote_addr) # 常规信息
app.logger.warning("检测到异常访问模式") # 警告:需要注意但不影响运行
app.logger.error("数据库连接失败") # 错误:功能受影响
app.logger.critical("磁盘空间不足,服务即将中止") # 严重错误:服务可能停止
return "OK"
日志级别(由低到高):
| 级别 | 使用场景 |
|---|---|
| DEBUG | 开发调试时的详细信息,生产环境默认不输出 |
| INFO | 一般的运行信息,如请求记录、服务启动 |
| WARNING | 警告信息,潜在问题但不影响当前运行 |
| ERROR | 错误信息,某个功能出错但服务还在运行 |
| CRITICAL | 严重错误,可能导致服务停止 |
在 Debug 模式下,Flask 的日志级别自动设为 DEBUG。生产环境中应按需设置为 INFO 或 WARNING,避免输出太多无用信息。
配置日志输出
将日志写入文件,便于事后分析和排查:
实例
import logging
from logging.handlers import RotatingFileHandler
# 配置文件日志处理器——日志文件达到 10MB 后自动轮转
handler = RotatingFileHandler(
"runoob_app.log", # 日志文件路径
maxBytes=10 * 1024 * 1024, # 单个文件最大 10MB
backupCount=5 # 保留最近 5 个备份
)
# 设置日志格式
handler.setFormatter(logging.Formatter(
"[%(asctime)s] %(levelname)s in %(module)s: %(message)s"
))
# 将 handler 添加到 Flask 的 logger
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
from logging.handlers import RotatingFileHandler
# 配置文件日志处理器——日志文件达到 10MB 后自动轮转
handler = RotatingFileHandler(
"runoob_app.log", # 日志文件路径
maxBytes=10 * 1024 * 1024, # 单个文件最大 10MB
backupCount=5 # 保留最近 5 个备份
)
# 设置日志格式
handler.setFormatter(logging.Formatter(
"[%(asctime)s] %(levelname)s in %(module)s: %(message)s"
))
# 将 handler 添加到 Flask 的 logger
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
捕获未处理的异常
使用 app.errorhandler(500) 可以捕获未处理的异常并记录日志:
实例
import traceback
@app.errorhandler(500)
def internal_error(error):
# 记录完整的异常堆栈
app.logger.error("服务器内部错误:\n%s", traceback.format_exc())
# 向用户显示友好的错误页面
return """
<h1>500 - 服务器内部错误</h1>
<p>抱歉,服务器遇到了一个意外错误。我们已记录该问题,请稍后再试。</p>
<p>如果持续出现此问题,请联系 RUNOOB 技术支持。</p>
""", 500
@app.errorhandler(500)
def internal_error(error):
# 记录完整的异常堆栈
app.logger.error("服务器内部错误:\n%s", traceback.format_exc())
# 向用户显示友好的错误页面
return """
<h1>500 - 服务器内部错误</h1>
<p>抱歉,服务器遇到了一个意外错误。我们已记录该问题,请稍后再试。</p>
<p>如果持续出现此问题,请联系 RUNOOB 技术支持。</p>
""", 500
常见 HTTP 错误码速查
| 状态码 | 含义 | 常见原因 |
|---|---|---|
| 400 | Bad Request | 请求参数格式错误、缺少必要字段 |
| 401 | Unauthorized | 用户未登录或认证失败 |
| 403 | Forbidden | 用户已登录但没有访问权限 |
| 404 | Not Found | 资源(文章、用户等)不存在 |
| 405 | Method Not Allowed | 使用了错误的 HTTP 方法(如用 GET 访问 POST 接口) |
| 500 | Internal Server Error | 代码异常、数据库连接失败等未处理的错误 |
