web-dev-qa-db-ja.com

運用中のファイルにFlaskの優れたデバッグログメッセージを書き込むにはどうすればよいですか?

Flaskアプリケーションは正常に動作し、時折エラーを生成します。これは、debug=Trueで実行しているときに表示されます。

if __== '__main__':
    app.run(debug=True)

次のような有用なエラーメッセージが表示されます。

Traceback (most recent call last):
  File "./main.py", line 871, in index_route

KeyError: 'stateIIIII'

実稼働環境でアプリケーションを実行するときに(Lighttpd + fastcgiを使用して)これらのエラーメッセージをファイルに保存したいと思います。

さまざまなStackOverflowの質問( http://flask.pocoo.org/docs/errorhandling/http://docs.python.org/2/library/logging.html など); Flaskメーリングリスト;いくつかのブログでは、すべてのすばらしいエラーメッセージをファイルに送信するだけの簡単な方法はないようです-Python物事をカスタマイズするためのロギングモジュールです。

アプリケーションファイルの先頭には、次のようなさまざまなインポートがあります。

app = Flask(__name__)

if app.debug is not True:   
    import logging
    from logging.handlers import RotatingFileHandler
    file_handler = RotatingFileHandler('python.log', maxBytes=1024 * 1024 * 100, backupCount=20)
    file_handler.setLevel(logging.ERROR)
    app.logger.setLevel(logging.ERROR)
    app.logger.addHandler(file_handler)

次に、各ルートのコードをtry/exceptステートメントに入れ、トレースバックを使用してエラーの原因となった行を特定し、Niceエラーメッセージを出力しました。

def some_route():
    try:
        # code for route in here (including a return statement)

    except:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        app.logger.error(traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2))
        return render_template('error.html')

そして、ファイルの最後でdebug=Trueステートメントを削除します。実稼働環境で実行されている場合、アプリケーションはfastcgiサーバー(?)で実行されているため、これを行う必要はないと思います。アプリケーションコードの最後の2行は次のようになります。

if __== '__main__':
    app.run()

これを機能させるのに苦労しています。私が管理した最善の方法は、(app.logger.error('test message'))を使用してファイルに保存される単一のエラーログメッセージを取得することですが、それはその1つのメッセージのみを出力します。そのエラーの直後に別のエラーを記録しようとしても、無視されます。

55
Ben

なぜ機能しないのかはわかりませんが、これがどのように行われているかはわかります。

まず、app.loggerのレベルを設定する必要はありません。したがって、この行app.logger.setLevel()を削除します。

すべてのビューの例外を保存し、エラーページを返します。このコードをどこにでも書くのは大変な作業です。 Flaskはこれを行うためのメソッドを提供します。このようにerrorhandlerメソッドを定義します。

    @app.errorhandler(500)
    def internal_error(exception):
        app.logger.error(exception)
        return render_template('500.html'), 500

ビューで例外が発生するたびに、このメソッドが呼び出され、引数として例外が渡されます。 Pythonロギングは、例外の完全なトレースバックを保存するために使用される例外メソッドを提供します。

これはすべての例外を処理するため、try/exceptブロックにコードを配置する必要さえありません。ただし、エラーハンドラーを呼び出す前に(ロールバックセッションやトランザクションなど)何かをしたい場合は、次のようにします。

    try:
        #code
    except:
        #code
        raise

ログファイルの各エントリに日付と時刻を追加する場合は、(質問で取り上げられている同様のコードの代わりに)次のコードを使用できます。

if app.debug is not True:   
    import logging
    from logging.handlers import RotatingFileHandler
    file_handler = RotatingFileHandler('python.log', maxBytes=1024 * 1024 * 100, backupCount=20)
    file_handler.setLevel(logging.ERROR)
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    file_handler.setFormatter(formatter)
    app.logger.addHandler(file_handler)
60
codecool

後でこれを読む人のために。

より有用な情報をエラーメッセージにプッシュすることをお勧めします。 URL、クライアントIP、ユーザーエージェントなど。Flaskは、app.debug==False関数を使用して、内部的に(Flask.log_exceptionモードで)例外を記録します。したがって、手動で@app.errorhandlerにログを記録する代わりに、次のようなことを行います。

