JavaRush /Курси /Модуль 4: FastAPI /Налаштування глобального обробника помилок у FastAPI

Налаштування глобального обробника помилок у FastAPI

Модуль 4: FastAPI
Рівень 20 , Лекція 8
Відкрита

Уяви: твій додаток чудово справляється зі своїм завданням, все працює, користувачі задоволені. Але раптом — бац! — прилітає помилка, яку ти не передбачив. Може, клієнт відправив несподівані дані, а може, ти забув перевірити якийсь тип — і все, додаток падає або повертає щось дивне.

Щоб такі сюрпризи не перетворювались на кошмар, потрібен глобальний обробник помилок. Він підстрахує тебе на випадок, якщо десь щось пішло не так і локальна обробка не спрацювала. Такий обробник може красиво повернути зрозуміле повідомлення користувачу, записати помилку в лог для аналізу, і, найголовніше, не вивести назовні чутливі деталі на кшталт відлагоджувальної інформації або трасу.


Як працює глобальний обробник у FastAPI?

FastAPI надає зручний спосіб налаштування глобального обробника помилок через спеціальний декоратор @app.exception_handler. Він дозволяє зареєструвати обробник для всіх або певних типів винятків.

Наводимо приклад глобального обробника.

Припустимо, у нас є API для роботи з нотатками:


from fastapi import FastAPI

app = FastAPI()

@app.get("/notes/{note_id}")
async def get_note(note_id: int):
    # Тут може виникнути помилка, якщо ID недопустимий
    if note_id < 0:
        raise ValueError("ID повинен бути додатнім числом")
    return {"note_id": note_id, "content": "Нотатка знайдена!"}

Якщо користувач передасть від'ємний note_id, Python підкине виняток ValueError, який, якщо його не обробити, покаже користувачу нечіткий текст помилки. Тепер ми налаштуємо глобальний обробник для таких ситуацій.

Налаштування глобального обробника через @app.exception_handler

Глобальні обробники налаштовуються таким чином:


from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

# Глобальний обробник для винятків ValueError
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
    # Повертаємо JSON-відповідь з описом помилки
    return JSONResponse(
        status_code=400,
        content={"error": "Некоректний запит", "details": str(exc)},
    )

@app.get("/notes/{note_id}")
async def get_note(note_id: int):
    if note_id < 0:
        raise ValueError("ID повинен бути додатнім числом")
    return {"note_id": note_id, "content": "Нотатка знайдена!"}

Тепер, якщо користувач передасть від'ємний note_id, він отримає акуратну JSON-відповідь з описом проблеми:


{
  "error": "Некоректний запит",
  "details": "ID повинен бути додатнім числом"
}

Обробка всіх необроблених винятків

Що, якщо помилка не належить до ValueError? Для цього в FastAPI можна створити універсальний обробник, який перехоплює всі необроблені винятки.


@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    # Логуємо помилку (наприклад, з детальним traceback)
    print(f"Unhandled error: {exc}")
    return JSONResponse(
        status_code=500,
        content={"error": "Сталася внутрішня помилка сервера"}
    )

Тепер будь-який виняток, який не впіймали інші обробники, буде коректно опрацьований цим глобальним обробником.

Обробка кастомних винятків

Ми можемо створити власний клас винятків і обробник для нього. Наприклад:


class BusinessLogicError(Exception):
    def __init__(self, message: str):
        self.message = message

@app.exception_handler(BusinessLogicError)
async def business_logic_error_handler(request: Request, exc: BusinessLogicError):
    return JSONResponse(
        status_code=400,
        content={"error": "Помилка бізнес-логіки", "details": exc.message},
    )

@app.get("/raise-business-error")
async def raise_error():
    raise BusinessLogicError("Сталося порушення бізнес-правил")

При виклику ендпоінта /raise-business-error користувач отримає зрозуміле повідомлення:


{
  "error": "Помилка бізнес-логіки",
  "details": "Сталося порушення бізнес-правил"
}

Інтеграція з логуванням

Глобальний обробник часто використовують для запису критичних помилок у журнали логів. Це можна зробити за допомогою вбудованого модуля Python logging:


import logging

logger = logging.getLogger(__name__)

@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    # Логуємо помилку
    logger.error(f"Unhandled error: {exc}", exc_info=True)
    return JSONResponse(
        status_code=500,
        content={"error": "Сталася внутрішня помилка сервера"}
    )

Порада: налаштуй ротацію логів і підключи моніторингові системи (наприклад, Sentry або ELK Stack).


Перехоплення помилок зовнішніх API

Працюючи з зовнішніми API, можна комбінувати глобальний обробник з бібліотекою httpx. Наприклад, якщо сторонній сервіс повертає помилку:


import httpx

@app.get("/external-api")
async def call_external_api():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/resource")
        if response.status_code != 200:
            raise ValueError("Помилка зовнішнього API")
        return response.json()

Якщо виникне помилка, глобальний обробник для ValueError забезпечить її обробку. Це робить роботу з зовнішніми сервісами більш передбачуваною.


Типові помилки при налаштуванні глобального обробника

  1. Неопрацьовані винятки: якщо ти забув налаштувати обробник для винятків типу Exception, неприємні помилки можуть доходити до кінцевих користувачів.
  2. Зайве повернення інформації: ніколи не повертай стеки помилок у JSON — це може містити чутливу інформацію. Користувачам достатньо отримати коротке повідомлення.
  3. Логування з відлагоджувальною інформацією: не забувай використовувати рівні логування (DEBUG, INFO, ERROR), щоб твої журнали були читабельними.

Тепер твій FastAPI-додаток підготовлений до обробки помилок на всіх рівнях: і локально, і глобально. Завдяки цьому твій API буде стійким, передбачуваним і легким для відладки.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