# Логи

Структурированный 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`. Предел держит само сообщение, а не
  обрезка на стороне логирующего: обрезка живёт в другом месте и о новой ошибке
  разбора не узнает.
