Уяви: твій додаток чудово справляється зі своїм завданням, все працює, користувачі задоволені. Але раптом — бац! — прилітає помилка, яку ти не передбачив. Може, клієнт відправив несподівані дані, а може, ти забув перевірити якийсь тип — і все, додаток падає або повертає щось дивне.
Щоб такі сюрпризи не перетворювались на кошмар, потрібен глобальний обробник помилок. Він підстрахує тебе на випадок, якщо десь щось пішло не так і локальна обробка не спрацювала. Такий обробник може красиво повернути зрозуміле повідомлення користувачу, записати помилку в лог для аналізу, і, найголовніше, не вивести назовні чутливі деталі на кшталт відлагоджувальної інформації або трасу.
Як працює глобальний обробник у 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 забезпечить її обробку. Це робить роботу з зовнішніми сервісами більш передбачуваною.
Типові помилки при налаштуванні глобального обробника
- Неопрацьовані винятки: якщо ти забув налаштувати обробник для винятків типу
Exception, неприємні помилки можуть доходити до кінцевих користувачів. - Зайве повернення інформації: ніколи не повертай стеки помилок у JSON — це може містити чутливу інформацію. Користувачам достатньо отримати коротке повідомлення.
- Логування з відлагоджувальною інформацією: не забувай використовувати рівні логування (
DEBUG,INFO,ERROR), щоб твої журнали були читабельними.
Тепер твій FastAPI-додаток підготовлений до обробки помилок на всіх рівнях: і локально, і глобально. Завдяки цьому твій API буде стійким, передбачуваним і легким для відладки.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