アプリケーションエラーの処理—Flaskドキュメント

提供:Dev Guides
< FlaskFlask/docs/2.0.x/errorhandling
移動先:案内検索

アプリケーションエラーの処理

アプリケーションは失敗し、サーバーは失敗します。 遅かれ早かれ、本番環境で例外が発生します。 コードが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に報告され、そこからエラー通知を受け取ることができます。

参考


エラーハンドラ

Flaskでエラーが発生すると、適切な HTTPステータスコードが返されます。 400-499は、クライアントの要求データ、または要求されたデータに関するエラーを示します。 500-599は、サーバーまたはアプリケーション自体のエラーを示します。

エラーが発生したときに、カスタムエラーページをユーザーに表示したい場合があります。 これは、エラーハンドラーを登録することで実行できます。

エラーハンドラーは、エラーのタイプが発生したときに応答を返す関数です。これは、ビューが要求URLが一致したときに応答を返す関数であるのと同様です。 処理中のエラーのインスタンスが渡されます。これはおそらくHTTPExceptionです。

応答のステータスコードはハンドラーのコードに設定されません。 ハンドラーから応答を返すときは、必ず適切な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アプリケーションをビルドするとき、例外に遭遇します。 リクエストの処理中にコードの一部が壊れた場合(およびエラーハンドラーが登録されていない場合)、デフォルトで「500内部サーバーエラー」(InternalServerError)が返されます。 同様に、リクエストが未登録のルートに送信されると、「404 NotFound」(NotFound)エラーが発生します。 ルートが許可されていない要求メソッドを受信すると、「405メソッドが許可されていません」(MethodNotAllowed)が発生します。 これらはすべてHTTPExceptionのサブクラスであり、Flaskでデフォルトで提供されます。

Flaskを使用すると、Werkzeugによって登録されたHTTP例外を発生させることができます。 ただし、デフォルトのHTTP例外は、単純な例外ページを返します。 エラーが発生したときに、カスタムエラーページをユーザーに表示したい場合があります。 これは、エラーハンドラーを登録することで実行できます。

リクエストの処理中にFlaskが例外をキャッチすると、最初にコードで検索されます。 コードにハンドラーが登録されていない場合、Flaskはクラス階層によってエラーを検索します。 最も具体的なハンドラーが選択されます。 ハンドラーが登録されていない場合、HTTPExceptionサブクラスはコードに関する一般的なメッセージを表示し、その他の例外は一般的な「500 InternalServerError」に変換されます。

たとえば、ConnectionRefusedErrorのインスタンスが発生し、ハンドラーがConnectionErrorおよびConnectionRefusedErrorに登録されている場合、より具体的なConnectionRefusedErrorハンドラーが応答を生成するための例外インスタンス。

ブループリントが例外を発生させる要求を処理していると仮定すると、ブループリントに登録されているハンドラーは、アプリケーションにグローバルに登録されているハンドラーよりも優先されます。 ただし、ブループリントを決定する前に404がルーティングレベルで発生するため、ブループリントは404ルーティングエラーを処理できません。


ジェネリック例外ハンドラ

HTTPExceptionExceptionなどの非常に一般的な基本クラスのエラーハンドラーを登録することができます。 ただし、これらは予想以上にキャッチされることに注意してください。

たとえば、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

エラーハンドラは引き続き例外クラス階層を尊重します。 HTTPExceptionExceptionの両方のハンドラーを登録すると、Exceptionハンドラーは、HTTPExceptionハンドラーの方が多いため、HTTPExceptionサブクラスを処理しません。明確な。


未処理の例外

例外に登録されているエラーハンドラがない場合は、代わりに500内部サーバーエラーが返されます。 この動作については、flask.Flask.handle_exception()を参照してください。

InternalServerErrorに登録されているエラーハンドラーがある場合、これが呼び出されます。 Flask 1.1.0以降、このエラーハンドラーには、元の未処理のエラーではなく、常にInternalServerErrorのインスタンスが渡されます。

元のエラーはe.original_exceptionとして入手できます。

「500InternalServer Error」のエラーハンドラーには、明示的な500エラーに加えて、キャッチされない例外が渡されます。 デバッグモードでは、「500 InternalServerError」のハンドラーは使用されません。 代わりに、対話型デバッガーが表示されます。


カスタムエラーページ

Flaskアプリケーションを構築するときに、HTTPExceptionを上げて、リクエストに問題があることをユーザーに通知したい場合があります。 幸い、Flaskには便利なabort()関数が付属しており、必要に応じてwerkzeugからのHTTPエラーでリクエストを中止します。 また、基本的な説明が記載されたわかりやすい白黒のエラーページも表示されますが、特別なことは何もありません。

エラーコードに応じて、ユーザーが実際にそのようなエラーを目にする可能性は低くなります。

以下のコードを検討してください。ユーザープロファイルルートがある可能性があり、ユーザーがユーザー名を渡さなかった場合、「400 BadRequest」を発生させることができます。 ユーザーがユーザー名を渡しても見つからない場合は、「404NotFound」を表示します。

from flask import abort, render_template, request

# a username needs to be supplied in the query args
# a successful request would be like /profile?username=jack
@app.route("/profile")
def user_profile():
    username = request.arg.get("username")
    # if a username isn't supplied in the request, return a 400 bad request
    if username is None:
        abort(400)

    user = get_user(username=username)
    # if a user can't be found by their username, return 404 not found
    if user is None:
        abort(404)

    return render_template("profile.html", user=user)

「404ページが見つかりません」例外の別の実装例を次に示します。

from flask import render_template

@app.errorhandler(404)
def page_not_found(e):
    # note that we set the 404 status explicitly
    return render_template('404.html'), 404

