アプリケーションエラー
バージョン0.3の新機能。
アプリケーションは失敗し、サーバーは失敗します。 遅かれ早かれ、本番環境で例外が発生します。 コードが100 % c正しい場合でも、例外が発生することがあります。 どうして? 関係する他のすべてが失敗するからです。 完全に細かいコードがサーバーエラーにつながる可能性があるいくつかの状況を次に示します。
- クライアントはリクエストを早期に終了しましたが、アプリケーションはまだ受信データから読み取っていました
- データベースサーバーが過負荷になり、クエリを処理できませんでした
- ファイルシステムがいっぱいです
- ハードドライブがクラッシュしました
- バックエンドサーバーが過負荷
- 使用しているライブラリのプログラミングエラー
- サーバーの別のシステムへのネットワーク接続に失敗しました
そして、それはあなたが直面する可能性のある問題のほんの一例です。 では、そのような問題にどのように対処するのでしょうか。 デフォルトでは、アプリケーションが本番モードで実行されている場合、Flaskは非常に単純なページを表示し、例外をlogger
に記録します。
しかし、できることは他にもあります。エラーに対処するためのより良いセットアップについて説明します。
エラーログツール
エラーメールの送信は、たとえ重要なものであっても、十分な数のユーザーがエラーに遭遇し、通常はログファイルが表示されない場合、圧倒される可能性があります。 このため、アプリケーションエラーの処理には Sentry の使用をお勧めします。 GitHub でオープンソースプロジェクトとして利用でき、無料で試すことができるホストバージョンとしても利用できます。 Sentryは重複エラーを集約し、デバッグ用に完全なスタックトレースとローカル変数をキャプチャし、新しいエラーまたは頻度のしきい値に基づいてメールを送信します。
Sentryを使用するには、 sentry-sdk クライアントを追加の flask 依存関係とともにインストールする必要があります。
$ 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に報告され、そこからエラー通知を受け取ることができます。
フォローアップの読み取り:
- Sentryは、同様の方法でワーカーキュー(RQ、Celery)からのエラーのキャッチもサポートしています。 詳細については、 PythonSDKドキュメントを参照してください。
- セントリー入門
- Flask固有のドキュメント。
エラーハンドラ
エラーが発生したときに、カスタムエラーページをユーザーに表示したい場合があります。 これは、エラーハンドラーを登録することで実行できます。
エラーハンドラーは、応答を返す通常のビュー関数ですが、ルートに登録される代わりに、要求の処理中に発生する例外または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
のインスタンスが発生し、ハンドラーが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
のエラーハンドラーは、未処理のエラーも含め、すべてのエラーがユーザーに表示される方法を変更するのに役立つように思われる場合があります。 ただし、これはPythonでexcept Exception:
を実行するのと似ており、すべてのHTTPステータスコードを含む all その他の未処理のエラーをキャプチャします。 ほとんどの場合、より具体的な例外のハンドラーを登録する方が安全です。 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
の両方のハンドラーを登録すると、Exception
ハンドラーは、HTTPException
ハンドラーの方が多いため、HTTPException
サブクラスを処理しません。明確な。
未処理の例外
例外に登録されているエラーハンドラがない場合は、代わりに500内部サーバーエラーが返されます。 この動作については、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
アプリケーションエラーのデバッグ
本番アプリケーションの場合は、アプリケーションエラーの説明に従って、ログと通知を使用してアプリケーションを構成します。 このセクションでは、デプロイメント構成をデバッグし、フル機能のPythonデバッガーを使用してさらに深く掘り下げる際の指針を示します。
疑わしい場合は、手動で実行する
アプリケーションを本番用に構成する際に問題が発生しましたか? ホストへのシェルアクセスがある場合は、デプロイメント環境のシェルからアプリケーションを手動で実行できることを確認してください。 権限の問題をトラブルシューティングするために、構成されたデプロイメントと同じユーザーアカウントで実行するようにしてください。 本番ホストで debug = True を指定してFlaskの組み込み開発サーバーを使用できます。これは、構成の問題を検出するのに役立ちますが、制御された環境で一時的にこれを実行してください。しないでください debug = True を使用して本番環境で実行します。
デバッガーの操作
より深く掘り下げるために、おそらくコード実行をトレースするために、Flaskは箱から出してすぐにデバッガーを提供します(デバッグモードを参照)。 別のPythonデバッガーを使用する場合は、デバッガーが相互に干渉することに注意してください。 お気に入りのデバッガーを使用するには、いくつかのオプションを設定する必要があります。
debug
-デバッグモードを有効にして例外をキャッチするかどうかuse_debugger
-内部Flaskデバッガーを使用するかどうかuse_reloader
-モジュールが変更された場合にプロセスをリロードしてフォークするかどうか
debug
は、他の2つのオプションに任意の値を設定するために、Trueである必要があります(つまり、例外をキャッチする必要があります)。
デバッグにAptana / Eclipseを使用している場合は、use_debugger
とuse_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')