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