Как написать сообщение с журналом Flask отлично отладочного журнала в файл в процессе производства?

У меня есть приложение Flask, которое работает хорошо и создает случайную ошибку, которая видна, когда она работает с debug=True:

if __name__ == '__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 и использовал traceback, чтобы выяснить, из какой строки возникла ошибка, и напечатать приятное сообщение об ошибке:

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 (?), Когда он запускается на производстве. Последние две строки моего кода приложения выглядят следующим образом:

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

Я изо всех сил пытаюсь заставить это работать. Я думаю, что лучше всего мне удалось получить одно сообщение журнала ошибок, которое будет сохранено в файле, используя (app.logger.error('test message')), но оно печатает только одно сообщение. Попытка зарегистрировать другую ошибку сразу после этого просто игнорируется.

Ответ 1

Я не знаю, почему он не работает, но я могу сказать, как это делается.

Прежде всего, вам не нужно устанавливать уровень app.logger. Поэтому удалите эту строку app.logger.setLevel().

Вы хотите сохранить страницу исключения и возврата ошибки для каждого вида. Много писать, писать этот код повсюду. Flask предоставляет метод для этого. Определите метод обработчика ошибок, подобный этому.

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

Всякий раз, когда представление вызывает исключение, этот метод вызывается и передает исключение в качестве аргумента. Протокол Python предоставляет метод исключения, который используется для сохранения полной трассировки исключения.

Так как это обрабатывает все исключения, вам даже не нужно класть код в try/except block. Хотя, если вы хотите что-то сделать до вызова обработчика ошибок (например, сеанса отката или транзакции), сделайте следующее:

    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)

Ответ 2

Для тех, кто читает это позже.

Я думаю, что лучше придумать более полезную информацию в сообщениях об ошибках. URL, клиентский IP-адрес, пользовательский агент и т.д. Флажок регистрирует исключения внутри (в режиме 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
        )

Затем во время настройки свяжите FileHandler с app.logger и продолжайте. Я не использую StreamHandler, потому что многие серверы (например, uWSGI) любят загрязнять его с их собственными проприетарно-многословными бесполезными сообщениями без очереди.

Не бойтесь расширения флакона. Вы будете вынуждены сделать это рано или поздно;)

Ответ 3

Я не специалист по logging модуля, но, учитывая мой опыт в этом + несколько лет на Python + Flask, вы можете иметь хорошую конфигурацию регистрации, учитывая некоторые наблюдения:

  • в начале каждой функции (маршрута), создайте объект временной метки, чтобы регистрировать точное время, когда запрос был сделан, независимо, если он был успешным или нет

  • use @app.after_request, для регистрации каждого успешного запроса

  • use @app.errorhandler, для регистрации общих ошибок + Tracebacks

Вот пример, демонстрирующий эту идею:

#/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 __name__ == '__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 и файле в одно и то же время: этот Gist

Ответ 4

Если вы используете gunicorn для запуска своего приложения Flask, вы можете занести в журнал все исключения Flask к журналам gunicorn, добавив обработчики ошибок стрельбы в регистратор Flask:

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