• en
  • Language: ru
  • Documentation version: 1.1.x

Ошибки приложения

Добавлено в версии 0.3.

Приложения отказывают, серверы отказывают. Рано или поздно вы увидите исключение в производстве. Даже если ваш код на 100% правильный, вы все равно будете время от времени видеть исключения. Почему? Потому что все остальное не работает. Вот несколько ситуаций, когда совершенно правильный код может привести к ошибкам сервера:

  • клиент завершил запрос раньше времени, а приложение все еще считывало входящие данные

  • сервер базы данных был перегружен и не смог обработать запрос

  • файловая система заполнена

  • разбился жесткий диск

  • перегрузка внутреннего сервера

  • программная ошибка в используемой вами библиотеке

  • сетевое подключение сервера к другой системе не удалось

И это лишь малая часть проблем, с которыми вы можете столкнуться. Как же нам справиться с подобными проблемами? По умолчанию, если ваше приложение работает в производственном режиме, Flask отобразит для вас очень простую страницу и запишет исключение в журнал logger.

Но вы можете сделать больше, и мы расскажем о некоторых лучших настройках для работы с ошибками.

Инструменты протоколирования ошибок

Отправка писем об ошибках, даже если речь идет только о критических ошибках, может стать непосильной, если на ошибку попадает достаточно много пользователей, а файлы журнала, как правило, никогда не просматриваются. Вот почему мы рекомендуем использовать Sentry для работы с ошибками приложений. Он доступен как проект с открытым исходным кодом on GitHub, а также доступен как hosted version, который вы можете попробовать бесплатно. Sentry объединяет дубликаты ошибок, захватывает полную трассировку стека и локальные переменные для отладки, а также отправляет вам письма на основе новых ошибок или пороговых значений частоты.

Для использования Sentry вам необходимо установить клиент sentry-sdk с дополнительными зависимостями flask:

$ pip install sentry-sdk[flask]

А затем добавьте это в ваше приложение Flask:

import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

sentry_sdk.init('YOUR_DSN_HERE',integrations=[FlaskIntegration()])

Значение YOUR_DSN_HERE необходимо заменить на значение DSN, полученное при установке Sentry.

После установки сбои, приводящие к внутренней ошибке сервера, автоматически сообщаются в Sentry, откуда вы можете получать уведомления об ошибках.

Последующие чтения:

Обработчики ошибок

Вы можете захотеть показывать пользователю пользовательские страницы ошибок при возникновении ошибки. Это можно сделать, зарегистрировав обработчики ошибок.

Обработчик ошибок - это обычная функция представления, которая возвращает ответ, но вместо того, чтобы быть зарегистрированной для маршрута, она регистрируется для исключения или кода состояния HTTP, который может быть получен при попытке обработать запрос.

Регистрация

Зарегистрируйте обработчики, украсив функцию символом errorhandler(). Или используйте register_error_handler(), чтобы зарегистрировать функцию позже. Не забудьте установить код ошибки при возврате ответа.

@app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e):
    return 'bad request!', 400

# or, without the decorator
app.register_error_handler(400, handle_bad_request)

Подклассы werkzeug.exceptions.HTTPException типа BadRequest и их HTTP-коды взаимозаменяемы при регистрации обработчиков. (BadRequest.code == 400)

Нестандартные HTTP-коды не могут быть зарегистрированы кодом, поскольку они не известны Werkzeug. Вместо этого определите подкласс HTTPException с соответствующим кодом, зарегистрируйте и поднимите этот класс исключений.

class InsufficientStorage(werkzeug.exceptions.HTTPException):
    code = 507
    description = 'Not enough storage space.'

app.register_error_handler(InsufficientStorage, handle_507)

raise InsufficientStorage()

Обработчики могут быть зарегистрированы для любого класса исключений, а не только для подклассов HTTPException или кодов состояния HTTP. Обработчики могут быть зарегистрированы для определенного класса или для всех подклассов родительского класса.

Работа с

Когда Flask ловит исключение при обработке запроса, оно сначала ищется по коду. Если для кода не зарегистрирован обработчик, его ищут по иерархии классов; выбирается наиболее специфичный обработчик. Если обработчик не зарегистрирован, подклассы HTTPException показывают общее сообщение о своем коде, а другие исключения преобразуются в общее сообщение 500 Internal Server Error.

