Логирование¶
Сервер и агент оба логируют структурно через 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. Если обработчик приложил к запросу
ошибку, рядом появляется поле errors — текст ошибки без тела запроса.
Уровень строки следует за
исходом: 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":0.0124,
"client_ip":"10.0.4.17","bytes":128,
"actor":"agent","actor_role":""}
latency_ms в проде измеряется в СЕКУНДАХ
Имя поля обещает миллисекунды, а кодировщик продакшен-профиля пишет
секунды: 0.0124 в примере выше — это 12,4 мс. В dev_mode=true
формат другой, строка вида 12.4ms. Пороги в запросах и алертах
задавайте по тому, что реально лежит в вашем потоке, а не по имени поля.
Точный набор полей одной строки зависит от того, что уже известно на
момент записи (serial есть не у каждого запроса — только у тех, где
устройство уже идентифицировано).
Агент¶
Логгер агента — тоже zap: по умолчанию JSON-encoder на уровне info,
при флаге --verbose (или его платформенном эквиваленте) —
human-readable console-encoder на debug. Вывод всегда идёт на
stdout и, если агенту передан --log-file, дублируется («tee») в этот
файл. Установщик передаёт его на всех платформах, поэтому файл есть и
искать его стоит именно там.
У агента две половины, и журналов тоже два¶
Это первое, что стоит знать при разборе: машинная половина (цели wifi,
общий enrollment_token) и пользовательская (user_mtls, ad_logon,
ssh, k8s) — разные процессы под разными учётными записями, и пишут
они в разные файлы. Симптом «цель не выдаётся» чаще живёт в
пользовательском журнале, а его как раз и не открывают.
| ОС | Машинная половина | Пользовательская половина |
|---|---|---|
| Linux | /var/log/alatyr-agent.log, он же journalctl -u alatyr-agent.service -f |
~/.local/state/alatyr-agent/user-agent.log (служба systemctl --user status alatyr-agent-user) |
| Windows | C:\ProgramData\AlatyrAgent\agent.log |
%LOCALAPPDATA%\AlatyrAgent\user-agent.log |
| macOS | — (машинной половины нет: агент целиком per-user) | ~/Library/Logs/AlatyrAgent/agent.log, рядом sshagent.log для процесса ssh-agent |
Обе службы Linux — резидентные, а не разовый запуск по таймеру:
через машинную половину пользовательская получает enrollment_token, и
живёт этот канал ровно столько, сколько живёт процесс. Поэтому
systemctl status отвечает на вопрос «работает ли агент» буквально, а
пустой журнал одной из половин означает, что она не запущена.
Детали установки — Установка, устройство агента — Агенты.
Журнал ограничен по размеру¶
Файл агента ротируется сам: он растёт до предела, после чего старое вытесняется. Это не настройка на будущее, а следствие замера — журнал обмена со смарт-картой дорастал до сотен мегабайт, переставал читаться и накапливал идентификаторы устройства и человека без срока хранения. Практическое следствие: снимайте нужный кусок журнала сразу, пока инцидент свежий, — через неделю его может уже не быть.
На машинах, где агент устанавливался раньше (до текущей версии), каталог
может по-прежнему называться WifiCertAgent.
Куда смотреть при разборе инцидента¶
- Возьмите
request_idиз заголовка ответаX-Request-ID(или из строки в клиентском/агентском логе, если запрос делал агент — он получает тот же id обратно). - Найдите по нему все серверные строки — они образуют полную историю одного HTTP-запроса, включая финальную access-строку с итоговым статусом и латентностью.
- Если инцидент касается конкретного устройства — ищите по
serial, он связывает строки поперёк множества запросов и заявок этого устройства. GET /api/v1/audit— не то же самое, что структурные логи: это отдельный, персистентный в БД журнал административных действий (approve/reject/revoke, изменение ролей и т.п.), а не поток HTTP-запросов. Подробнее об аудите — в Администрирование.
См. также Диагностику — указатель по типовым отказам: какой симптом в каком разделе разобран.