作者选择了自由和开源基金作为Write for DOnations计划的一部分来接受捐赠。

简介

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

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

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

先决条件

  • 本地 Python 3 编程环境。您可以按照如何为 Python 3系列安装和设置本地编程环境中的分发教程进行操作。在本教程中,我们将调用我们的项目目录flask_app

  • 了解基本的 Flask 概念,例如路由、视图函数和模板。如果您不熟悉 Flask,请查看如何使用 Flask 和 Python 创建您的第一个 Web 应用程序和如何在 Flask 应用程序中使用模板。

  • 了解基本的 HTML 概念。您可以查看我们的如何使用 HTML 构建网站教程系列以了解背景知识。

第一步——使用 Flask 调试器

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

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

nano app.py

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

烧瓶_app/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)

此输出提供以下信息:

  • 正在服务的 Flask 应用程序(本例中为app.py)

  • 环境,这里是production。警告消息强调此服务器不适用于生产部署。您正在使用此服务器进行开发,因此您可以忽略此警告,但有关更多信息,请参阅 Flask 文档中的部署选项页面。您还可以查看此Flask 部署教程与 Gunicorn或这个与 uWSGI的教程,或者您可以使用 DigitalOcean 应用程序平台按照如何使用 Gunicorn 部署 Flask 应用程序来部署您的 Flask 应用程序到应用程序平台教程。

  • 调试模式关闭,这意味着 Flask 调试器没有运行,您将不会在应用程序中收到有用的错误消息。在生产环境中,显示详细错误会使您的应用程序面临安全漏洞。

  • 服务器在http://127.0.0.1:5000/URL 上运行。要停止服务器,请使用CTRL+C,但暂时不要这样做。

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

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/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文件中:

烧瓶_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

向其中添加以下代码:

烧瓶_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/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 或其他外部数据源。)该函数返回对render_template()函数的调用,其中有两个参数,message.html作为模板文件,message变量用于将传递给模板。此变量将具有来自messages列表的列表项,具体取决于 URL 中idx变量的值。

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

nano templates/message.html

向其中添加以下代码:

烧瓶_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()函数。然后通过添加try ... except子句来编辑message()视图函数,如下面突出显示的部分所示:

烧瓶_app/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 FoundHTTP 错误进行响应。

在开发服务器运行的情况下,使用您的浏览器重新访问之前以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/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

向其中添加以下代码:

烧瓶_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/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的状态码进行响应。

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

烧瓶_app/app.py


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

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

保存并关闭文件。

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

nano templates/500.html

向其中添加以下代码:

烧瓶_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]:请求的日期和时间。

  • GET:HTTP请求方法。在这种情况下,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/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()记录导致问题的特定索引(ERROR级别,因为发生错误)引发了IndexError异常的事实。

访问以下网址:

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日志,因为索引为3的消息不存在。

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

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

结论

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

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

Logo

Python社区为您提供最前沿的新闻资讯和知识内容

更多推荐