Например, если возникает экземпляр ConnectionRefusedError, а обработчик зарегистрирован для ConnectionError и ConnectionRefusedError, то для генерации ответа вызывается более специфический обработчик ConnectionRefusedError с экземпляром исключения.

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

Общие обработчики исключений

Можно зарегистрировать обработчики ошибок для очень общих базовых классов, таких как HTTPException или даже Exception. Однако имейте в виду, что они будут ловить больше, чем вы ожидаете.

Обработчик ошибок HTTPException может быть полезен, например, для превращения HTML-страниц ошибок по умолчанию в JSON. Однако этот обработчик будет срабатывать при ошибках, которые вы не вызываете напрямую, таких как 404 и 405 ошибки при маршрутизации. Убедитесь, что вы тщательно разработали обработчик, чтобы не потерять информацию об ошибке HTTP.

from flask import json
from werkzeug.exceptions import HTTPException

@app.errorhandler(HTTPException)
def handle_exception(e):
    """Return JSON instead of HTML for HTTP errors."""
    # start with the correct headers and status code from the error
    response = e.get_response()
    # replace the body with JSON
    response.data = json.dumps({
        "code": e.code,
        "name": e.name,
        "description": e.description,
    })
    response.content_type = "application/json"
    return response

Обработчик ошибок для Exception может показаться полезным для изменения того, как все ошибки, даже необработанные, представляются пользователю. Однако это аналогично тому, как если бы вы сделали except Exception: в Python, он будет перехватывать все необработанные ошибки, включая все коды состояния HTTP. В большинстве случаев будет безопаснее зарегистрировать обработчики для более специфических исключений. Поскольку экземпляры HTTPException являются допустимыми ответами WSGI, вы также можете передавать их напрямую.

from werkzeug.exceptions import HTTPException

@app.errorhandler(Exception)
def handle_exception(e):
    # pass through HTTP errors
    if isinstance(e, HTTPException):
        return e

    # now you're handling non-HTTP exceptions only
    return render_template("500_generic.html", e=e), 500

Обработчики ошибок по-прежнему соблюдают иерархию классов исключений. Если вы зарегистрируете обработчики для HTTPException и Exception, обработчик Exception не будет обрабатывать подклассы HTTPException, поскольку обработчик HTTPException более специфичен.

Необработанные исключения

Если для исключения не зарегистрирован обработчик ошибок, вместо него будет возвращена внутренняя ошибка сервера 500. Информацию о таком поведении см. в flask.Flask.handle_exception().

Если для InternalServerError зарегистрирован обработчик ошибок, то будет вызван именно он. Начиная с версии Flask 1.1.0, этому обработчику ошибок всегда будет передаваться экземпляр InternalServerError, а не исходная необработанная ошибка. Исходная ошибка доступна в виде e.original_exception. До версии Werkzeug 1.0.0 этот атрибут будет существовать только для необработанных ошибок, для совместимости используйте getattr для получения доступа к нему.

@app.errorhandler(InternalServerError)
def handle_500(e):
    original = getattr(e, "original_exception", None)

    if original is None:
        # direct 500 error, such as abort(500)
        return render_template("500.html"), 500

    # wrapped unhandled error
    return render_template("500_unhandled.html", e=original), 500

Ведение журнала

См. Ведение журнала для получения информации о том, как регистрировать исключения, например, отправляя их по электронной почте администраторам.

Отладка ошибок приложения

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

Если есть сомнения, запускайте вручную

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

Работа с отладчиками

Чтобы копнуть глубже, возможно, отследить выполнение кода, Flask предоставляет отладчик из коробки (см. Режим отладки). Если вы хотите использовать другой отладчик Python, обратите внимание, что отладчики мешают друг другу. Чтобы использовать ваш любимый отладчик, необходимо задать некоторые параметры:

  • debug - включать ли режим отладки и перехватывать исключения

  • use_debugger - использовать ли внутренний отладчик Flask

  • use_reloader - следует ли перезагрузить и форкнуть процесс, если модули были изменены

debug должно быть True (т.е. исключения должны быть пойманы), чтобы два других параметра имели какое-либо значение.

Если вы используете Aptana/Eclipse для отладки, вам нужно установить и use_debugger, и use_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")

    use_debugger = app.debug and not(app.config.get('DEBUG_WITH_APTANA'))
    app.run(use_debugger=use_debugger, debug=app.debug,
            use_reloader=use_debugger, host='0.0.0.0')