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

New in version 0.3.

アプリケーションやサーバーにはエラーが起きます。早かれ遅かれ本番環境でエラー見ることになるでしょう。 たとえ、コードが100%正しくても、エラーを見ることになります。なぜでしょう? なぜならすべてのことに失敗が入り込んでくるからです。 ここでは、本当によく書かれているコードがサーバーエラーを起こすいくつかの状況示します。 :

  • クライアントが早くリクエストを終了し、アプリケーションがまだ読み込みを行なっている。
  • データベースサーバーが過負荷になって、クエリを処理できなくなっている。
  • ファイルシステムが一杯になっている。
  • ハードドライブがクラッシュしている。
  • バックエンドサーバーが過負荷になっている。
  • 使っているライブラリのエラー。
  • 他のサーバーへのネットワーク接続の失敗。

そして、これらは直面するかもしれない小さな問題の例です。 そのような問題をどのように対処すればいいのでしょうか? 標準では、アプリケーションがプロダクションモードで起動されている場合に、Flaskはとてもシンプルなページを表示して、 logger に対して例外のログを残します。

しかし、他にもできることがあります。そして、エラーを扱うためのより良いセットアップをカバーするでしょう。

エラーメール

アプリケーションが(あなたのサーバー上で)プロダクションモードで実行されているなら、デフォルトでログメッセージを見ることはないでしょう。 それはなぜでしょう?Flaskは設定がないフレームワークになるようにしているからです。 何も設定していない場合、どこにログを残すべきですか? 可能性があるので推測は良いアイデアではない。推測された場所はユーザーがログファイルを作成する権限持っている場所ではありません。 また、ほとんどの小規模のアプリケーションのために誰もログをとにかく見ている人はいないでしょう。

実際には、アプリケーションのエラーに対してログファイルの設定をしている場合、 ユーザーが問題のデバッグの例外を見ることはないでしょう。 その時に警告を発して、それに対して何かすることできます。

FlaskはPythonの組み込みのロギングシステムを使っていて、欲しいエラー情報を実際にメールで送ってくれます。 ここでは、例外のメールを送るためのFlaskのロガーの設定の仕方を示します。

ADMINS = ['yourname@example.com']
if not app.debug:
    import logging
    from logging.handlers import SMTPHandler
    mail_handler = SMTPHandler('127.0.0.1',
                               'server-error@example.com',
                               ADMINS, 'YourApplication Failed')
    mail_handler.setLevel(logging.ERROR)
    app.logger.addHandler(mail_handler)

何が起こったのでしょうか? 新しい SMTPHandler 作成します。これは、 “YourApplication Failed” という タイトルで server-error@example.com から全ての ADMINS にメールを送ります。 メールサーバーは認証が必要な場合、これも実装することができます。 SMTPHandler のドキュメントをチェックして下さい。

エラーやよりクリティカルなメッセージだけメールを送る処理を指示します。 なぜなら、リクエストを処理している間に発生するだろう警告や、他のログを取るほどでもないもののメールを受けとりたくないからです。

本番環境で実行する前に、エラーメールに情報を書き込むための ログのフォーマットの管理 も見てください。 たくさんのフラストレーションから救ってくれるでしょう。

ファイルにログを書き込む

たとえメールを取得した場合でも、警告も記録したいかもしれません。 問題をデバッグするために必要とされるかもしれないたくさんの情報をキープしておくことは良いアイデアだと思います。 Flask自体はコアシステムに任意の警告を出さないので、奇妙かもしれませんが、コード内で警告を出す義務があります。

ロギングシステムで提供されているハンドラーは複数あります。 しかし、それらの全てが基本的なエラーログに対して使いやすい訳ではない。 最も興味深いのは、おそらく以下のものだと思います。 :

  • FileHandler - ファイルシステムのファイルにメッセージを記録する。
  • RotatingFileHandler - ファイルシステムのファイルにメッセージを記録して、 メッセージの数がある数に達すると循環される。
  • NTEventLogHandler - Windowsシステムのシステムのイベントログを記録します。 Windowsに依存している場合には、これは使いたいものでしょう。
  • SysLogHandler - UNIXのsyslogにログを送ります。

ログハンドラーを選択すると、上記のSMTPハンドラーでしたように、より低い設定を使っているか確認するだけです(WARNING をお勧めします)。

if not app.debug:
    import logging
    from themodule import TheHandlerYouWant
    file_handler = TheHandlerYouWant(...)
    file_handler.setLevel(logging.WARNING)
    app.logger.addHandler(file_handler)

ログのフォーマットの管理

デフォルトで、ハンドラーはファイルにメッセージの文字列を書きこむかメールでメッセージを送るでしょう。 ログはさらなる情報を記録して、エラーがなぜ起こったかということや、より詳細な情報や、どこで起こったかということの確かな情報を 得ることができるようにロガーに設定することはいいことです。

