如何处理Flask应用程序中的错误

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。

介绍

Flask 是一个轻量级的 Python Web 框架,它为使用 Python 语言创建 Web 应用程序提供了有用的工具和功能。

当您开发 Web 应用程序时,您将不可避免地遇到应用程序行为与预期相反的情况。 您可能会拼错变量,滥用 for 循环,或者以引发 Python 异常的方式构造 if 语句,例如在声明函数之前调用函数,或者只是寻找一个页面不存在。 如果您学习了如何正确处理错误和异常,您会发现开发 Flask 应用程序会更容易、更顺畅。

在本教程中,您将构建一个小型 Web 应用程序,演示如何处理开发 Web 应用程序时遇到的常见错误。 您将创建自定义错误页面,使用 Flask 调试器对异常进行故障排除,并使用日志记录来跟踪应用程序中的事件。

先决条件

第 1 步——使用 Flask 调试器

在此步骤中,您将创建一个有一些错误的应用程序,并在没有调试模式的情况下运行它,以查看应用程序如何响应。 然后,您将在调试模式下运行它并使用调试器对应用程序错误进行故障排除。

激活编程环境并安装 Flask 后,在 flask_app 目录中打开一个名为 app.py 的文件进行编辑:

nano app.py

app.py 文件中添加以下代码:

烧瓶应用程序/app.py

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')

在上面的代码中,首先从 flask 包中导入 Flask 类。 然后创建一个名为 app 的 Flask 应用程序实例。 您使用 @app.route() 装饰器创建一个名为 index() 的视图函数,它调用 render_template() 函数作为返回值,进而渲染一个名为 index.html 的模板]。 这段代码有两个错误:第一个是你没有导入render_template()函数,第二个是index.html模板文件不存在。

保存并关闭文件。

接下来,使用以下命令通知 Flask 使用 FLASK_APP 环境变量的应用程序(在 Windows 上,使用 set 而不是 export):

export FLASK_APP=app

然后使用 flask run 命令运行应用服务器:

flask run

您将在终端中看到以下信息:

Output * Serving Flask app 'app' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

此输出提供以下信息:

现在,使用浏览器访问索引页面:

http://127.0.0.1:5000/

您将看到如下所示的消息:

OutputInternal Server Error

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

这是 500 Internal Server Error,这是一个服务器错误响应,表明服务器在应用程序代码中遇到了内部错误。

在终端中,您将看到以下输出:

Output[2021-09-12 15:16:56,441] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
  File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/home/abd/python/flask/series03/flask_app/app.py", line 8, in index
    return render_template('index.html')
NameError: name 'render_template' is not defined
127.0.0.1 - - [12/Sep/2021 15:16:56] "GET / HTTP/1.1" 500 -

上面的回溯通过触发内部服务器错误的代码。 NameError: name 'render_template' is not defined 行给出了问题的根本原因:render_template() 函数尚未导入。

在这里可以看到,要到终端排错,不方便。

通过在开发服务器中启用调试模式,您可以获得更好的故障排除体验。 为此,请使用 CTRL+C 停止服务器并将环境变量 FLASK_ENV 设置为 development,以便您可以在开发模式下运行应用程序(启用调试器),使用以下命令(在 Windows 上,使用 set 而不是 export):

export FLASK_ENV=development

运行开发服务器:

flask run

您将在终端中看到类似于以下内容的输出:

