著者は、Write for DOnationsプログラムの一環として、寄付を受け取るためにFree and Open Source Fundを選択しました。
はじめに
Flaskは、Python言語でWebアプリケーションを作成するための便利なツールや機能を提供する、軽量なPython Webフレームワークです。
Webアプリケーションを開発していると、アプリケーションが予想とは異なる動作をする状況に必ず遭遇します。変数のスペルミス、for
ループの誤用、またはif
文の構築方法が原因でPythonの例外が発生することがあります。例えば、関数を宣言する前に呼び出したり、存在しないページを探したりするなどです。エラーと例外を適切に処理する方法を学ぶことで、Flaskアプリケーションの開発が容易かつスムーズになります。
このチュートリアルでは、Webアプリケーション開発時に遭遇する一般的なエラーを処理する方法を示す小さなWebアプリケーションを構築します。カスタムエラーページの作成、Flaskデバッガを使用した例外のトラブルシューティング、およびアプリケーション内のイベントの追跡にロギングを使用する方法を学びます。
前提条件
-
ローカルのPython 3プログラミング環境。お使いのディストリビューションのチュートリアルに従って、Python 3のローカルプログラミング環境のインストールと設定方法シリーズをご覧ください。このチュートリアルでは、プロジェクトディレクトリを
flask_app
と呼びます。 -
ルート、ビュー関数、テンプレートなど、基本的なFlaskの概念を理解していること。Flaskについて馴染みがない場合は、FlaskとPythonを使って初めてのWebアプリケーションを作成する方法とFlaskアプリケーションでテンプレートを使用する方法をチェックしてください。
-
基本的なHTMLの概念を理解していること。背景知識については、HTMLでウェブサイトを構築する方法チュートリアルシリーズを復習することができます。
ステップ1 — Flaskデバッガの使用
このステップでは、いくつかのエラーを含むアプリケーションを作成し、デバッグモードをオフにして実行してアプリケーションの応答を確認します。その後、デバッグモードをオンにしてデバッガを使用し、アプリケーションのエラーをトラブルシューティングします。
プログラミング環境がアクティブでFlaskがインストールされている状態で、flask_app
ディレクトリ内にあるapp.py
ファイルを編集用に開いてください:
以下のコードをapp.py
ファイル内に追加してください:
上記のコードでは、まずflask
パッケージからFlask
クラスをインポートします。次に、app
という名前のFlaskアプリケーションインスタンスを作成します。@app.route()
デコレータを使用して、index()
というビュー関数を作成し、その戻り値としてrender_template()
関数を呼び出します。これにより、index.html
というテンプレートがレンダリングされます。このコードには2つのエラーがあります:1つ目はrender_template()
関数をインポートしていないこと、2つ目はindex.html
テンプレートファイルが存在しないことです。
ファイルを保存して閉じます。
次に、以下のコマンドを使用してFLASK_APP
環境変数でFlaskにアプリケーションを伝えます(Windowsの場合はexport
の代わりにset
を使用してください):
そして、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ドキュメントのデプロイメントオプションページを参照してください。また、Gunicornを使ったFlaskデプロイメントチュートリアルや、uWSGIを使ったチュートリアルをチェックしてみることもできます。さらに、DigitalOcean App Platformを使用してFlaskアプリケーションをデプロイするには、Gunicornを使ってApp Platformに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ではexport
の代わりにset
を使用します):
開発サーバーを実行します:
ターミナルには以下のような出力が表示されます:
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シェルです)。
ブラウザでインデックスページを更新すると、以下のページが表示されます:
ここでは、より理解しやすい形でエラーメッセージが表示されています。最初の見出しは、問題を引き起こしたPythonの例外の名前を示しています(この場合はNameError
)。2行目は、直接の原因(render_template()
が定義されていない、つまりこの場合はインポートされていない)を示しています。その後、実行されたFlaskの内部コードを通るトレースバックが表示されます。トレースバックは下から上に読むことが一般的で、トレースバックの最後の行には通常、最も有用な情報が含まれています。
注意:
囲まれたターミナルアイコンを使うと、ブラウザ上で異なるフレームでPythonコードを実行できます。これは、Pythonのインタラクティブシェルで行うように変数の値を確認したい場合に便利です。ターミナルアイコンをクリックすると、サーバーを実行した際に取得したDebugger PINコードを入力する必要があります。このチュートリアルでは、このインタラクティブシェルは必要ありません。
このNameError
の問題を解決するために、サーバーを稼働させたまま、新しいターミナルウィンドウを開き、環境をアクティブにし、app.py
ファイルを開いてください:
ファイルを以下のように修正します:
保存してファイルを閉じます。
ここで、不足していたrender_template()
関数をインポートしました。
開発サーバーを稼働させた状態で、ブラウザのインデックスページを更新します。
今回は、次のような情報を含むエラーページが表示されます:
Outputjinja2.exceptions.TemplateNotFound
jinja2.exceptions.TemplateNotFound: index.html
このエラーメッセージは、index.html
テンプレートが存在しないことを示しています。
これを修正するために、コードの重複を避けるために他のテンプレートが継承するbase.html
テンプレートファイルを作成し、それを拡張するindex.html
テンプレートを作成します。
Flaskがテンプレートファイルを探すディレクトリであるtemplates
ディレクトリを作成します。そして、お好みのエディタでbase.html
ファイルを開きます:
以下のコードをbase.html
ファイルに追加します:
保存してファイルを閉じます。
この基本テンプレートには、他のテンプレートで再利用するために必要なすべてのHTMLの定型文が含まれています。title
ブロックは各ページのタイトルを設定するために置き換えられ、content
ブロックは各ページの内容で置き換えられます。ナビゲーションバーには2つのリンクがあり、url_for()
ヘルパー関数を使用してindex()
ビュー関数にリンクするインデックスページ用のリンクと、アプリケーションに含める場合のAboutページ用のリンクがあります。
次に、ベーステンプレートから継承するindex.html
というテンプレートファイルを開きます。
以下のコードを追加してください:
ファイルを保存して閉じます。
上記のコードでは、ベーステンプレートを拡張し、content
ブロックを上書きします。そして、ページタイトルを設定し、title
ブロックを使用してH1
ヘッダーに表示し、挨拶文をH2
ヘッダーに表示します。
開発サーバーを実行している状態で、ブラウザのインデックスページを更新します。
アプリケーションにエラーが表示されなくなり、インデックスページが期待通りに表示されることがわかります。
これでデバッグモードを使用し、エラーメッセージの処理方法を学びました。次に、任意のエラーメッセージでリクエストを中断し、カスタムエラーページで応答する方法を学びます。
ステップ2 — カスタムエラーページの作成
このステップでは、ユーザーがサーバー上に存在しないデータを要求した場合に404 HTTPエラーメッセージでリクエストを中断する方法を学びます。また、404 Not Found
エラーや500 Internal Server Error
エラーなど、一般的なHTTPエラーのためのカスタムエラーページを作成する方法も学びます。
リクエストを中断し、カスタムの404 HTTPエラーページで応答する方法を示すために、いくつかのメッセージを表示するページを作成します。リクエストされたメッセージが存在しない場合、404エラーで応答します。
まず、app.py
ファイルを開いて、メッセージページの新しいルートを追加します:
ファイルの最後に以下のルートを追加します:
ファイルを保存して閉じます。
上記のルートでは、URL変数idx
があります。これは、表示されるメッセージを決定するインデックスです。例えば、URLが/messages/0
の場合、最初のメッセージ(Message Zero
)が表示されます。int
コンバータを使用して、正の整数のみを受け入れます。これは、URL変数がデフォルトで文字列値を持つためです。
message()
ビュー関数内には、3つのメッセージを含むmessages
という通常のPythonリストがあります。(実際のシナリオでは、これらのメッセージはデータベース、API、または他の外部データソースから取得されます。)この関数は、render_template()
関数の呼び出しを返します。これには2つの引数があり、テンプレートファイルとしてmessage.html
と、テンプレートに渡されるmessage
変数があります。この変数は、URL内のidx
変数の値に応じて、messages
リストからのリストアイテムを持ちます。
次に、新しいmessage.html
テンプレートファイルを開きます:
それに次のコードを追加します:
ファイルを保存して閉じます。
上記のコードでは、基本テンプレートを拡張し、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
最初の3つのURLのそれぞれで、H2
にはMessage Zero
、Message One
、またはMessage Two
というテキストが表示されます。しかし、4番目のURLでは、サーバーはIndexError: list index out of range
というエラーメッセージで応答します。本番環境では、応答は500 Internal Server Error
になりますが、適切な応答は3
というインデックスのメッセージが見つからないことを示す404 Not Found
です。
Flaskのabort()
ヘルパー関数を使って404
エラーで応答することができます。そのために、app.py
ファイルを開いてください:
最初の行を編集してabort()
関数をインポートします。次に、message()
ビュー関数を編集し、以下の強調表示された部分に示すようにtry ... except
句を追加します:
ファイルを保存して閉じます。
上記のコードでは、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.errorhandler()
デコレータを使用して、関数page_not_found()
をカスタムエラーハンドラとして登録します。この関数はエラーを引数として受け取り、テンプレート404.html
を指定したrender_template()
関数の呼び出しを返します。このテンプレートは後で作成し、必要に応じて別の名前を使用することもできます。また、render_template()
呼び出しの後に整数404
を返します。これにより、レスポンスのステータスコードが404
であることをFlaskに伝えます。これを追加しない場合、デフォルトのステータスコードレスポンスは200
となり、リクエストが成功したことを意味します。
次に、新しい404.html
テンプレートを開きます:
以下のコードを追加します:
ファイルを保存して閉じます。
他のテンプレートと同様に、基本テンプレートを拡張し、content
とtitle
ブロックの内容を置き換え、独自のHTMLコードを追加します。ここでは、タイトルとして<h1>
見出し、ページが見つからなかったことをユーザーに伝えるカスタムエラーメッセージを含む<p>
タグ、およびURLを手動で入力した可能性のあるユーザー向けのヘルプメッセージがあります。
他のテンプレートと同じように、エラーページで好きなHTML、CSS、JavaScriptを使用できます。
開発サーバーを実行している状態で、ブラウザを使用して次のURLに再訪します:
http://127.0.0.1:5000/messages/3
ページには、基本テンプレートのナビゲーションバーとカスタムエラーメッセージが表示されるようになります。
同様に、500 Internal Server Error
エラーに対してカスタムエラーページを追加することもできます。app.py
ファイルを開きます:
以下のエラーハンドラを404
エラーハンドラの下に追加します:
ここでは、404
エラーハンドラと同じパターンを使用します。app.errorhandler()
デコレータに500
引数を指定し、internal_error()
関数をエラーハンドラにします。500.html
テンプレートをレンダリングし、ステータスコード500
で応答します。
次に、カスタムエラーがどのように表示されるかをデモンストレーションするために、ファイルの最後に500
HTTPエラーで応答するルートを追加します。このルートは、デバッガが実行されているかどうかに関係なく、常に500 Internal Server Error
を返します:
ここでは、/500
ルートを作成し、abort()
関数を使用して500
HTTPエラーで応答します。
ファイルを保存して閉じます。
次に、新しい500.html
テンプレートを開きます:
以下のコードを追加します:
ファイルを保存して閉じます。
ここでは、404.html
テンプレートと同じことを行います。ベーステンプレートを拡張し、コンテンツブロックをタイトルと2つのカスタムメッセージで置き換えて、内部サーバーエラーについてユーザーに通知します。
開発サーバーが稼働している状態で、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のバージョン。200
または404
: レスポンスのステータスコード。
これらのログは、アプリケーションで発生する問題を診断するのに役立ちます。app.logger
が提供するロガーを使用して、特定のリクエストに関する詳細情報を知りたい場合により多くの情報をログに記録できます。
ロギングを使用すると、異なるロギングレベルで情報を報告するためにさまざまな関数を使用できます。各レベルは、特定の重大度で発生したイベントを示します。以下の関数を使用できます:
app.logger.debug()
: イベントに関する詳細情報。app.logger.info()
: 予想通りに動作していることの確認。app.logger.warning()
: 予期しない事象(例:「ディスク容量不足」)が発生したことの表示だが、アプリケーションは予想通りに動作しています。app.logger.error()
: アプリケーションの一部でエラーが発生しました。app.logger.critical()
: 重大なエラーが発生し、アプリケーション全体が動作を停止する可能性があります。
Flaskロガーの使用方法を示すために、app.py
ファイルを開いて編集し、いくつかのイベントをログに記録します:
message()
ビュー関数を次のように編集します:
ファイルを保存して閉じます。
ここでは、異なるレベルでいくつかのイベントをログに記録しました。期待通りに動作しているイベント(INFO
レベル)をログに記録するためにapp.logger.info()
を使用します。詳細な情報(DEBUG
レベル)を記録するためにapp.logger.debug()
を使用し、特定のインデックスでメッセージを取得していることを示します。そして、IndexError
例外が発生したことを、問題を引き起こした特定のインデックスとともにログに記録するためにapp.logger.error()
を使用します(エラーが発生したため、ERROR
レベル)。
以下のURLにアクセスしてください:
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 -
ここでは、app.logger.info()
がログに記録する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 -
ご覧の通り、以前に見たINFO
とDEBUG
のログに加えて、インデックス3
のメッセージが存在しないため、新しいERROR
ログがあります。
イベント、詳細情報、エラーをログに記録することで、何が問題になったかを特定し、トラブルシューティングを容易にすることができます。
このステップでは、Flaskのロガーの使い方を学びました。ロギングについてより深く理解するために、Python 3でのロギングの使い方をチェックしてください。ロギングについて詳しく知りたい場合は、FlaskのロギングドキュメントとPythonのロギングに関するドキュメントを参照してください。
結論
これで、Flaskでデバッグモードを使用する方法、およびFlaskウェブアプリケーションを開発する際に遭遇する可能性のある一般的なエラーをトラブルシューティングして修正する方法を知りました。また、一般的なHTTPエラーに対するカスタムエラーページを作成し、Flaskのロガーを使用してアプリケーション内のイベントを追跡し、アプリケーションの動作を検査し理解するのに役立てました。
Flaskについてもっと読みたい場合は、Flaskのトピックページをチェックしてください。
Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-errors-in-a-flask-application