Грамотное покрытие кода обработкой ошибок (try/catch/finally) и структурным логированием без «тихих» падений. Куда ставить try/catch (границы I/O/внешних вызовов/парсинга/транзакций) и куда НЕ ставить, нормализация ошибок (typed errors, cause, stack, unknown в catch), async/finally/cleanup, ретраи/таймауты для транзиентных сбоев; грамотная расстановка логов — уровни, контекст, correlation id, лог один раз на границе, без секретов/PII. Use при добавлении/ревью error handling и логирования в бэ...
Installs into .claude/skills of the current project.
Are you the author of Error Handling?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/vitammiin-error-handling-agent-vorcl-flow)
---
name: error-handling
description: Грамотное покрытие кода обработкой ошибок (try/catch/finally) и структурным логированием без «тихих» падений. Куда ставить try/catch (границы I/O/внешних вызовов/парсинга/транзакций) и куда НЕ ставить, нормализация ошибок (typed errors, cause, stack, unknown в catch), async/finally/cleanup, ретраи/таймауты для транзиентных сбоев; грамотная расстановка логов — уровни, контекст, correlation id, лог один раз на границе, без секретов/PII. Use при добавлении/ревью error handling и логирования в бэке (Node/Fastify) и фронте (React).
version: 1.0.0
---
# Навык: Error Handling & Logging
Цель — **надёжный** код без «тихих» падений и с **грамотной расстановкой логов**: понятно, что упало, почему и с каким контекстом — без шума и утечек секретов.
## Куда ставить try/catch (и куда НЕ ставить)
- **Ставь на границах**, где ошибка ожидаема и её есть чем обработать: I/O (сеть, файлы, БД), внешние API, парсинг/десериализация (`JSON.parse`, zod), транзакции, места, где нужен cleanup.
- **НЕ оборачивай всё подряд.** Программные ошибки (баги, нарушенные инварианты) пусть всплывают до глобального обработчика — не глотай их локально. Один осмысленный `try` на операцию лучше, чем `try` вокруг каждой строки.
- **Не дублируй обработку:** ловишь и пробрасываешь — не логируй на каждом слое (см. «Логи один раз»).
## Никаких «тихих» падений
- **Пустой `catch {}` запрещён.** В catch всегда одно из трёх: (1) **обработать** — осмысленный фолбэк + лог; (2) **пробросить** (`throw`), возможно обогатив: `throw new AppError('...', { cause: error })`; (3) **преобразовать** в доменную ошибку/`Result`. «Проглотил и пошёл дальше» — только с явным комментарием, почему это безопасно.
- Не теряй причину: `new Error(msg, { cause })` сохраняет **ссылку** на исходную ошибку (её `message`/`stack`/тип доступны через `err.cause`), но **не сшивает** стеки в один трейс — логгер должен явно сериализовать цепочку `cause` (pino err-serializer это делает; `console.log` без `util.inspect` — нет).
- В прикладном коде бросай **только `Error`** (или наследников), иначе теряются `stack` и тип. Исключение: фреймворки бросают спец-объекты для control-flow (Next `redirect()`/`notFound()`) — их **не** трогай и не проглатывай в `catch` без re-throw.
- В TypeScript `catch (error)` имеет тип `unknown` — сузь перед использованием (`error instanceof Error ? … : …`), не обращайся к `error.message` вслепую.
## Нормализация ошибок
- Доменные классы: `class AppError extends Error { code; httpStatus; context }`. Отличай **operational** (ожидаемые: 4xx, недоступность внешнего) от **programmer** (баги → 5xx, алерт).
- На границе наружу (HTTP) — единый error-handler: маппинг ошибки в статус + **безопасное** тело (без стека/внутренностей клиенту). Fastify — `setErrorHandler`; Next App Router — `error.tsx` на уровне сегмента, для ошибок корневого layout нужен `global-error.tsx`, а в route handlers/Server Actions ошибки нормализуй явно (структурированный ответ/`throw` с маппингом), не полагаясь на `error.tsx`.
- Валидация входа (zod) на границе → предсказуемые 400, а не 500 глубоко внутри.
- **i18n сообщений:** наружу отдавай стабильный машинный `code` (+ параметры), а человекочитаемый текст **локализуй** на границе по локали запроса; **логи не локализуй** (один язык, обычно English). Подробнее — скилл **i18n**.
## Async, finally, cleanup
- `await` внутри `try`; не смешивай `.then().catch()` с `try/await` бессистемно.
- Освобождение ресурсов (коннект, файл, лок, таймер) — в `finally`, чтобы не текло при ошибке.
- Параллельно: `Promise.allSettled` — когда важны все результаты; `Promise.all` — когда любой сбой должен прервать. На уровне процесса лови `unhandledRejection`/`uncaughtException`, но **не продолжай работу**: состояние процесса после них неопределённо — залогируй и **заверши процесс** (graceful shutdown → `exit 1`), пусть оркестратор/PM2/Docker перезапустит. Это не «глобальный catch, чтобы работать дальше».
## Транзиентные сбои: ретраи/таймауты
- Внешние вызовы — с **таймаутом** (`AbortSignal.timeout`). Ретрай — только на **транзиентных** ошибках (сеть/таймаут/`5xx`/`429`), с экспоненциальным бэкоффом + jitter и лимитом попыток; на `4xx`/бизнес-ошибках не ретрай. **Мутации** ретрай только при **идемпотентности по эффекту** (idempotency key/дедуп на сервере), а не потому что «это просто повтор».
- Каскады — circuit breaker/деградация; не держи пользователя в бесконечном ожидании.
## Грамотная расстановка логов
- **Структурные логи** (JSON: pino/структурный logger), не голый `console.log` в проде. Поля: `level`, `msg`, `err` (message+stack+cause), `context` (ids, операция), `requestId`/correlation id.
- **Уровни:** `error` — сбой, требующий внимания; `warn` — аномалия с фолбэком; `info` — **значимые бизнес-события** (лог на каждый запрос — это отдельный access-log на выделенном канале/с сэмплингом, а не `info` по умолчанию, иначе шум); `debug` — детали диагностики (в проде выкл./по флагу). Не логируй всё на `error`.
- **Логируй один раз** — на слое, где есть контекст и где ошибка **обрабатывается** (обычно верхняя граница/handler). Пробрасываешь — **не** логируй здесь же (иначе дубли на каждом уровне).
- **Что логировать:** какая операция, ключевые id, длительность важных операций, исход; ошибку — целиком (`err` со `stack` и **цепочкой `cause`** — убедись, что сериализатор логгера разворачивает `cause`, иначе первопричина потеряется).
- **Что НЕ логировать:** пароли, токены, ключи, PII, чувствительные тела/полный payload без нужды — маскируй/редактируй. На горячих путях — сэмплируй.
- Логи не для управления потоком: не парси текст лога как сигнал.
## Фронтенд (React)
- **Error boundary** на дерево UI (класс-компонент или `react-error-boundary`): ловит **только** ошибки рендера/жизненного цикла/конструкторов дочерних компонентов — **не** ловит ошибки в event handlers, async (`setTimeout`/промисы), при SSR и в самом boundary. Показывает фолбэк, репортит.
- Ошибки хендлеров/async и данных (fetch/react-query) — вне boundary: `try/catch` в хендлере, состояние `error` у react-query (повтор, дружелюбный UI; не «пустой экран» и не стек пользователю), глобальный `window.onerror`/`onunhandledrejection`.
## Бэкенд (Node/Fastify)
- Единый `setErrorHandler` — маппинг доменных ошибок → статус + безопасное тело; логирование там же (один раз).
- Процессные обработчики (`unhandledRejection`, `uncaughtException`, сигналы) → лог + graceful shutdown (дренаж коннектов).
- Не «съедай» ошибки в middleware/репозиториях молча.
## Анти-паттерны (ищи и убирай)
- `catch (e) {}` / `catch (e) { console.log(e) }` без обработки/проброса; `catch`, возвращающий `null`/`undefined` и скрывающий сбой.
- `throw 'строка'`; потеря `cause`; логирование одной ошибки на 3 уровнях; лог секретов/PII; `try` вокруг кода, который не бросает; необработанные промис-реджекты; незакрытые ресурсы при ошибке.