11 KiB
Форматы данных и логов
Документ описывает все форматы данных системы: байт GPIO, протокол FIFO-пайпа, бинарные и текстовые логи, пути хранения и правила очистки.
Аудитория: разработчики и интеграторы.
Содержание
- Байт GPIO
- Протокол FIFO-пайпа
- Бинарные логи (.bin)
- Человекочитаемый GPIO-лог
- События системы
- Пути хранения
- Ротация
- Retention (очистка)
Байт GPIO
Единица данных системы — один байт, снятый с 8-битной GPIO-шины. Разбор — в internal/logger/parser.go (ParseGPIO):
бит: 7 6 | 5 4 3 2 1 0
└─┬─┘ └────┬─────┘
strength count
(0–3) (0–63)
| Поле | Биты | Диапазон | Смысл |
|---|---|---|---|
count (в API — signal) |
0–5 | 0–63 | Количество обнаружений |
strength (в API — amplitude) |
6–7 | 0–3 | Сила сигнала |
Текстовые имена уровней силы (GetStrengthName):
| Значение | Имя |
|---|---|
| 0 | Слабый |
| 1 | Средний |
| 2 | Сильный |
| 3 | Максимальный |
Порог тревоги: при count > 10 (IsHighCount) генерируется событие ОБНАРУЖЕНО_<count>_ОБЪЕКТОВ_СИЛА_<strength> и строка «ВНИМАНИЕ» в human-логе (анти-спам: не чаще одного алерта на одинаковый count за -alert-cooldown секунд).
Пример: байт 0x8C = 1000 1100 → strength = 2 («Сильный»), count = 12 → сработает алерт.
Протокол FIFO-пайпа
Связь C-программы захвата с Go-сервером — именованный канал (по умолчанию /tmp/gpio_pipe, флаг -pipe).
- Поток сырых байт без кадрирования: каждый байт — одно измерение в формате байта GPIO. Никаких заголовков, разделителей и контрольных сумм.
- Сервер читает блоками до 4096 байт (internal/pipe/reader.go).
- Метка времени присваивается на стороне сервера в момент чтения (
time.Now().UnixMicro()). - При отсутствии/обрыве пайпа сервер переподключается сам: проверка существования каждые 2 с, повторное открытие через 1 с; смены состояния фиксируются событиями
PIPE_*(см. словарь событий).
Бинарные логи (.bin)
Основной архив данных. Запись — internal/logger/data_logger.go, чтение — HandleLogData в cmd/server/main.go.
Формат файла: конкатенация записей по 9 байт, без заголовка и футера:
┌────────────────────────────────┬───────────┐
│ timestamp: uint64 LE, 8 байт │ value: 1б │
└────────────────────────────────┴───────────┘
timestamp— микросекунды Unix (UnixMicro), little-endian;value— сырой байт GPIO.
Буферизация записи: сэмплы копятся в буфере 64 КБ и сбрасываются на диск раз в 1 секунду либо при заполнении буфера; после каждого сброса вызывается fsync (durability при отключении питания).
Именование: gpio-YYYY-MM-DD-HH.bin, время локальное, один файл на час (см. Ротация). Пример: gpio-2026-07-17-12.bin — данные за 12:00–12:59 17 июля 2026.
Чтение на другой платформе: каждая запись — <Q + B в терминах Python struct:
import struct
with open("gpio-2026-07-17-12.bin", "rb") as f:
while chunk := f.read(9):
if len(chunk) < 9:
break
ts_us, value = struct.unpack("<QB", chunk)
strength, count = (value >> 6) & 0x3, value & 0x3F
Человекочитаемый GPIO-лог
Файл gpio_human.log (см. пути), пишет internal/pipe/reader.go через HumanLogger.
Обычная строка (пишется при изменении count/strength либо раз в -human-log-interval секунд):
[2026-07-17 12:34:56.789] Обнаружение: 12 объектов | Сила: Сильный (уровень 2) | Сырое: 0x8C (140)
Строка тревоги (при count > 10, с учётом анти-спама):
[2026-07-17 12:34:56.789] ВНИМАНИЕ: Обнаружено превышение! 12 объектов | Сила: Сильный (уровень 2)
События системы
События пишутся internal/logger/event_logger.go сразу в два файла:
events.log— JSON-строки (одна на событие):
{"ts": 1789034096, "time": "2026-07-17 12:34:56", "event": "ПЛАНОВАЯ_РОТАЦИЯ"}
events_human.log— текст (этот файл читает/api/log/events):
[2026-07-17 12:34:56.789] EVENT: ПЛАНОВАЯ_РОТАЦИЯ
Словарь событий (по вызовам Event(...) в коде):
| Событие | Источник | Когда |
|---|---|---|
СЕРВЕР_ЗАПУЩЕН |
main.go | Старт сервера |
HUMAN_ЛОГЕР_ГОТОВ |
main.go | Инициализация HumanLogger |
DATA_ЛОГЕР_ГОТОВ |
main.go | Инициализация RotatingLogger |
ОШИБКА_ИНИЦИАЛИЗАЦИИ_DATA_ЛОГЕРА |
main.go | Сбой инициализации (сервер завершается) |
RETENTION_ГОТОВ |
main.go | Запуск очистки логов |
ПЛАНОВАЯ_РОТАЦИЯ |
rotation.go | Открыт новый почасовой файл |
ПЛАНОВАЯ_РОТАЦИЯ_НЕУДАЧНА |
rotation.go | Не удалось открыть новый файл |
PIPE_НЕ_НАЙДЕН |
reader.go | FIFO-файл отсутствует |
ОШИБКА_ОТКРЫТИЯ_PIPE |
reader.go | FIFO существует, но не открывается |
PIPE_ПОДКЛЮЧЕН |
reader.go | Пайп открыт / связь восстановлена |
PIPE_ОТКЛЮЧЕН |
reader.go | Ошибка чтения из пайпа |
ТИШИНА_5МИН |
monitor.go | Нет данных дольше порога -silence-5min |
ТИШИНА_10МИН |
monitor.go | Нет данных дольше порога -silence-10min |
ОБНАРУЖЕНО_<N>_ОБЪЕКТОВ_СИЛА_<M> |
reader.go | Алерт превышения (count > 10) |
ОЧИСТКА_ЛОГОВ_УДАЛЕНО_<N>_ФАЙЛОВ_<M>_MB |
retention.go | Retention удалил файлы |
ОШИБКА_ПОЛУЧЕНИЯ_ДИРЕКТОРИИ |
retention.go | Retention не смог получить каталог данных |
События смены состояния пайпа пишутся только при изменении состояния (дедупликация в PipeReader), тишина — при каждой проверке watchdog (раз в -watchdog-interval минут), пока тишина длится.
Пути хранения
Все пути вычисляет internal/logger/paths.go. Режим определяется по существованию каталога /opt/gpio-monitoring: есть — «пакетный» режим (deb-установка), нет — режим разработки (XDG).
| Что | Пакетный режим | Режим разработки |
|---|---|---|
| Данные приложения | /var/lib/gpio-monitoring |
~/.local/share/gpio-monitoring |
| Каталог логов | /var/log/gpio-monitoring |
~/.local/share/gpio-monitoring/logs |
Бинарные .bin |
/var/log/gpio-monitoring/data/ |
~/.local/share/gpio-monitoring/logs/data/ |
| События (JSON) | .../events.log |
.../logs/events.log |
| События (текст) | .../events_human.log |
.../logs/events_human.log |
| GPIO human-лог | .../gpio_human.log |
.../logs/gpio_human.log |
Ротация
internal/logger/rotation.go: RotatingLogger держит текущий DataLogger и раз в -rotation-check-interval минут (по умолчанию 1) сверяет текущий час с часом открытого файла. При смене часа старый файл закрывается (с финальным flush) и открывается новый gpio-YYYY-MM-DD-HH.bin; пишется событие ПЛАНОВАЯ_РОТАЦИЯ.
Retention (очистка)
internal/logger/retention.go: фоновая FIFO-очистка каталога data/. Проверка раз в -retention-interval минут (по умолчанию 15), первая — через 1 минуту после старта.
Алгоритм Cleanup():
- По возрасту (
-retention-hours, 0 = отключено): файлы старше порога удаляются с самого старого края. Файл считается устаревшим, только если он старше порога и по времени из имени, и по ModTime — защита от скачка системных часов после NTP-синхронизации на устройстве без RTC. - По размеру (
-retention-mb, 0 = отключено): пока суммарный размер.bin-файлов превышает лимит, удаляется самый старый файл. - В любом случае сохраняются минимум 2 самых свежих файла (
minKeepFiles) — защита от полного стирания каталога.
Если имя файла не соответствует шаблону gpio-YYYY-MM-DD-HH.bin, вместо времени из имени используется ModTime. Поведение закреплено тестами internal/logger/retention_test.go.