107 lines
3.7 KiB
Markdown
107 lines
3.7 KiB
Markdown
# Обработка ошибок
|
||
|
||
## Матрица ошибок по слоям
|
||
|
||
| Слой | Что делаем | Пример |
|
||
|------|-----------|--------|
|
||
| **API** | HTTPException с detail и status_code | `raise HTTPException(404, detail="Not found")` |
|
||
| **Services** | Бизнес-исключения без HTTP-статусов | `raise IdeaNotFoundError(idea_id)` |
|
||
| **Integrations** | try/except с fallback | `return AIResult(success=False, error=...)` |
|
||
| **Data/DB** | Ошибки не всплывают выше | Ловим в сервисе |
|
||
|
||
---
|
||
|
||
## Иерархия исключений
|
||
|
||
```python
|
||
# app/core/exceptions.py
|
||
|
||
class AppError(Exception):
|
||
"""Базовое исключение приложения."""
|
||
def __init__(self, message: str, details: dict | None = None):
|
||
self.message = message
|
||
self.details = details or {}
|
||
|
||
class NotFoundError(AppError):
|
||
"""Ресурс не найден."""
|
||
def __init__(self, resource: str, resource_id: str):
|
||
super().__init__(f"{resource} not found: {resource_id}", {"resource": resource, "id": resource_id})
|
||
|
||
class ValidationError(AppError):
|
||
"""Ошибка валидации."""
|
||
def __init__(self, field: str, message: str):
|
||
super().__init__(message, {"field": field})
|
||
|
||
class AuthError(AppError):
|
||
"""Ошибка аутентификации."""
|
||
def __init__(self, message: str = "Authentication failed"):
|
||
super().__init__(message)
|
||
|
||
class ForbiddenError(AppError):
|
||
"""Нет прав."""
|
||
def __init__(self, message: str = "Access denied"):
|
||
super().__init__(message)
|
||
```
|
||
|
||
---
|
||
|
||
## Fallback Pattern (для внешних вызовов)
|
||
|
||
```python
|
||
# Паттерн для всех вызовов внешних API
|
||
|
||
async def call_with_fallback(provider: AIProvider, prompt: str) -> AIResult:
|
||
max_retries = 2
|
||
last_error = None
|
||
|
||
for attempt in range(max_retries + 1):
|
||
try:
|
||
result = await asyncio.wait_for(
|
||
provider.analyze(prompt),
|
||
timeout=10.0
|
||
)
|
||
if result.success:
|
||
return result
|
||
last_error = result
|
||
except asyncio.TimeoutError:
|
||
last_error = AIResult(success=False, error="Timeout")
|
||
except Exception as e:
|
||
last_error = AIResult(success=False, error=str(e))
|
||
|
||
if attempt < max_retries:
|
||
await asyncio.sleep(2 if attempt == 0 else 5)
|
||
|
||
return last_error
|
||
```
|
||
|
||
---
|
||
|
||
## Логирование ошибок
|
||
|
||
| Уровень | Когда | Пример |
|
||
|---------|-------|--------|
|
||
| DEBUG | Входящие параметры | `Request params: id=123` |
|
||
| INFO | Успешная операция | `User created: id=456` |
|
||
| WARNING | Timeout, retry | `Yandex GPT timeout, retry 1/2` |
|
||
| ERROR | Ошибка внешнего API | `GigaChat 500: Internal error` |
|
||
| CRITICAL | Исчерпаны все retry | `All AI providers failed for idea 789` |
|
||
|
||
---
|
||
|
||
## Graceful Degradation
|
||
|
||
Когда внешний сервис недоступен:
|
||
|
||
1. **DB недоступна** → 503 Service Unavailable
|
||
2. **Redis недоступен** → работаем без кэша (log WARNING)
|
||
3. **AI провайдер недоступен** → возвращаем fallback результат
|
||
4. **Celery недоступен** → выполняем задачу синхронно
|
||
|
||
---
|
||
|
||
## [ASK] Вопросы по обработке ошибок
|
||
|
||
- Нужны ли пользовательские исключения для всех бизнес-сценариев?
|
||
- Нужен ли sentry или аналогичный мониторинг ошибок?
|
||
- Как обрабатывать ошибки валидации на фронтенде?
|