处理应用程序错误¶
应用程序失败,服务器失败。迟早你会在生产中看到一个例外。即使您的代码是100%正确的,您仍然会不时看到异常。为什么?因为其他一切都会失败。以下是一些完美的代码可能导致服务器错误的情况:
客户端提前终止了请求,应用程序仍在读取传入数据
数据库服务器超载,无法处理查询
文件系统已满
硬盘坏了
后台服务器过载
正在使用的库出现程序错误
服务器与另一个系统的网络连接失败
这只是你可能面临的一小部分问题。那么我们如何处理这种问题呢?默认情况下,如果应用程序在生产模式下运行,并且出现异常,Flask将为您显示一个非常简单的页面,并将异常记录到 logger
.
但是你可以做的更多,我们将介绍一些更好的设置来处理错误,包括自定义异常和第三方工具。
错误日志记录工具¶
如果有足够多的用户点击了错误,并且日志文件通常从未被查看过,那么发送错误邮件(即使只是针对关键邮件)可能会变得非常困难。这就是为什么我们建议使用 Sentry 用于处理应用程序错误。它可以作为源代码可用的项目使用 on GitHub 也可作为 hosted version 你可以免费试用。Sentry聚合重复的错误,捕获完整的堆栈跟踪和用于调试的本地变量,并根据新的错误或频率阈值向您发送邮件。
要使用岗哨,您需要安装 sentry-sdk
额外客户 flask
依赖关系。
$ pip install sentry-sdk[flask]
然后将此添加到你的烧瓶应用程序:
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init('YOUR_DSN_HERE', integrations=[FlaskIntegration()])
这个 YOUR_DSN_HERE
值需要替换为从Sentry安装中获得的DSN值。
安装后,导致内部服务器错误的故障会自动报告给Sentry,从那里你可以收到错误通知。
参见:
Sentry还支持以类似的方式从工作队列(RQ、芹菜等)捕获错误。见 Python SDK docs 更多信息。
错误处理程序¶
当烧瓶中发生错误时,适当的 HTTP status code 将被退回。400-499表示客户的请求数据错误,或关于请求的数据错误。500-599表示服务器或应用程序本身有错误。
发生错误时,你可能希望向用户显示自定义错误页。注册错误处理器可以做到这点。
错误处理程序是在引发错误类型时返回响应的函数,类似于视图是在请求URL匹配时返回响应的函数。它传递了正在处理的错误的实例,这很可能是 HTTPException
.
响应的状态代码不会设置为处理程序的代码。确保在从处理程序返回响应时提供适当的HTTP状态代码。
注册¶
通过用修饰函数来注册处理程序 errorhandler()
. 或使用 register_error_handler()
稍后注册函数。记住在返回响应时设置错误代码。
@app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e):
return 'bad request!', 400
# or, without the decorator
app.register_error_handler(400, handle_bad_request)
当注册时,werkzeug.exceptions.HTTPException
的子类,如 BadRequest
,它们的HTTP代码在注册处理程序时是可互换的。( BadRequest.code == 400
)
非标准HTTP代码不能通过代码注册,因为Werkzeug不知道这些代码。相反,定义 HTTPException
使用适当的代码注册并引发异常类。
class InsufficientStorage(werkzeug.exceptions.HTTPException):
code = 507
description = 'Not enough storage space.'
app.register_error_handler(InsufficientStorage, handle_507)
raise InsufficientStorage()
除了HTTPException 子类或者 HTTP 状态码,出错处理器可被用于任何异常类的注册。出错处理器可被用于特定类的注册,也可用于一个父类的所有子类的注册。
处理¶
在构建烧瓶应用程序时 will 遇到异常。如果在处理请求时代码的某些部分中断(并且您没有注册错误处理程序),则会出现“500内部服务器错误” (InternalServerError
)将默认返回。同样,“404未找到” (NotFound
)如果将请求发送到未注册的路由,将发生错误。如果路由接收到不允许的请求方法,“405方法不允许” (MethodNotAllowed
)将被提高。这些都是 HTTPException
默认在烧瓶中提供。
FlASK使您能够引发Werkzeug注册的任何HTTP异常。但是,默认的HTTP异常会返回简单的异常页面。您可能希望在发生错误时向用户显示自定义错误页。这可以通过注册错误处理程序来完成。
当Flask在处理请求时捕捉到异常时,首先通过代码查找异常。如果没有为代码注册处理程序,Flask将按其类层次结构查找错误;选择最具体的处理程序。如果没有注册处理程序, HTTPException
子类显示有关其代码的一般消息,而其他异常则转换为一般的“500内部服务器错误”。
例如,如果一个 ConnectionRefusedError 的实例被抛出,并且一个出错处理器注册到 ConnectionError 和 ConnectionRefusedError,那么会使用更合适的 ConnectionRefusedError 来处理异常实例,生成响应。
当一个蓝图在处理抛出异常的请求时,在蓝图中注册的出错处理器优先于在应用中全局注册的出错处理器。但是,蓝图无法处理404路由错误,因为404发生在路由级别,无法检测到蓝图。
通用异常处理程序¶
可以为非常通用的基类注册错误处理程序,例如 HTTPException
甚至 Exception
. 但是,要知道这些会比你预期的要多。
例如,的错误处理程序 HTTPException
对于将默认的HTML错误页面转换为JSON可能很有用。但是,这个处理程序将触发您不会直接导致的事情,例如路由过程中的404和405错误。一定要仔细设计处理程序,这样就不会丢失有关HTTP错误的信息。
from flask import json
from werkzeug.exceptions import HTTPException
@app.errorhandler(HTTPException)
def handle_exception(e):
"""Return JSON instead of HTML for HTTP errors."""
# start with the correct headers and status code from the error
response = e.get_response()
# replace the body with JSON
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response
的错误处理程序 Exception
对于改变所有错误(即使是未处理的错误)呈现给用户的方式似乎很有用。但是,这与 except Exception:
在Python中,它将捕获 all 否则未处理的错误,包括所有HTTP状态代码。
在大多数情况下,为更具体的异常注册处理程序会更安全。因为 HTTPException
实例是有效的WSGI响应,您也可以直接传递它们。
from werkzeug.exceptions import HTTPException
@app.errorhandler(Exception)
def handle_exception(e):
# pass through HTTP errors
if isinstance(e, HTTPException):
return e
# now you're handling non-HTTP exceptions only
return render_template("500_generic.html", e=e), 500
错误处理程序仍然尊重异常类层次结构。如果同时为两个处理程序注册处理程序 HTTPException
和 Exception
vt.的. Exception
处理程序将不会处理 HTTPException
子类,因为 HTTPException
处理程序更具体。
未处理的异常¶
当没有为异常注册错误处理程序时,将返回500个内部服务器错误。看到了吗 flask.Flask.handle_exception()
有关此行为的信息。
如果有注册的错误处理程序 InternalServerError
,这将被调用。从Flask1.1.0开始,此错误处理程序将始终传递 InternalServerError
,而不是原始的未处理错误。
原始错误可通过以下方式获得 e.original_exception
。
除了显式的500个错误之外,“500个内部服务器错误”的错误处理程序将传递未捕获的异常。在调试模式下,将不使用“500内部服务器错误”的处理程序。相反,将显示交互式调试器。
自定义错误页¶
有时在构建一个Flask应用程序时,您可能需要 HTTPException
向用户发出请求有问题的信号。幸运的是,烧瓶带来了方便 abort()
函数,该函数根据需要从werkzeug中断一个HTTP错误请求。它还将为您提供一个简单的黑白错误页面,并提供基本的描述,但没有什么特别的。
根据错误代码的不同,用户实际看到此类错误的可能性越来越小。
考虑下面的代码,我们可能有一个用户配置文件路由,如果用户未能传递用户名,我们可以发出“400个错误请求”。如果用户传递了一个用户名而我们找不到它,我们将发出一个“404notfound”。
from flask import abort, render_template, request
# a username needs to be supplied in the query args
# a successful request would be like /profile?username=jack
@app.route("/profile")
def user_profile():
username = request.arg.get("username")
# if a username isn't supplied in the request, return a 400 bad request
if username is None:
abort(400)
user = get_user(username=username)
# if a user can't be found by their username, return 404 not found
if user is None:
abort(404)
return render_template("profile.html", user=user)
下面是“404页未找到”异常的另一个实现示例:
from flask import render_template
@app.errorhandler(404)
def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('404.html'), 404
使用时 应用工厂 :
from flask import Flask, render_template
def page_not_found(e):
return render_template('404.html'), 404
def create_app(config_filename):
app = Flask(__name__)
app.register_error_handler(404, page_not_found)
return app
示例模板可能是:
{% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %}
{% block body %}
<h1>Page Not Found</h1>
<p>What you were looking for is just not there.
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
{% endblock %}
其他示例¶
上面的例子实际上并不是对默认异常页面的改进。我们可以创建一个自定义的500.html模板,如下所示:
{% extends "layout.html" %}
{% block title %}Internal Server Error{% endblock %}
{% block body %}
<h1>Internal Server Error</h1>
<p>Oops... we seem to have made a mistake, sorry!</p>
<p><a href="{{ url_for('index') }}">Go somewhere nice instead</a>
{% endblock %}
可以通过在“500 Internal Server Error”上呈现模板来实现:
from flask import render_template
@app.errorhandler(500)
def internal_server_error(e):
# note that we set the 500 status explicitly
return render_template('500.html'), 500
使用时 应用工厂 :
from flask import Flask, render_template
def internal_server_error(e):
return render_template('500.html'), 500
def create_app():
app = Flask(__name__)
app.register_error_handler(500, internal_server_error)
return app
使用时 使有蓝图的模块化应用程序 :
from flask import Blueprint
blog = Blueprint('blog', __name__)
# as a decorator
@blog.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
# or with register_error_handler
blog.register_error_handler(500, internal_server_error)
蓝图错误处理程序¶
在 使有蓝图的模块化应用程序 ,大多数错误处理程序将按预期工作。但是,关于404和405异常的处理程序有一个警告。这些错误处理程序只能从适当的 raise
声明或呼叫 abort
在blueprint的另一个视图函数中;它们不是由无效的URL访问调用的。
这是因为blueprint不“拥有”某个URL空间,因此应用程序实例无法知道如果给定的URL无效,它应该运行哪个blueprint错误处理程序。如果您想基于URL前缀对这些错误执行不同的处理策略,可以使用 request
代理对象。
from flask import jsonify, render_template
# at the application level
# not the blueprint level
@app.errorhandler(404)
def page_not_found(e):
# if a request is in our blog URL space
if request.path.startswith('/blog/'):
# we return a custom blog 404 page
return render_template("blog/404.html"), 404
else:
# otherwise we return our generic site-wide 404 page
return render_template("404.html"), 404
@app.errorhandler(405)
def method_not_allowed(e):
# if a request has the wrong method to our API
if request.path.startswith('/api/'):
# we return a json saying so
return jsonify(message="Method Not Allowed"), 405
else:
# otherwise we return a generic site-wide 405 page
return render_template("405.html"), 405
以JSON形式返回API错误¶
在Flask中构建api时,一些开发人员意识到内置异常对于api来说不够表达,并且 text/html 它们发出对API使用者不是很有用。
使用与上述相同的技术和 jsonify()
我们可以返回JSON对API错误的响应。 abort()
是用 description
参数。错误处理程序将使用它作为JSON错误消息,并将状态代码设置为404。
from flask import abort, jsonify
@app.errorhandler(404)
def resource_not_found(e):
return jsonify(error=str(e)), 404
@app.route("/cheese")
def get_one_cheese():
resource = get_resource()
if resource is None:
abort(404, description="Resource not found")
return jsonify(resource)
我们还可以创建自定义异常类。例如,我们可以为一个API引入一个新的自定义异常,该异常可以接受一个正确的人类可读的消息、一个错误的状态代码和一些可选的有效负载,以便为错误提供更多的上下文。
这是一个简单的例子:
from flask import jsonify, request
class InvalidAPIUsage(Exception):
status_code = 400
def __init__(self, message, status_code=None, payload=None):
super().__init__()
self.message = message
if status_code is not None:
self.status_code = status_code
self.payload = payload
def to_dict(self):
rv = dict(self.payload or ())
rv['message'] = self.message
return rv
@app.errorhandler(InvalidAPIUsage)
def invalid_api_usage(e):
return jsonify(e.to_dict()), e.status_code
# an API app route for getting user information
# a correct request might be /api/user?user_id=420
@app.route("/api/user")
def user_api(user_id):
user_id = request.arg.get("user_id")
if not user_id:
raise InvalidAPIUsage("No user id provided!")
user = get_user(user_id=user_id)
if not user:
raise InvalidAPIUsage("No such user!", status_code=404)
return jsonify(user.to_dict())
视图现在可以用错误消息引发该异常。另外,可以通过 payload 参数。
日志¶
见 日志 有关如何记录异常的信息,例如通过电子邮件将异常发送给管理员。
调试¶
见 调试应用程序错误 有关如何调试开发和生产中的错误的信息。