アプリケーションのエラー Application Errors

Changelog

バージョン 0.3 で追加.

アプリケーションには障害が発生し、サーバにも障害が発生します。遅かれ早かれ、本番環境で例外(exception)が見つかるでしょう。もしコードが100%正しいとしても、時には例外が見つかります。なぜでしょうか?コード以外の関連する全てモノに障害が発生するためです。ここでは、完全によくできたコードがサーバのエラーに至る状況をいくつか示します: Applications fail, servers fail. Sooner or later you will see an exception in production. Even if your code is 100% correct, you will still see exceptions from time to time. Why? Because everything else involved will fail. Here are some situations where perfectly fine code can lead to server errors:

  • クライアントがリクエストを早いうちに終了させて、アプリケーションが受信データの読み取り中です the client terminated the request early and the application was still reading from the incoming data

  • データベース・サーバが過負荷であり、問合せ(query)を処理できません the database server was overloaded and could not handle the query

  • ファイルシステムに空きがありません a filesystem is full

  • ハードドライブ(ハードディスク)がクラッシュしました a harddrive crashed

  • 背後(backend)のサーバが過負荷です a backend server overloaded

  • 使用しているライブラリにプログラムのエラーがあります a programming error in a library you are using

  • サーバから他のシステムへのネットワーク接続に障害が発生しました network connection of the server to another system failed

そして、これは直面するであろう問題の小さなサンプルでしかありません。ではこのような問題にどう対処するのでしょうか?標準設定では、もしアプリケーションが本番環境(production)モードで走っている場合、Flaskはとてもシンプルなページを表示し、loggerへ例外をログします。 And that's just a small sample of issues you could be facing. So how do we deal with that sort of problem? By default if your application runs in production mode, Flask will display a very simple page for you and log the exception to the :attr:`~flask.Flask.logger`.

しかし、もっとできることがあり、エラーを処理するためのより良いいくつかの準備(setup)についてこの文書で扱います。 But there is more you can do, and we will cover some better setups to deal with errors.

エラーログ処理ツール Error Logging Tools

エラーメールを送信していると、たとえ非常に重大なものだけにした場合でも、多くのユーザがエラーに当たると圧倒されるものになり、ログファイルは典型的には見られなくなります。これが、アプリケーションのエラー処理のためのSentryを勧める理由です。それはオープンソースのプロジェクトとしてGitHub上で入手可能であり、無料で試すことのできるホストされたバージョンとしても利用可能です。Sentryは重複するエラーを収集し、スタックトレース全体とローカル変数をデバッグのために捕捉し、そして新しいエラーであるか、もしくは閾値を超える頻度であることに基づいてメール送信します。 Sending error mails, even if just for critical ones, can become overwhelming if enough users are hitting the error and log files are typically never looked at. This is why we recommend using `Sentry <https://sentry.io/>`_ for dealing with application errors. It's available as an Open Source project `on GitHub <https://github.com/getsentry/sentry>`_ and is also available as a `hosted version <https://sentry.io/signup/>`_ which you can try for free. Sentry aggregates duplicate errors, captures the full stack trace and local variables for debugging, and sends you mails based on new errors or frequency thresholds.

Sentryを使うには、sentry-sdkクライアントを、追加分のflask依存対象と一緒にインストールする必要があります: To use Sentry you need to install the `sentry-sdk` client with extra `flask` dependencies::

$ pip install sentry-sdk[flask]

そしてそれから、以下をFlaskのappに追加します: And then add this to your Flask app::

import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

sentry_sdk.init('YOUR_DSN_HERE',integrations=[FlaskIntegration()])

YOUR_DSN_HEREの値は、インストールしたSentryから得たDSNの値に置き換える必要があります。 The `YOUR_DSN_HERE` value needs to be replaced with the DSN value you get from your Sentry installation.

インストールの後、Internal Server Errorに至る障害は自動的にSentryへ報告され、そこからエラー通知を受け取ることができます。 After installation, failures leading to an Internal Server Error are automatically reported to Sentry and from there you can receive error notifications.

