应用程序错误 — Flask 文档

来自菜鸟教程
Flask/docs/1.1.x/errorhandling
跳转至:导航、​搜索

应用程序错误

0.3 版中的新功能。


应用程序失败,服务器失败。 迟早你会在生产中看到异常。 即使您的代码是 100% c 正确,您仍然会不时看到异常。 为什么? 因为其他涉及的一切都会失败。 以下是一些完美的代码可能导致服务器错误的情况:

  • 客户端提前终止请求,应用程序仍在读取传入的数据
  • 数据库服务器过载,无法处理查询
  • 文件系统已满
  • 硬盘坏了
  • 后端服务器过载
  • 您正在使用的库中的编程错误
  • 服务器到另一个系统的网络连接失败

这只是您可能面临的一小部分问题。 那么我们如何处理这样的问题呢? 默认情况下,如果您的应用程序在生产模式下运行,Flask 将为您显示一个非常简单的页面,并将异常记录到 logger

但是你可以做更多的事情,我们将介绍一些更好的设置来处理错误。

错误记录工具

发送错误邮件,即使只是针对关键邮件,如果有足够多的用户遇到错误并且通常从不查看日志文件,也会变得不堪重负。 这就是我们推荐使用 Sentry 来处理应用程序错误的原因。 它可以作为开源项目 在 GitHub 上使用,也可以作为 托管版本 使用,您可以免费试用。 Sentry 聚合重复错误,捕获完整的堆栈跟踪和局部变量以进行调试,并根据新错误或频率阈值向您发送邮件。

要使用 Sentry,您需要安装带有额外 flask 依赖项的 sentry-sdk 客户端:

$ pip install sentry-sdk[flask]

然后将其添加到您的 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,您可以从那里收到错误通知。

后续内容如下:


错误处理程序

您可能希望在发生错误时向用户显示自定义错误页面。 这可以通过注册错误处理程序来完成。

错误处理程序是一个返回响应的普通视图函数,但它不是为路由注册,而是为尝试处理请求时将引发的异常或 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 状态代码。 可以为特定类或父类的所有子类注册处理程序。


处理

当 Flask 在处理请求时捕获异常时,首先通过代码查找异常。 如果没有为代码注册处理程序,则通过其类层次结构查找; 选择最具体的处理程序。 如果未注册处理程序,HTTPException 子类会显示有关其代码的通用消息,而其他异常将转换为通用 500 内部服务器错误。

例如,如果引发了 ConnectionRefusedError 的实例,并且为 ConnectionErrorConnectionRefusedError 注册了一个处理程序,则使用以下命令调用更具体的 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 的错误处理程序对于更改所有错误(甚至是未处理的错误)呈现给用户的方式似乎很有用。 但是,这类似于在 Python 中执行 except Exception:,它将捕获 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

错误处理程序仍然尊重异常类层次结构。 如果同时为 HTTPExceptionException 注册处理程序,Exception 处理程序将不会处理 HTTPException 子类,因为 HTTPException 处理程序更多具体的。


未处理的异常

当没有为异常注册错误处理程序时,将返回 500 Internal Server Error。 有关此行为的信息,请参阅 flask.Flask.handle_exception()

如果为 InternalServerError 注册了错误处理程序,则将调用该处理程序。 从 Flask 1.1.0 开始,此错误处理程序将始终传递一个 InternalServerError 实例,而不是原始未处理的错误。 原始错误可用作 e.original_exception。 在 Werkzeug 1.0.0 之前,这个属性只会在未处理的错误期间存在,为了兼容性,使用 getattr 来访问它。

@app.errorhandler(InternalServerError)
def handle_500(e):
    original = getattr(e, "original_exception", None)

    if original is None:
        # direct 500 error, such as abort(500)
        return render_template("500.html"), 500

    # wrapped unhandled error
    return render_template("500_unhandled.html", e=original), 500

日志记录

请参阅 Logging 以获取有关如何记录异常的信息,例如通过电子邮件将异常发送给管理员。


调试应用程序错误

对于生产应用程序,如 应用程序错误 中所述,使用日志记录和通知配置您的应用程序。 本节提供调试部署配置和使用全功能 Python 调试器深入挖掘时的指针。

如有疑问,请手动运行

在为生产配置应用程序时遇到问题? 如果您拥有对主机的 shell 访问权限,请验证您是否可以从部署环境中的 shell 手动运行您的应用程序。 确保在与配置的部署相同的用户帐户下运行以解决权限问题。 您可以在生产主机上使用 Flask 的内置开发服务器和 debug=True,这有助于捕捉配置问题,但 请务必在受控环境中临时执行此操作。 不要使用 debug=True 在生产环境中运行。


使用调试器

为了更深入地挖掘,可能是为了跟踪代码执行,Flask 提供了一个开箱即用的调试器(参见 调试模式 )。 如果您想使用另一个 Python 调试器,请注意调试器会相互干扰。 您必须设置一些选项才能使用您最喜欢的调试器:

  • debug - 是否启用调试模式并捕获异常
  • use_debugger - 是否使用内部 Flask 调试器
  • use_reloader - 如果模块被更改,是否重新加载和分叉进程

debug 必须为 True(即必须捕获异常)才能使其他两个选项具有任何值。

如果您使用 Aptana/Eclipse 进行调试,您需要将 use_debuggeruse_reloader 都设置为 False。

一个可能有用的配置模式是在您的 config.yaml 中设置以下内容(当然,根据您的应用程序更改块):

FLASK:
    DEBUG: True
    DEBUG_WITH_APTANA: True

然后在您的应用程序的入口点 (main.py) 中,您可以拥有如下内容:

if __name__ == "__main__":
    # To allow aptana to receive errors, set use_debugger=False
    app = create_app(config="config.yaml")

    use_debugger = app.debug and not(app.config.get('DEBUG_WITH_APTANA'))
    app.run(use_debugger=use_debugger, debug=app.debug,
            use_reloader=use_debugger, host='0.0.0.0')