# Логи Структурированный JSON (`log/slog`) в stdout, один формат для dev и prod. Сбор и ротацию делает окружение. - `msg` — короткая константа в нижнем регистре, категория события (`delivery accepted`, `parse failed`). Данные — атрибутами, не в тексте. Подсистему выносим в поле `capability` (`ingest`/`parse`/`query`), не в префикс сообщения. - **Уровень — это адресат, а не громкость поломки:** | Уровень | Кому | Примеры | |---|---|---| | `DEBUG` | разработчику при отладке | `/healthz`, тела запросов, шаги разбора | | `INFO` | владельцу, аудит постфактум | принята доставка, разбор завершён, старт | | `WARN` | владельцу, «может стать проблемой» | точка не разобрана, незнакомая форма метрики | | `ERROR` | владельцу, в разбор | не записался архив, сбой БД | - Невалидный ввод от отправителя — `DEBUG`, а не `ERROR`: это норма, разбирать нечего. `WARN` ≠ «ничего страшного», `WARN` = «может стать проблемой». - Событийное → `INFO`, рутинно-частое (healthcheck, поллинг) → `DEBUG`. - **Либо лог, либо возврат, не оба.** Промежуточные слои только оборачивают и возвращают. Ошибка логируется **один раз**, на границе доменного слоя, которая определяет исход операции (`ingest`) — не в транспорте. Транспорт переводит ошибку в ответ и не логирует повторно. - Ошибка — атрибутом: `log.Error("parse failed", "error", err, "delivery_id", id)`. - Время в логах — UTC, RFC 3339 с долями секунды. - Корреляция — по `delivery_id` (ULID), отдельный `trace_id` не заводим. - **Секреты в логи не попадают**: токены приёма и чтения, `Authorization`. При сомнении логируем факт наличия, не значение. - Данные о здоровье — чувствительные. Тела запросов пишем только на `DEBUG` и с обрезкой по длине. - **Текст ошибки разбора не содержит значений из входа** — только род токена (словарём JSON, не именем типа языка) и смещение. Инвариант выше обходится одним `fmt.Errorf("%v", tok)`: тело в 8 МиБ дало текст ошибки в 8 МиБ, и он уехал атрибутом `error` на уровень `WARN`. Предел держит само сообщение, а не обрезка на стороне логирующего: обрезка живёт в другом месте и о новой ошибке разбора не узнает.