アプリケーションファクトリを使用する場合:

from flask import Flask, render_template

def page_not_found(e):
  return render_template('404.html'), 404

def create_app(config_filename):
    app = Flask(__name__)
    app.register_error_handler(404, page_not_found)
    return app

テンプレートの例は次のとおりです。

{% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %}
{% block body %}
  <h1>Page Not Found</h1>
  <p>What you were looking for is just not there.
  <p><a href="{{ url_for('index') }}">go somewhere nice</a>
{% endblock %}

さらなる例

上記の例は、実際にはデフォルトの例外ページを改善するものではありません。 次のようなカスタム500.htmlテンプレートを作成できます。

{% extends "layout.html" %}
{% block title %}Internal Server Error{% endblock %}
{% block body %}
  <h1>Internal Server Error</h1>
  <p>Oops... we seem to have made a mistake, sorry!</p>
  <p><a href="{{ url_for('index') }}">Go somewhere nice instead</a>
{% endblock %}

これは、「500 InternalServerError」でテンプレートをレンダリングすることで実装できます。

from flask import render_template

@app.errorhandler(500)
def internal_server_error(e):
    # note that we set the 500 status explicitly
    return render_template('500.html'), 500

アプリケーションファクトリを使用する場合:

from flask import Flask, render_template

def internal_server_error(e):
  return render_template('500.html'), 500

def create_app():
    app = Flask(__name__)
    app.register_error_handler(500, internal_server_error)
    return app

ブループリント付きモジュラーアプリケーションを使用する場合:

from flask import Blueprint

blog = Blueprint('blog', __name__)

# as a decorator
@blog.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500

# or with register_error_handler
blog.register_error_handler(500, internal_server_error)

ブループリントエラーハンドラ

ブループリントを使用したモジュラーアプリケーションでは、ほとんどのエラーハンドラーが期待どおりに機能します。 ただし、404および405例外のハンドラーに関する警告があります。 これらのエラーハンドラーは、適切なraiseステートメント、または別のブループリントのビュー関数でのabortの呼び出しからのみ呼び出されます。 たとえば、無効なURLアクセスによって呼び出されることはありません。

これは、ブループリントが特定のURLスペースを「所有」していないため、アプリケーションインスタンスには、無効なURLが指定された場合に実行するブループリントエラーハンドラーを知る方法がないためです。 URLプレフィックスに基づいてこれらのエラーに対してさまざまな処理戦略を実行する場合は、requestプロキシオブジェクトを使用してアプリケーションレベルで定義できます。

from flask import jsonify, render_template

# at the application level
# not the blueprint level
@app.errorhandler(404)
def page_not_found(e):
    # if a request is in our blog URL space
    if request.path.startswith('/blog/'):
        # we return a custom blog 404 page
        return render_template("blog/404.html"), 404
    else:
        # otherwise we return our generic site-wide 404 page
        return render_template("404.html"), 404

@app.errorhandler(405)
def method_not_allowed(e):
    # if a request has the wrong method to our API
    if request.path.startswith('/api/'):
        # we return a json saying so
        return jsonify(message="Method Not Allowed"), 405
    else:
        # otherwise we return a generic site-wide 405 page
        return render_template("405.html"), 405

APIエラーをJSONとして返す

FlaskでAPIを構築するとき、一部の開発者は、組み込みの例外がAPIに対して十分に表現力がなく、発行している text / html のコンテンツタイプがAPIコンシューマーにとってあまり役に立たないことに気付きます。

上記とjsonify()と同じ手法を使用して、APIエラーに対してJSON応答を返すことができます。 abort()は、descriptionパラメーターで呼び出されます。 エラーハンドラーはそれをJSONエラーメッセージとして使用し、ステータスコードを404に設定します。

from flask import abort, jsonify

@app.errorhandler(404)
def resource_not_found(e):
    return jsonify(error=str(e)), 404

@app.route("/cheese")
def get_one_cheese():
    resource = get_resource()

    if resource is None:
        abort(404, description="Resource not found")

    return jsonify(resource)

カスタム例外クラスを作成することもできます。 たとえば、人間が読める適切なメッセージ、エラーのステータスコード、およびエラーのコンテキストを増やすためのオプションのペイロードを受け取ることができるAPIの新しいカスタム例外を導入できます。

これは簡単な例です。

from flask import jsonify, request

class InvalidAPIUsage(Exception):
    status_code = 400

    def __init__(self, message, status_code=None, payload=None):
        super().__init__()
        self.message = message
        if status_code is not None:
            self.status_code = status_code
        self.payload = payload

    def to_dict(self):
        rv = dict(self.payload or ())
        rv['message'] = self.message
        return rv

@app.errorhandler(InvalidAPIUsage)
def invalid_api_usage(e):
    return jsonify(e.to_dict())

# an API app route for getting user information
# a correct request might be /api/user?user_id=420
@app.route("/api/user")
def user_api(user_id):
    user_id = request.arg.get("user_id")
    if not user_id:
        raise InvalidAPIUsage("No user id provided!")

    user = get_user(user_id=user_id)
    if not user:
        raise InvalidAPIUsage("No such user!", status_code=404)

    return jsonify(user.to_dict())

ビューは、エラーメッセージとともにその例外を発生させることができるようになりました。 さらに、ペイロードパラメーターを使用して、追加のペイロードを辞書として提供できます。


ロギング

管理者に電子メールで送信するなど、例外をログに記録する方法については、ロギングを参照してください。


デバッグ

開発および本番環境でエラーをデバッグする方法については、アプリケーションエラーのデバッグを参照してください。