Как написать сообщение с журналом 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)