Output * Serving Flask app 'app' (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 120-484-907

在这里您可以看到环境现在为 development,调试模式已打开,并且调试器处于活动状态。 Debugger PIN 是您在浏览器中解锁控制台所需的 PIN(您可以通过单击下图中圈出的小终端图标来访问交互式 python shell)。

在浏览器上刷新索引页面,您将看到以下页面:

在这里,您会看到以更易于理解的方式显示的错误消息。 第一个标题为您提供导致问题的 Python 异常的名称(在本例中为 NameError)。 第二行给出了直接原因(render_template() 没有定义,这意味着在这种情况下它没有被导入)。 之后,您可以通过已执行的内部 Flask 代码进行回溯。 从底部向上读取回溯,因为回溯中的最后一行通常包含最有用的信息。

注意: 带圆圈的终端图标允许您在不同的框架上在浏览器中运行 Python 代码。 当您想像在 Python 交互式 shell 中那样检查变量的值时,这很有用。 单击终端图标时,您需要输入运行服务器时获得的调试器 PIN 码。 在本教程中,您将不需要这个交互式 shell。


要解决此 NameError 问题,请让服务器保持运行,打开一个新的终端窗口,激活您的环境,然后打开您的 app.py 文件:

nano app.py

修改文件如下所示:

烧瓶应用程序/app.py

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')

保存并关闭文件。

在这里您导入了缺少的 render_template() 函数。

在开发服务器运行的情况下,刷新浏览器上的索引页面。

这次您将看到一个错误页面,其中包含如下信息:

Outputjinja2.exceptions.TemplateNotFound
jinja2.exceptions.TemplateNotFound: index.html

此错误消息表明 index.html 模板不存在。

要解决此问题,您将创建一个 base.html 模板文件,其他模板将从中继承以避免代码重复,然后创建一个扩展基本模板的 index.html 模板。

创建 templates 目录,这是 Flask 查找模板文件的目录。 然后使用您喜欢的编辑器打开一个 base.html 文件:

mkdir templates
nano templates/base.html

将以下代码添加到您的 base.html 文件中:

flask_app/templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

保存并关闭文件。

此基本模板包含您需要在其他模板中重用的所有 HTML 样板。 title 块将被替换为每个页面设置标题,content 块将被替换为每个页面的内容。 导航栏有两个链接,一个用于索引页面,您使用 url_for() 辅助函数链接到 index() 视图函数,另一个用于“关于”页面(如果您选择包含一个)在您的应用程序中。

接下来,打开一个名为 index.html 的模板文件,它将继承自基本模板。

nano templates/index.html

向其中添加以下代码:

flask_app/templates/index.html

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Index {% endblock %}</h1>
    <h2>Welcome to FlaskApp!</h2>
{% endblock %}

保存并关闭文件。

在上面的代码中,您扩展了基本模板并覆盖了 content 块。 然后设置页面标题并使用 title 块将其显示在 H1 标题中,并在 H2 标题中显示问候语。

在开发服务器运行的情况下,刷新浏览器上的索引页面。

您将看到应用程序不再显示错误,并且索引页按预期显示。

您现在已经使用了调试模式并了解了如何处理错误消息。 接下来,您将中止使用您选择的错误消息进行响应的请求,并查看如何使用自定义错误页面进行响应。

第 2 步 - 创建自定义错误页面

在此步骤中,您将学习如何在用户请求服务器上不存在的数据时中止请求并使用 404 HTTP 错误消息进行响应。 您还将学习如何为常见的 HTTP 错误创建自定义错误页面,例如 404 Not Found 错误和 500 Internal Server Error 错误。

为了演示如何中止请求并使用自定义 404 HTTP 错误页面进行响应,您将创建一个显示一些消息的页面。 如果请求的消息不存在,您将返回 404 错误。

首先,打开您的 app.py 文件,为消息页面添加新路由:

nano app.py

在文件末尾添加以下路由:

烧瓶应用程序/app.py

# ...

@app.route('/messages/<int:idx>')
def message(idx):
    messages = ['Message Zero', 'Message One', 'Message Two']
    return render_template('message.html', message=messages[idx])

保存并关闭文件。

在上面的路由中,您有一个 URL 变量 idx。 这是将确定将显示什么消息的索引。 例如,如果 URL 是 /messages/0,将显示第一条消息 (Message Zero)。 您使用 int 转换器 只接受正整数,因为 URL 变量默认具有字符串值。

message() 视图函数中,您有一个名为 messages 的常规 Python 列表,其中包含三个消息。 (在实际场景中,这些消息将来自数据库、API 或其他外部数据源。)该函数返回对带有两个参数 message.htmlrender_template() 函数的调用作为模板文件,以及将传递给模板的 message 变量。 此变量将具有来自 messages 列表的列表项,具体取决于 URL 中 idx 变量的值。

接下来打开一个新的 message.html 模板文件:

nano templates/message.html

向其中添加以下代码:

flask_app/templates/message.html

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Messages {% endblock %}</h1>
    <h2>{{ message }}</h2>
{% endblock %}

保存并关闭文件。

在上面的代码中,您扩展了基本模板并覆盖了 content 块。 您在 H1 标题中添加标题 (Messages),并在 H2 标题中显示 message 变量的值。

在开发服务器运行的情况下,在浏览器上访问以下 URL:

http://127.0.0.1:5000/messages/0
http://127.0.0.1:5000/messages/1
http://127.0.0.1:5000/messages/2
http://127.0.0.1:5000/messages/3

您将看到 H2 在前三个 URL 中的每一个上分别包含文本 Message ZeroMessage OneMessage Two。 但是,在第四个 URL 上,服务器将响应 IndexError: list index out of range 错误消息。 在生产环境中,响应应该是 500 Internal Server Error,但这里的正确响应是 404 Not Found,表示服务器找不到索引为 3

您可以使用 Flask 的 abort() 辅助函数来响应 404 错误。 为此,请打开 app.py 文件:

nano app.py

编辑第一行以导入abort()函数。 然后编辑message()通过添加查看功能尝试 ... 除了子句如以下突出显示的部分所示:

烧瓶应用程序/app.py

from flask import Flask, render_template, abort

# ...
# ...


@app.route('/messages/<int:idx>')
def message(idx):
    messages = ['Message Zero', 'Message One', 'Message Two']
    try:
        return render_template('message.html', message=messages[idx])
    except IndexError:
        abort(404)

保存并关闭文件。

在上面的代码中,您导入了 abort() 函数,用于中止请求并以错误响应。 在 message() 视图函数中,您使用 try ... except 子句来包装函数。 您首先尝试返回带有与 URL 中的索引对应的消息的 messages 模板。 如果索引没有相应的消息,则会引发 IndexError 异常。 然后使用 except 子句捕获该错误,并使用 abort(404) 中止请求并以 404 Not Found HTTP 错误响应。

在开发服务器运行的情况下,使用浏览器重新访问之前以 IndexError 响应的 URL(或访问索引大于 2 的任何 URL):

http://127.0.0.1:5000/messages/3

您将看到以下响应:

Not Found

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

您现在有一个更好的错误消息,表明服务器找不到请求的消息。

接下来,您将为 404 错误页面和 500 错误页面制作一个模板。

首先,您将使用特殊的 @app.errorhandler() 装饰器注册一个函数作为 404 错误的处理程序。 打开app.py文件进行编辑:

nano app.py

通过添加突出显示的部分来编辑文件,如下所示:

烧瓶应用程序/app.py

from flask import Flask, render_template, abort

app = Flask(__name__)


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/messages/<int:idx>')
def message(idx):
    messages = ['Message Zero', 'Message One', 'Message Two']
    try:
        return render_template('message.html', message=messages[idx])
    except IndexError:
        abort(404)

保存并关闭文件。

在这里,您使用 @app.errorhandler() 装饰器将函数 page_not_found() 注册为自定义错误处理程序。 该函数将错误作为参数,并使用名为 404.html 的模板返回对 render_template() 函数的调用。 您稍后将创建此模板,如果需要,您可以使用其他名称。 您还可以在 render_template() 调用之后返回整数 404。 这告诉 Flask 响应中的状态码应该是 404。 如果不添加,默认状态码响应为200,表示请求成功。

接下来,打开一个新的 404.html 模板:

nano templates/404.html

向其中添加以下代码:

flask_app/templates/404.html

{% extends 'base.html' %}

{% block content %}
        <h1>{% block title %} 404 Not Found. {% endblock %}</h1>
        <p>OOPS! Sammy couldn't find your page; looks like it doesn't exist.</p>
        <p>If you entered the URL manually, please check your spelling and try again.</p>
{% endblock %}

保存并关闭文件。

就像任何其他模板一样,您扩展基本模板,替换 contenttitle 块的内容,并添加自己的 HTML 代码。 这里有一个 <h1> 标题作为标题,一个带有自定义错误消息的 <p> 标记告诉用户找不到页面,以及对可能手动输入 URL 的用户有用的消息.

您可以在错误页面中使用任何 HTML、CSS 和 JavaScript,就像在其他模板中一样。

在开发服务器运行的情况下,使用浏览器重新访问以下 URL:

http://127.0.0.1:5000/messages/3

您将看到该页面现在具有基本模板中的导航栏和自定义错误消息。

同样,您可以为 500 Internal Server Error 错误添加自定义错误页面。 打开app.py文件:

nano app.py

404 错误处理程序下方添加以下错误处理程序:

烧瓶应用程序/app.py

# ...

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_error(error):
    return render_template('500.html'), 500

# ...

在这里,您使用与 404 错误处理程序相同的模式。 您使用带有 500 参数的 app.errorhandler() 装饰器将一个名为 internal_error() 的函数变成错误处理程序。 您渲染一个名为 500.html 的模板,并以状态代码 500 进行响应。

然后为了演示自定义错误将如何呈现,在文件末尾添加一个响应 500 HTTP 错误的路由。 无论调试器是否正在运行,这条路线总是会给出 500 Internal Server Error

烧瓶应用程序/app.py

# ...
@app.route('/500')
def error500():
    abort(500)

在这里,您创建一个路由 /500 并使用 abort() 函数响应 500 HTTP 错误。

保存并关闭文件。

接下来,打开新的 500.html 模板:

nano templates/500.html

向其中添加以下代码:

flask_app/templates/500.html

{% extends 'base.html' %}

{% block content %}
        <h1>{% block title %} 500 Internal Server Error {% endblock %}</h1>
        <p>OOOOPS! Something went wrong on the server.</p>
        <p>Sammy is currently working on this issue. Please try again later.</p>
{% endblock %}

保存并关闭文件。

在这里,您执行与 404.html 模板相同的操作。 您扩展基本模板,并将内容块替换为标题和两条自定义消息,通知用户内部服务器错误。

在开发服务器运行的情况下,访问响应 500 错误的路由:

http://127.0.0.1:5000/500

您的自定义页面将出现,而不是通用错误页面。

您现在知道如何在 Flask 应用程序中使用自定义错误页面来处理 HTTP 错误。 接下来,您将学习如何使用日志记录来跟踪应用程序中的事件。 跟踪事件可帮助您了解代码的行为方式,进而有助于开发和故障排除。

第 3 步 — 使用日志记录跟踪应用程序中的事件

在此步骤中,您将使用日志记录来跟踪服务器运行和应用程序正在使用时发生的事件,这有助于您了解应用程序代码中发生的情况,以便更轻松地排除错误。

每当开发服务器运行时,您已经看到日志,通常如下所示:

127.0.0.1 - - [21/Sep/2021 14:36:45] "GET /messages/1 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:52] "GET /messages/2 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:54] "GET /messages/3 HTTP/1.1" 404 -