さらに追いかけるときに読むもの: Follow-up reads:

  • Sentryは(RQ, Celery(訳注: RQ・Celeryともに、ジョブをキューに投入し、ワーカーでキューからジョブを取り出して実行させる、ジョブキューのPythonのライブラリ)のような)ワーカーのキューでのエラーを同じように捕捉することもサポートしています。さらなる情報はPython SDKドキュメントを見てください。 Sentry also supports catching errors from your worker queue (RQ, Celery) in a similar fashion. See the `Python SDK docs <https://docs.sentry.io/platforms/python/>`_ for more information.

  • Sentry入門 `Getting started with Sentry <https://docs.sentry.io/quickstart/?platform=python>`_

  • Flask-特化のドキュメント `Flask-specific documentation <https://docs.sentry.io/platforms/python/flask/>`_.

エラー処理器(Error handlers) Error handlers

エラーが起きたとき、独自のエラーページを見せたいかもしれません。これは、error handlerを登録することで可能です。 You might want to show custom error pages to the user when an error occurs. This can be done by registering error handlers.

error handlerはレスポンスを返す普通のview関数ですが、経路(route)に対して登録する代わりに、リクエストを処理しようと試みるときに発生することがある例外またはHTTPステータスコードに対して登録します。 An error handler is a normal view function that returns a response, but instead of being registered for a route, it is registered for an exception or HTTP status code that would be raised while trying to handle a request.

登録 Registering

errorhandler()を使って関数を修飾(decorating)してhandlerを登録します(訳注: Pythonのdecorator機能を使って登録します)。または、register_error_handler()を使って、後から関数を登録します。レスポンスを返すときにはエラーコードを設定することを覚えておいてください。: Register handlers by decorating a function with :meth:`~flask.Flask.errorhandler`. Or use :meth:`~flask.Flask.register_error_handler` to register the function later. Remember to set the error code when returning the response. ::

@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)