class MoarFlask(Flask):
    def log_exception(self, exc_info):
        """...description omitted..."""
        self.logger.error(
            """
Request:   {method} {path}
IP:        {ip}
User:      {user}
Agent:     {agent_platform} | {agent_browser} {agent_browser_version}
Raw Agent: {agent}
            """.format(
                method = request.method,
                path = request.path,
                ip = request.remote_addr,
                agent_platform = request.user_agent.platform,
                agent_browser = request.user_agent.browser,
                agent_browser_version = request.user_agent.version,
                agent = request.user_agent.string,
                user=user
            ), exc_info=exc_info
        )

次に、構成時に、FileHandlerapp.loggerにバインドして続行します。私はStreamHandlerを使用していないので、多くのサーバー(uWSGIなど)が独自の独自の単語を使い物にならない、回転不可能なメッセージで汚染することを好みます。

Flaskを拡張することを恐れないでください。あなたは遅かれ早かれそれを行うことを余儀なくされます;)

37
Ivan Kleshnin

私はloggingモジュールの専門家ではありませんが、その経験+ Python + Flaskでの経験については、いくつかの観察を考慮して、適切なログ設定を行うことができます:

  • すべての機能(ルート)の先頭で、timestampオブジェクトを作成し、リクエストが行われた正確な時間を登録します成功したかどうか

  • @ app.after_requestを使用して、成功したすべてのリクエストを登録します

  • @ app.errorhandlerを使用して、一般的なエラー+トレースバックを登録します。

このアイデアを示す例を次に示します。

#/usr/bin/python3
""" Demonstration of logging feature for a Flask App. """

from logging.handlers import RotatingFileHandler
from flask import Flask, request, jsonify
from time import strftime

__author__ = "@ivanleoncz"

import logging
import traceback


app = Flask(__name__)

@app.route("/")
@app.route("/index")
def get_index():
    """ Function for / and /index routes. """
    return "Welcome to Flask! "


@app.route("/data")
def get_data():
    """ Function for /data route. """
    data = {
            "Name":"Ivan Leon",
            "Occupation":"Software Developer",
            "Technologies":"[Python, Flask, JavaScript, Java, SQL]"
    }
    return jsonify(data)


@app.route("/error")
def get_nothing():
    """ Route for intentional error. """
    return foobar # intentional non-existent variable


@app.after_request
def after_request(response):
    """ Logging after every request. """
    # This avoids the duplication of registry in the log,
    # since that 500 is already logged via @app.errorhandler.
    if response.status_code != 500:
        ts = strftime('[%Y-%b-%d %H:%M]')
        logger.error('%s %s %s %s %s %s',
                      ts,
                      request.remote_addr,
                      request.method,
                      request.scheme,
                      request.full_path,
                      response.status)
    return response


@app.errorhandler(Exception)
def exceptions(e):
    """ Logging after every Exception. """
    ts = strftime('[%Y-%b-%d %H:%M]')
    tb = traceback.format_exc()
    logger.error('%s %s %s %s %s 5xx INTERNAL SERVER ERROR\n%s',
                  ts,
                  request.remote_addr,
                  request.method,
                  request.scheme,
                  request.full_path,
                  tb)
    return "Internal Server Error", 500


if __== '__main__':
    handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=3)        
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.ERROR)
    logger.addHandler(handler)
    app.run(Host="127.0.0.1",port=8000)

Logrotateとstdoutとfileの同時ログオンに関する詳細については、 this Gist

10
ivanleoncz

Gunicornを使用してFlaskアプリを実行している場合、gunicornエラーハンドラーをFlaskロガーに追加することで、すべてのFlask例外をgunicornログに記録できます。

module/__init__.py

@app.before_first_request
def setup_logging():
    if not app.debug:
        import logging
        gunicorn_logger = logging.getLogger('gunicorn.error')
        for handler in gunicorn_logger.handlers:
            app.logger.addHandler(handler)
1
user545424