在这些日志中,您可以看到以下信息:

  • 127.0.0.1:服务器运行的主机。
  • [21/Sep/2021 14:36:45]:请求的日期和时间。
  • GETHTTP请求方法。 在这种情况下,GET 用于检索数据。
  • /messages/2:用户请求的路径。
  • HTTP/1.1:HTTP 版本。
  • 200404:响应的状态码。

这些日志可帮助您诊断应用程序中出现的问题。 当您想了解有关某些请求的更多详细信息时,可以使用 Flask 提供的记录器 app.logger 记录更多信息。

使用日志记录,您可以使用不同的函数来报告不同日志记录级别的信息。 每个级别表示发生的事件具有一定程度的严重性。 可以使用以下功能:

  • app.logger.debug():有关事件的详细信息。
  • app.logger.info():确认事情按预期工作。
  • app.logger.warning():表示发生了意外情况(例如“磁盘空间不足”),但应用程序正在按预期工作。
  • app.logger.error():应用程序的某些部分发生错误。
  • app.logger.critical():严重错误; 整个应用程序可能会停止工作。

要演示如何使用 Flask 记录器,请打开 app.py 文件进行编辑以记录一些事件:

nano app.py

编辑 message() 视图函数,如下所示:

烧瓶应用程序/app.py

# ...

