Ошибки приложения
Журнал изменений
Новое в версии 0.3.
Приложения выходят из строя, серверы выходят из строя. Рано или поздно вы увидите исключение в рабочей среде. Даже если ваш код на 100% правильный, время от времени вы все равно будете видеть исключения. Почему? Потому что всё остальное, что вовлечено, может выйти из строя. Вот некоторые ситуации, когда совершенно исправный код может привести к ошибкам сервера:
- клиент прервал запрос на ранней стадии, и приложение всё ещё считывало данные из входящего потока
- сервер базы данных был перегружен и не смог обработать запрос
- полно место на файловой системе
- сбой жёсткого диска
- перегрузка сервера бэкэнда
- ошибка программирования в используемой библиотеке
- сбой сетевого соединения сервера с другой системой
И это лишь небольшой образец проблем, с которыми вы можете столкнуться. Так как же справиться с подобными проблемами? По умолчанию, если ваше приложение работает в режиме производства, Flask отобразит очень простую страницу и запишет исключение в журнал logger.
Но вы можете сделать больше, и мы рассмотрим более лучшие способы обработки ошибок.
Инструменты для ведения журналов ошибок
Отправка писем об ошибках, даже только для критических, может стать непосильной задачей, если достаточное количество пользователей сталкивается с ошибками, и журналы ошибок, как правило, не просматриваются. Именно поэтому мы рекомендуем использовать Sentry для обработки ошибок приложения. Он доступен как проект с открытым исходным кодом на GitHub и также доступен в виде хостированной версии, которую можно попробовать бесплатно. Sentry агрегирует повторяющиеся ошибки, собирает полные трассировки стека и локальные переменные для отладки и отправляет вам письма об новых ошибках или превышении пороговых значений частоты.
Для использования Sentry вам необходимо установить клиент raven:
$ pip install raven
А затем добавьте это в ваше приложение Flask:
from raven.contrib.flask import Sentry sentry = Sentry(app, dsn='YOUR_DSN_HERE')
Или, если вы используете фабрики, вы также можете инициализировать его позже:
from raven.contrib.flask import Sentry
sentry = Sentry(dsn='YOUR_DSN_HERE')
def create_app():
app = Flask(__name__)
sentry.init_app(app)
...
return app
Значение YOUR_DSN_HERE необходимо заменить на значение DSN, которое вы получаете из вашей установки Sentry.
После этого ошибки автоматически сообщаются в Sentry, и оттуда вы можете получать уведомления об ошибках.
Обработчики ошибок
Вы можете отобразить пользователю пользовательские страницы ошибок при возникновении ошибки. Это можно сделать, зарегистрировав обработчики ошибок.
Обработчики ошибок — это обычные Подключаемые представления, но вместо регистрации для маршрутов они регистрируются для исключений, которые возникают при попытке выполнить что-то другое.
Регистрация
Зарегистрируйте обработчики ошибок, используя errorhandler() или register_error_handler():
@app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e):
return 'bad request!'
app.register_error_handler(400, lambda e: 'bad request!')
Эти два способа эквивалентны, но первый более понятен и оставляет вам функцию, которую вы можете вызвать по своему желанию (и в тестах). Обратите внимание, что werkzeug.exceptions.HTTPException подклассы, такие как BadRequest из примера, и их HTTP-коды взаимозаменяемы при передаче в методы регистрации или декоратор (BadRequest.code == 400).
Однако вы не ограничены HTTPException или кодами HTTP-статусов, но можете зарегистрировать обработчик для любого класса исключений, который вам нужен.
Журнал изменений
Изменено в версии 0.11: Обработчики ошибок теперь упорядочиваются по степени специфичности классов исключений, для которых они зарегистрированы, вместо порядка их регистрации.
Обработка
После того, как возникло исключение, происходит обход его иерархии, и поиск в списках исключений, для которых зарегистрированы обработчики. Выбирается наиболее специфичный обработчик.
Например, если возникает экземпляр ConnectionRefusedError, и зарегистрирован обработчик для ConnectionError и ConnectionRefusedError, то вызывается более специфичный обработчик ConnectionRefusedError на экземпляре исключения, и его ответ отображается пользователю.
Письма об ошибках
Если приложение работает в режиме производства (что будет происходить на вашем сервере), вы можете не увидеть никаких сообщений журнала. Причина в том, что Flask по умолчанию просто сообщает об ошибке в поток WSGI или stderr (в зависимости от доступных вариантов). Определение места расположения этих сообщений порой может быть затруднено. Часто они находятся в файлах журналов вашего веб-сервера.
Я практически гарантирую, что если вы будете использовать только файл журнала для ошибок приложения, вы никогда его не будете просматривать, за исключением случаев отладки проблем, о которых сообщил вам пользователь. Скорее всего, вам нужно будет получать письмо сразу после возникновения исключения. Таким образом, вы получите предупреждение и сможете что-то с этим сделать.
Flask использует встроенную систему логирования Python, и она может фактически отправлять вам письма об ошибках, что, вероятно, вам и нужно. Вот как можно настроить Flask logger для отправки писем об исключениях:
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, который будет отправлять письма с сервером электронной почты, работающим по адресу 127.0.0.1, всем ADMINS с адреса server-error@example.com с темой «Ваше приложение вышло из строя». Если ваш сервер электронной почты требует учетных данных, их также можно предоставить. Для этого ознакомьтесь с документацией для SMTPHandler.
Мы также сообщаем обработчику отправлять только ошибки и более критические сообщения. Потому что мы определенно не хотим получать письмо о предупреждениях или других бесполезных записях журнала, которые могут возникнуть во время обработки запроса.
Прежде чем запускать это в рабочей среде, ознакомьтесь также с Настройка формата журнала, чтобы добавить больше информации в это письмо об ошибке. Это сэкономит вам много разочарований.
Ведение журнала в файл
Даже если вы получаете письма, вам, вероятно, также нужно вести журнал предупреждений. Это хорошая идея — хранить как можно больше информации, которая может потребоваться для отладки проблемы. По умолчанию, начиная с Flask 0.11, ошибки автоматически записываются в журнал вашего веб-сервера. Предупреждения, однако, нет. Обратите внимание, что 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)
Настройка формата журнала
По умолчанию обработчик записывает только строку сообщения в файл или отправляет вам это сообщение по электронной почте. Запись журнала содержит больше информации, и имеет смысл настроить ваш logger для включения этой информации, чтобы у вас было лучшее представление о том, почему произошла ошибка и, что более важно, где.
Форматировщик может быть создан со строкой формата. Обратите внимание, что трассировки стека добавляются в запись журнала автоматически. Вам не нужно делать это в строке формата форматировщика журнала.
Вот несколько примеров настроек:
Электронная почта
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(): -
обрабатывает фактическое форматирование. Ему передается объект
LogRecord, и он должен вернуть отформатированную строку. -
formatTime(): -
вызывается для форматирования
asctime. Если вам нужен другой формат времени, вы можете переопределить этот метод. -
formatException() -
вызывается для форматирования исключений. Ему передается кортеж
exc_info, и он должен вернуть строку. По умолчанию обычно всё хорошо, переопределять его не обязательно.
Для получения дополнительной информации посетите официальную документацию.
Другие библиотеки
До сих пор мы настраивали только логгер, созданный вашей собственной программой. Другие библиотеки также могут вести журнал. Например, 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)
© 2007–2020 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/0.12.x/errorhandling/