Перейти к содержанию

Логирование

Сервер и агент оба логируют структурно через zap — в продакшене JSON, одна строка на событие. Эта страница — про формат, уровни и где физически искать логи каждого компонента.

Сервер

Формат и уровень

ALATYR_LOG_LEVEL (по умолчанию info; допустимые значения debug/info/warn/error) и ALATYR_DEV_MODE вместе определяют кодировщик:

  • dev_mode=false (прод) — продакшен-профиль zap (JSON-encoder, время в ISO8601). Один JSON-объект на строку stdout — рассчитан на сбор логовым агрегатором (Loki, ELK, CloudWatch и т.п.), не на чтение человеком в терминале напрямую.
  • dev_mode=true — dev-профиль zap с цветным human-readable выводом.

Строка starting alatyr-server, которую сервер пишет при старте, несёт version, commit, built_at — тот же билд, что отдаёт GET /api/v1/version (см. раздел «Публичные и служебные» на странице REST API), так что любой лог-стрим прослеживается до конкретного бинаря.

Структурная корреляция: request_id и serial

Каждый запрос получает поле request_id: входящий заголовок X-Request-ID используется как есть, либо генерируется новый UUID; сервер возвращает то же значение в заголовке ответа. Все строки лога, относящиеся к одному запросу — включая финальную access-строку — несут одинаковый request_id.

Как только запрос идентифицирует конкретное устройство (enroll, enroll/user, enroll/ssh, enroll/ssh-key), к последующим строкам того же запроса добавляется поле serial. С этого момента поиск по serial в Loki/ELK восстанавливает всю историю конкретного устройства поперёк множества запросов, тогда как request_id связывает строки только одного запроса.

На каждый завершённый запрос пишется одна структурная access-строка с полями: status, method, path (без query-строки — там могут быть токены), route (шаблон вида /api/v1/devices/:serial, низкая кардинальность, без PII), latency_ms, client_ip (первый адрес из X-Forwarded-For, иначе адрес соединения), bytes, и, если запрос аутентифицирован — actor/actor_role. Уровень строки следует за исходом: 5xx → Error, 4xx → Warn, иначе Info. Исключение — высокочастотные агентские polling-роуты (/requests/:id/status, /checkin, /bundle-version, /certificate): их успешные вызовы логируются на Debug, чтобы не захламлять Info-поток, а любой 4xx/5xx на этих же роутах всё равно всплывает на Warn/Error. GET /health не логируется вовсе — probe каждые несколько секунд забил бы сигнал шумом.

Паника в хэндлере перехватывается и записывается как структурная строка panic recovered (method, path, стектрейс) на уровне Error; клиент при этом получает generic 500 — вместо голого текстового стектрейса в stderr, который сломал бы предположение «один JSON-стрим на stdout».

Пример строки (прод, JSON)

{"level":"info","ts":"2026-08-06T10:15:03.120Z","msg":"request",
 "request_id":"3fa2...c91e","serial":"C02X1234ABCD",
 "status":200,"method":"POST","path":"/api/v1/requests/.../checkin",
 "route":"/api/v1/requests/:id/checkin","latency_ms":12.4,
 "client_ip":"10.0.4.17","bytes":128,
 "actor":"agent","actor_role":""}

Точный набор полей одной строки зависит от того, что уже известно на момент записи (serial есть не у каждого запроса — только у тех, где устройство уже идентифицировано).

Агент

Логгер агента — тоже zap: по умолчанию JSON-encoder на уровне info, при флаге --verbose (или его платформенном эквиваленте) — human-readable console-encoder на debug. Вывод всегда идёт на stdout и, если передан --state-file, дублируется («tee») в лог-файл рядом с state.json.

Где физически искать лог-файл — по платформе (детали установки — в Установка):

ОС Куда смотреть
Linux journalctl -u alatyr-agent.service -f (агент запускается как systemd oneshot по таймеру, свой файл не пишет — весь вывод идёт в journal)
Windows C:\ProgramData\AlatyrAgent\agent.log (плюс история запусков — Task Scheduler → Library → AlatyrAgent)
macOS ~/Library/Logs/AlatyrAgent/agent.log (per-user LaunchAgent; отдельно ~/Library/Logs/AlatyrAgent/sshagent.log — для процесса ssh-agent)

На машинах, где агент устанавливался раньше (до текущей версии), каталог может по-прежнему называться WifiCertAgent.

Куда смотреть при разборе инцидента

  1. Возьмите request_id из заголовка ответа X-Request-ID (или из строки в клиентском/агентском логе, если запрос делал агент — он получает тот же id обратно).
  2. Найдите по нему все серверные строки — они образуют полную историю одного HTTP-запроса, включая финальную access-строку с итоговым статусом и латентностью.
  3. Если инцидент касается конкретного устройства — ищите по serial, он связывает строки поперёк множества запросов и заявок этого устройства.
  4. GET /api/v1/audit — не то же самое, что структурные логи: это отдельный, персистентный в БД журнал административных действий (approve/reject/revoke, изменение ролей и т.п.), а не поток HTTP-запросов. Подробнее об аудите — в Администрирование.

См. также Диагностика и ограничения — известные ограничения и что делать при конкретных симптомах.