@app.route('/messages/<int:idx>')
def message(idx):
    app.logger.info('Building the messages list...')
    messages = ['Message Zero', 'Message One', 'Message Two']
    try:
        app.logger.debug('Get message with index: {}'.format(idx))
        return render_template('message.html', message=messages[idx])
    except IndexError:
        app.logger.error('Index {} is causing an IndexError'.format(idx))
        abort(404)

# ...

保存并关闭文件。

在这里,您记录了不同级别的一些事件。 您使用 app.logger.info() 记录按预期工作的事件(这是 INFO 级别)。 您使用 app.logger.debug() 获取详细信息(DEBUG 级别),提到应用程序现在正在获取具有特定索引的消息。 然后,您使用 app.logger.error() 记录导致问题的特定索引引发了 IndexError 异常的事实(ERROR 级别,因为发生了错误)。

访问以下网址:

http://127.0.0.1:5000/messages/1

您将在运行服务器的终端中看到以下信息:

Output
[2021-09-21 15:17:02,625] INFO in app: Building the messages list...
[2021-09-21 15:17:02,626] DEBUG in app: Get message with index: 1
127.0.0.1 - - [21/Sep/2021 15:17:02] "GET /messages/1 HTTP/1.1" 200 -

在这里,您会看到 INFO 消息 app.logger.info() 日志,以及带有您使用 app.logger.debug() 记录的索引号的 DEBUG 消息。

现在访问不存在的消息的 URL:

http://127.0.0.1:5000/messages/3

您将在终端中看到以下信息:

Output[2021-09-21 15:33:43,899] INFO in app: Building the messages list...
[2021-09-21 15:33:43,899] DEBUG in app: Get message with index: 3
[2021-09-21 15:33:43,900] ERROR in app: Index 3 is causing an IndexError
127.0.0.1 - - [21/Sep/2021 15:33:43] "GET /messages/3 HTTP/1.1" 404 -

如您所见,您有以前见过的 INFODEBUG 日志,以及一个新的 ERROR 日志,因为索引为 [X142X ] 不存在。

记录事件、详细信息和错误可帮助您确定哪里出了问题并使故障排除更加容易。

您已在此步骤中学习了如何使用 Flask 记录器。 查看 How To Use Logging in Python 3 以更好地理解日志记录。 要深入了解日志记录,请参阅 Flask 日志记录文档Python 日志记录文档

结论

您现在知道如何在 Flask 中使用调试模式,以及如何解决和修复开发 Flask Web 应用程序时可能遇到的一些常见错误。 您还为常见的 HTTP 错误创建了自定义错误页面,并使用 Flask 记录器来跟踪应用程序中的事件,以帮助您检查和了解应用程序的行为方式。

如果您想了解更多关于 Flask 的信息,请查看 Flask 主题页面