BadRequestのようなwerkzeug.exceptions.HTTPExceptionのサブクラスとそれらのHTTPコードは、handlerを登録するとき相互に交換可能です。(BadRequest.code == 400 :exc:`werkzeug.exceptions.HTTPException` subclasses like :exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable when registering handlers. (``BadRequest.code == 400``)

非標準のHTTPコード(訳注: HTTPプロトコルの仕様に定義されていないHTTPレスポンスコード)はWerkzeugで分からないため、コードによる登録ができません。代わりに、適切なコードを使ってHTTPExceptionのサブクラスを定義し、そのクラス(とhandler)を登録し、その例外クラス(の例外)を発生させます: Non-standard HTTP codes cannot be registered by code because they are not known by Werkzeug. Instead, define a subclass of :class:`~werkzeug.exceptions.HTTPException` with the appropriate code and register and raise that exception class. ::

class InsufficientStorage(werkzeug.exceptions.HTTPException):
    code = 507
    description = 'Not enough storage space.'

app.register_error_handler(InsufficientStorage, handle_507)

raise InsufficientStorage()

handlerは、HTTPExceptionサブクラスまたはHTTPステータスコードに対してだけでなく、どのような例外クラスに対しても登録可能です。handlerは特定のクラスに対しても、ある親クラスの全てのサブクラスに対しても、登録可能です。 Handlers can be registered for any exception class, not just :exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status codes. Handlers can be registered for a specific class, or for all subclasses of a parent class.

処理(Handling) Handling

リクエストを処理しているときに例外がFlaskによって捕捉されたとき、handlerは最初にコード(訳注: HTTPコードのことだと思います)によって検索されます。もしコードに対するhandlerが登録されてない場合、handlerはクラス階層によって検索されます;最も具体的な(訳注: クラス階層上で最もサブクラス化された)handlerが選ばれます。もしhandlerが登録されていない場合、HTTPExceptionのサブクラスはそのコードに関する汎用的なメッセージを表示し、その他の例外は汎用的な500 Internal Server Errorへ変換されます。 When an exception is caught by Flask while handling a request, it is first looked up by code. If no handler is registered for the code, it is looked up by its class hierarchy; the most specific handler is chosen. If no handler is registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a generic message about their code, while other exceptions are converted to a generic 500 Internal Server Error.

例えば、もしConnectionRefusedErrorのインスタンスが発生し、そしてConnectionErrorConnectionRefusedErrorに対するhandlerが登録されていた場合、レスポンスを生成するために、最も具体的なConnectionRefusedErrorのhandlerが例外のインスタンスを使って呼び出されます。 For example, if an instance of :exc:`ConnectionRefusedError` is raised, and a handler is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError` handler is called with the exception instance to generate the response.

blueprintに登録されたhandlerは、blueprintが例外を発生させたリクエストを処理することを仮定して、アプリケーションでグローバルに登録されたものよりも優先されます。しかしながら、404エラーはblueprintが決定される前の経路決定(routing)のレベルで起こるため、blueprintでは404 routing errorは処理できません。 Handlers registered on the blueprint take precedence over those registered globally on the application, assuming a blueprint is handling the request that raises the exception. However, the blueprint cannot handle 404 routing errors because the 404 occurs at the routing level before the blueprint can be determined.

汎用の例外の処理器(Generic Exception Handlers) Generic Exception Handlers

非常に汎用的な基底クラスへ対して、例えばHTTPExceptionまたはExceptionにさえ、error handlerを登録することができます。しかしながら、これらはおそらくあなたが予想するよりも多くのものを捕捉するであろうことに注意してください。 It is possible to register error handlers for very generic base classes such as ``HTTPException`` or even ``Exception``. However, be aware that these will catch more than you might expect.

HTTPExceptionに対するerror handlerは、例えば、標準のHTMLエラーページをJSONへ変換するには便利かもしれません。しかしながら、このhandlerは、例えば経路決定(routing)のときの404と405エラーのように、あなたが直接引き起こさないものに対しても発動するでしょう。HTTPエラーに関する情報を失わないように、自分のhandlerは必ず注意して作り上げるようにしてください。 An error handler for ``HTTPException`` might be useful for turning the default HTML errors pages into JSON, for example. However, this handler will trigger for things you don't cause directly, such as 404 and 405 errors during routing. Be sure to craft your handler carefully so you don't lose information about the HTTP error.

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に対するerror handlerは、全てのエラー、処理されてないものさえも、ユーザへどのように表示するかを変更するには便利にみえるかもしれません。しかしながら、これはPythonでexcept Exception:を行うことと似たものであり、それは、さもなければ処理されないエラーを全て、HTTPステータスコードを含めて、捕捉するでしょう。殆どの場合、より具体的な例外に対するhandlerを登録する方がより安全です。HTTPExceptionのインスタンスは正当なWSGIレスポンスなので、それらを直接そのまま渡すことも可能です。 An error handler for ``Exception`` might seem useful for changing how all errors, even unhandled ones, are presented to the user. However, this is similar to doing ``except Exception:`` in Python, it will capture *all* otherwise unhandled errors, including all HTTP status codes. In most cases it will be safer to register handlers for more specific exceptions. Since ``HTTPException`` instances are valid WSGI responses, you could also pass them through directly.

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

error handlerでも例外のクラス階層を尊重します。もしHTTPExceptionExceptionの両方に対してhandlerを登録した場合、HTTPExceptionのhandlerの方がより具体的であるため、ExceptionのhandlerはHTTPExceptionサブクラスを処理しません。 Error handlers still respect the exception class hierarchy. If you register handlers for both ``HTTPException`` and ``Exception``, the ``Exception`` handler will not handle ``HTTPException`` subclasses because it the ``HTTPException`` handler is more specific.

処理されなかった例外(Unhandled Exceptions) Unhandled Exceptions

例外に対してerror handlerが登録されていないとき、500 Internal Server Errorが代わりに返されます。この振る舞いに関する情報についてはflask.Flask.handle_exception()を見てください。 When there is no error handler registered for an exception, a 500 Internal Server Error will be returned instead. See :meth:`flask.Flask.handle_exception` for information about this behavior.

InternalServerErrorに対してerror handlerが登録されていた場合、これが呼び起されます。Flask 1.1.0以降、このerror handlerにはオリジナルの処理されなかったエラーではなく、常にInternalServerErrorのインスタンスが渡されます。オリジナルのエラーはe.original_exceptionとして利用可能です。Werkzeug 1.0.0まででは、この属性は処理されなかったエラーの間でだけ存在しており、互換性のためにgetattrを使ってアクセスしてください。 If there is an error handler registered for ``InternalServerError``, this will be invoked. As of Flask 1.1.0, this error handler will always be passed an instance of ``InternalServerError``, not the original unhandled error. The original error is available as ``e.original_exception``. Until Werkzeug 1.0.0, this attribute will only exist during unhandled errors, use ``getattr`` to get access it for compatibility.

@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

ログ処理(Logging) Logging

どのように例外をログするか、例えば管理者へメールするなど、についての情報はログ処理(Logging)を見てください。 See :doc:`/logging` for information on how to log exceptions, such as by emailing them to admins.

アプリケーションのエラーのデバッグ Debugging Application Errors

本番環境のアプリケーションでは、アプリケーションのエラーで記述されているようにログ処理と通知を使って、自分のアプリケーションを設定してください。このセクションは、デプロイの設定をデバッグし、機能の揃ったPythonのデバッガでより深く掘り下げるときのポインタを提供します。 For production applications, configure your application with logging and notifications as described in :ref:`application-errors`. This section provides pointers when debugging deployment configuration and digging deeper with a full-featured Python debugger.

疑わしいときは、手で実行してください When in Doubt, Run Manually

本番環境用に自分のアプリケーションを設定するのに問題を抱えていますか?もしホスト(コンピュータのこと)のシェルにアクセスできる場合、自分のアプリケーションを、デプロイされている環境のシェルから手で実行できるか確かめてください。権限の問題を解決するために、必ずデプロイの設定と同じユーザアカウントのもとで実行してください。Flaskの組込まれた開発サーバをdebug=Trueを使って本番環境のホストで使用することが可能で、それは設定の問題を捕捉するには役立ちますが、必ず制御された環境の中で一時的に実行してください。本番環境ではdebug=Trueを使って実行しないでください。 Having problems getting your application configured for production? If you have shell access to your host, verify that you can run your application manually from the shell in the deployment environment. Be sure to run under the same user account as the configured deployment to troubleshoot permission issues. You can use Flask's builtin development server with `debug=True` on your production host, which is helpful in catching configuration issues, but **be sure to do this temporarily in a controlled environment.** Do not run in production with `debug=True`.

デバッガを使った作業 Working with Debuggers

より深く掘り下げるために、例えばコードの実行をトレースするために、Flaskは最初からデバッガを提供しています(デバッグモードを見てください)。もし別のPythonデバッガを使いたい場合、デバッガはそれぞれ干渉し合うことに注意してください。自分の好きなデバッガを使うためにはいくつかのオプションを設定する必要があります: To dig deeper, possibly to trace code execution, Flask provides a debugger out of the box (see :ref:`debug-mode`). If you would like to use another Python debugger, note that debuggers interfere with each other. You have to set some options in order to use your favorite debugger:

  • debug -デバッグモードを有効にして例外を捕捉するかどうか ``debug`` - whether to enable debug mode and catch exceptions

  • use_debugger -内部のFlaskデバッガを使うかどうか ``use_debugger`` - whether to use the internal Flask debugger

  • use_reloader - moduleが変更されたとき、再読み込みしてプロセスをforkするか(新しくプロセスを作成するか)どうか ``use_reloader`` - whether to reload and fork the process if modules were changed

debugは、他の2つのオプションが何かしら値を持つためには、Trueにする必要が(つまり、例外を捕捉する必要が)あります。 ``debug`` must be True (i.e., exceptions must be caught) in order for the other two options to have any value.

もしAptana/Eclipseをデバッグに使用している場合、use_debuggeruse_reloaderの両方をFalseに設定する必要があります。 If you're using Aptana/Eclipse for debugging you'll need to set both ``use_debugger`` and ``use_reloader`` to False.

設定のために役に立つあり得るパターンは、config.yamlに以下を設定することです(もちろん、自分のアプリケーションに適切なようにブロックを変更してください): A possible useful pattern for configuration is to set the following in your config.yaml (change the block as appropriate for your application, of course)::

FLASK:
    DEBUG: True
    DEBUG_WITH_APTANA: True

それから、自分のアプリケーションの開始地点(main.py)の中で、以下のようなものを持つことができます: Then in your application's entry-point (main.py), you could have something like::

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')