フォーマッターはフォーマット文字列で初期化します。 トレースバック自動的にログを取ることを促します。 フォーマッターのフォーマット文字列に何もすることはありません。

ここでは、幾つかのセットアップ事例を紹介します。 :

Eメール

from logging import Formatter
mail_handler.setFormatter(Formatter('''
Message type:       %(levelname)s
Location:           %(pathname)s:%(lineno)d
Module:             %(module)s
Function:           %(funcName)s
Time:               %(asctime)s

Message:

%(message)s
'''))

ファイルロギング

from logging import Formatter
file_handler.setFormatter(Formatter(
    '%(asctime)s %(levelname)s: %(message)s '
    '[in %(pathname)s:%(lineno)d]'
))

複雑なログのフォーマット

ここでは、フォーマット文字列のための便利な値をフォーマットのリストです。 このリストは完璧ではないので注意して下さい。全てのリストを見るには、 logging パッケージの公式ドキュメントを調べて下さい。

Format Description
%(levelname)s メッセージを残すログレベルのテキスト ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
%(pathname)s 問題が発生した時に呼ばれるロギングの ソースファイルのフルパス名(有効にしている場合)。
%(filename)s ファイル名、パスの一部。
%(module)s Module (name portion of filename).
%(funcName)s Name of function containing the logging call.
%(lineno)d Source line number where the logging call was issued (if available).
%(asctime)s Human-readable time when the LogRecord` was created. By default this is of the form "2003-07-08 16:49:45,896" (the numbers after the comma are millisecond portion of the time). This can be changed by subclassing the formatter and overriding the formatTime() method.
%(message)s The logged message, computed as msg % args

フォーマットをさらにカスタマイズしたい場合は、フォーマットのサブクラスを作ることができます。 フォーマットのクラスは三つのメソッドがあります。:

format():
実際のフォーマットで処理します。 LogRecord オブジェクトに渡してフォーマットにする文字列を返す必要があります。
formatTime():
called for asctime formatting. If you want a different time format you can override this method.
formatException()
called for exception formatting. It is passed an exc_info tuple and has to return a string. The default is usually fine, you don’t have to override it.

詳細については公式のドキュメントを見てください。

他のライブラリ

ここまでは、アプリケーションで作成したロガーのみ設定してきました。 他のライブラリも同じようなログを残すことができます。例えば、SQLAlchemyは内部でよくログを取ります。 logging パッケージを一度全てのロガーに設定するためのメソッドがあります。 複数の分割したアプリケーションでそれぞれ同じPythonインタープリターで起動したい場合もあるかもしれません。 以下のように異なるロギングをセットアップすることは不可能です。

代わりに、 getLogger() 関数でロガーを作成して、ハンドラーにそれぞれ追加して、 使いたいロガーを使うことをお勧めします。

from logging import getLogger
loggers = [app.logger, getLogger('sqlalchemy'),
           getLogger('otherlibrary')]
for logger in loggers:
    logger.addHandler(mail_handler)
    logger.addHandler(file_handler)

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

本番環境のアプリケーション用に、 アプリケーションのエラー処理 に書かれているとおり、 ロギングと通知をアプリケーションに設定します。 この章では、デプロイ済みの設定をデバッグして、フル機能のPythonデバッガーで深く掘り下げていく時の手引きとなります。

疑わしい場合は、手動で実行して下さい

本番環境用にアプリケーションを設定する際に問題がありましたか? ホストにシェルアクセスできる場合、デプロイ環境にシェルから手動でアプリケーションを実行することができます。

デバッガーと同時に動かす

より深く掘り下げるためには、コードの実行をトレースすることができ、 そのために、Flaskはデバッガーを提供しています(デバッグモード を見て下さい)。 他のPythonデバッガーを使いたい場合、それぞれのデバッガーのインターフェースに注意して下さい。 好きなデバッガーを使うための設定をしなければいけません。

  • debug - デバッグモードを有効にして例外を捕まえるかどうか
  • use_debugger - 内部のFlaskデバッガーを使うかどうか
  • use_reloader - 例外のプロセスをフォークとリロードするかどうか

他のオプションは任意の値でも構いませんが、 debug はTrueにして下さい(例外を捕まえることができるように)。

デバッグにAptana/Eclipseを使っている場合、 use_debuggeruse_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")

    if app.debug: use_debugger = True
    try:
        # Disable Flask's debugger if external debugger is requested
        use_debugger = not(app.config.get('DEBUG_WITH_APTANA'))
    except:
        pass
    app.run(use_debugger=use_debugger, debug=app.debug,
            use_reloader=use_debugger, host='0.0.0.0')