- deps: авторизация через app.services.security (JWT/OIDC), без oidc fallback - main: AUTH_DEPENDENCY для роутеров, RequestLoggerMiddleware, убран on_event(startup) - attachments: защита path traversal, проверка владельца/authority - docker-compose: SECRET_KEY обязателен, отдельная БД keycloak-db - middleware: ужесточён CSP (без unsafe-eval в prod, без api.openai.com) - api-client: токен только в памяти, без sessionStorage - cert_applications: _next_number с SELECT FOR UPDATE - Удалён lib/api.ts, импорты на @/lib/api/api-client - docs ERROR_HANDLING: aircraftApi.list(), middleware __init__.py Co-authored-by: Cursor <cursoragent@cursor.com>
9.3 KiB
9.3 KiB
Документация по обработке ошибок
Архитектура обработки ошибок
Система использует многоуровневую обработку ошибок:
- Error Boundary - для ошибок React компонентов
- API Error Handler - для ошибок API routes
- User-Friendly Messages - понятные сообщения для пользователей
- Sentry Integration - мониторинг критических ошибок
- Logging - логирование всех ошибок
Error Boundary
Использование
Error Boundary автоматически обернут вокруг всего приложения в app/layout.tsx.
Для обертки отдельных компонентов:
import { ErrorBoundary } from '@/components/ErrorBoundary';
function MyComponent() {
return (
<ErrorBoundary
fallback={<div>Произошла ошибка</div>}
onError={(error, errorInfo) => {
console.error('Component error:', error);
}}
>
<YourComponent />
</ErrorBoundary>
);
}
Кастомный fallback
<ErrorBoundary
fallback={
<div>
<h2>Ошибка в компоненте</h2>
<button onClick={() => window.location.reload()}>
Обновить страницу
</button>
</div>
}
>
<YourComponent />
</ErrorBoundary>
API Error Handler
Использование в API routes
import { handleError, AppError, Errors } from '@/lib/error-handler';
export async function POST(request: Request) {
try {
// Ваш код
if (!authorized) {
throw Errors.UNAUTHORIZED;
}
} catch (error) {
return handleError(error, {
path: '/api/endpoint',
method: 'POST',
userId: 'user-id',
});
}
}
Создание кастомных ошибок
throw new AppError(
'Сообщение об ошибке',
'ERROR_CODE',
400,
{ additional: 'data' }
);
Понятные сообщения для пользователей
Автоматическое преобразование
Система автоматически преобразует технические ошибки в понятные сообщения:
import { getUserFriendlyError } from '@/lib/errors/user-friendly-messages';
try {
// Код, который может выбросить ошибку
} catch (error) {
const friendly = getUserFriendlyError(error);
// friendly.title - заголовок
// friendly.message - понятное сообщение
// friendly.type - тип (error/warning/info)
// friendly.action - рекомендуемое действие
}
С контекстом
import { getContextualErrorMessage } from '@/lib/errors/user-friendly-messages';
const friendly = getContextualErrorMessage(error, {
action: 'сохранении данных',
resource: 'воздушное судно',
operation: 'create',
});
Компоненты для отображения ошибок
ErrorDisplay
import ErrorDisplay from '@/components/ErrorDisplay';
<ErrorDisplay
title="Ошибка загрузки"
message="Не удалось загрузить данные"
type="error"
onRetry={() => refetch()}
onClose={() => setError(null)}
showDetails={true}
details={error.stack}
/>
ErrorAlert (улучшенный)
import ErrorAlert from '@/components/ErrorAlert';
<ErrorAlert
message="Ошибка"
error={error} // Автоматически преобразуется в понятное сообщение
onRetry={() => retry()}
onClose={() => clearError()}
/>
Хук useErrorHandler
import { useErrorHandler } from '@/hooks/useErrorHandler';
function MyComponent() {
const { error, userFriendlyError, handleError, clearError, hasError } = useErrorHandler({
onError: (error) => {
// Дополнительная обработка
},
logError: true,
sendToSentry: true,
});
const fetchData = async () => {
try {
// Загрузка данных
} catch (err) {
handleError(err, {
action: 'загрузке данных',
resource: 'воздушные суда',
});
}
};
if (hasError && userFriendlyError) {
return (
<ErrorDisplay
title={userFriendlyError.title}
message={userFriendlyError.message}
type={userFriendlyError.type}
onRetry={fetchData}
/>
);
}
return <div>...</div>;
}
Интеграция с Sentry
Настройка
- Создайте аккаунт на sentry.io
- Создайте проект и получите DSN
- Добавьте в
.env.local:NEXT_PUBLIC_SENTRY_DSN=your-dsn-here SENTRY_ORG=your-org SENTRY_PROJECT=your-project
Использование
import { captureException, captureMessage, setUserContext } from '@/lib/monitoring/sentry';
// Отправка исключения
try {
// Код
} catch (error) {
captureException(error, {
component: 'MyComponent',
action: 'fetchData',
});
}
// Отправка сообщения
captureMessage('Важное событие', 'warning', {
userId: '123',
});
// Установка контекста пользователя
setUserContext('user-id', 'user@example.com', 'username');
Автоматическая отправка
Критические ошибки автоматически отправляются в Sentry:
- Ошибки базы данных
- Ошибки подключения
- Ошибки таймаута
- Ошибки 500+
Логирование ошибок
Все ошибки логируются через Winston:
import { logError, logSecurity, logAudit } from '@/lib/logger';
// Обычная ошибка
logError('Ошибка загрузки данных', error, {
userId: '123',
component: 'AircraftList',
});
// Ошибка безопасности
logSecurity('Попытка несанкционированного доступа', {
ip: '192.168.1.1',
path: '/api/admin',
});
// Аудит
logAudit('CREATE_AIRCRAFT', 'aircraft', {
userId: '123',
aircraftId: 'RA-12345',
});
Логи сохраняются в:
logs/error.log- только ошибкиlogs/combined.log- все логи
Коды ошибок
Используйте предопределенные коды ошибок:
import { ErrorCode } from '@/lib/errors/error-codes';
throw new AppError('Сообщение', ErrorCode.NETWORK_ERROR, 500);
Доступные коды:
NETWORK_ERROR- ошибка сетиVALIDATION_ERROR- ошибка валидацииUNAUTHORIZED- не авторизованFORBIDDEN- доступ запрещенNOT_FOUND- не найденоDATABASE_ERROR- ошибка БДRATE_LIMIT_EXCEEDED- превышен лимит- И другие...
Best Practices
- Всегда обрабатывайте ошибки - используйте try/catch
- Логируйте с контекстом - добавляйте информацию о действии
- Показывайте понятные сообщения - используйте
getUserFriendlyError - Используйте Error Boundary - для изоляции ошибок компонентов
- Отправляйте критические ошибки в Sentry - для мониторинга
- Не показывайте технические детали - только в режиме разработки
Примеры
Полный пример компонента с обработкой ошибок
'use client';
import { useState } from 'react';
import { useErrorHandler } from '@/hooks/useErrorHandler';
import ErrorDisplay from '@/components/ErrorDisplay';
import { aircraftApi } from '@/lib/api/api-client';
export default function AircraftList() {
const { error, userFriendlyError, handleError, clearError, hasError } = useErrorHandler();
const [aircraft, setAircraft] = useState([]);
const [loading, setLoading] = useState(false);
const fetchAircraft = async () => {
try {
setLoading(true);
clearError();
const data = await aircraftApi.list();
setAircraft(data.items ?? []);
} catch (err) {
handleError(err, {
action: 'загрузке списка воздушных судов',
resource: 'aircraft',
});
} finally {
setLoading(false);
}
};
if (hasError && userFriendlyError) {
return (
<ErrorDisplay
title={userFriendlyError.title}
message={userFriendlyError.message}
type={userFriendlyError.type}
onRetry={fetchAircraft}
/>
);
}
if (loading) return <div>Загрузка...</div>;
return (
<div>
{aircraft.map(a => <div key={a.id}>{a.registrationNumber}</div>)}
</div>
);
}
Дата создания: 2025-01-21
Версия: 1.0