Паттерн: Поток для журналирования (Audit Log)
Введение в аудиторское журналирование (Audit Logging)
В любой сложной инженерной системе, от авиалайнера до системы автоматизации здания, возможность ретроспективно проанализировать последовательность событий имеет первостепенное значение. Аудиторское журналирование (или Audit Log) — это процесс систематической, структурированной и централизованной записи всех значимых событий, происходящих в системе. Это не просто отладочные сообщения для инженера, а фундаментальный механизм, обеспечивающий подотчетность, безопасность и возможность глубокого анализа инцидентов.
> 💡 Подсказка: Думайте об аудите как о «черном ящике» вашего умного дома. Если что-то пойдет не так — например, свет включился посреди ночи или система отопления не сработала в мороз — именно эти записи помогут восстановить точную картину событий и найти первопричину.
Основная роль Audit Log заключается в ответе на три ключевых вопроса:
Ключевое отличие между простыми отладочными сообщениями, которые мы выводим через узел `Debug`, и полноценным журналом аудита заключается в их назначении и структуре.
| Характеристика | Узел `Debug` (Отладка) | Audit Log (Журнал аудита) |
| ------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------- |
| Назначение | Временная диагностика инженером на этапе разработки | Постоянная запись событий в рабочей (production) системе |
| Жизненный цикл | Сообщения существуют только в панели отладки, исчезают при перезагрузке | Записи хранятся долговременно (в файле, базе данных) |
| Структура | Произвольная (может быть число, строка, объект) | Строго стандартизированная (например, JSON с полями `level`, `source`, `message`) |
| Аудитория | Инженер/разработчик | Инженер, служба поддержки, система безопасности, иногда — конечный пользователь |
| Централизация | Децентрализован (узлы `Debug` разбросаны по потокам) | Единый, централизованный поток для сбора всех событий |
Таким образом, создание выделенного потока для журналирования преследует четыре основные цели:
- Централизация: Все события со всей системы стекаются в одну точку. Это избавляет от необходимости искать логи в десятках разных мест.
- Структурирование: Каждое событие приводится к единому, машиночитаемому формату. Это позволяет в дальнейшем легко фильтровать, искать и анализировать данные.
- Хранение: События записываются в надежное хранилище (файл на контроллере, таблица в MySQL, внешняя система логирования) для последующего анализа.
- Оповещение: На основе критически важных событий (например, `ERROR`, `CRITICAL`) система может автоматически инициировать оповещения для администратора.
Этот паттерн является неотъемлемой частью концепции «Операционного слоя» (Ops Layer), которую мы рассматривали ранее. Если узел `Catch` — это сборщик ошибок, а узел `Status` — мониторинг состояния, то Audit Log — это системный летописец, протоколирующий все, что заслуживает внимания.
---
Архитектура потока для журналирования
Правильно спроектированный поток для журналирования должен быть максимально простым, надежным и универсальным. Основой архитектуры является использование стандартных узлов Node-RED и строгого контракта сообщения.
Ключевые компоненты потока
Для создания единой точки входа для всех аудиторских сообщений идеально подходит пара узлов `link in` / `link out`. Это позволяет нам создать "виртуальный" вход в наш поток логирования, к которому можно подключиться из любого другого потока без создания физических "проводов" через всю рабочую область.
Типовая архитектура потока выглядит так:
// Любой рабочий поток (e.g., управление светом)
... -> [Function: Prepare Audit Msg] -> [link out: To Audit Log]
// --- На отдельной вкладке "Operations Layer" ---
// Поток Audit Log
[link in: From All Flows] -> [Function: Standardize Log] -> [Switch: By Level] --+-- (ERROR) --> [Pushover/Notify]
|
+-- (ALL) -----> [File: Append to audit.log]
|
+-- (CRITICAL) -> [MySQL: write]
Как видно из схемы, поток состоит из трех логических частей:
Проектирование контракта `msg.audit`
Чтобы система логирования была эффективной, все сообщения должны следовать единому формату. Для этого мы вводим контракт сообщения, резервируя в объекте `msg` специальное свойство `msg.audit`.
> ℹ️ Информация: Мы используем `msg.audit`, а не `msg.payload`, чтобы не нарушать основной поток данных. `msg.payload` предназначен для передачи полезной нагрузки между рабочими узлами (например, показания датчика или команда для реле). `msg.audit`, в свою очередь, является метаинформацией о самом процессе, которая передается параллельно основному потоку и не влияет на него.
Стандартная структура объекта `msg.audit` должна включать следующие поля:
{
"ts": 1678886400000,
"level": "INFO",
"source_id": "SCN-LIGHT-023",
"source_name": "Гостиная: Включение основного света",
"message": "Свет включен по команде от настенного выключателя SW-LIVINGROOM-01",
"details": {
"user": "auto",
"trigger_node_id": "a1b2c3d4.e5f6g7"
}
}
📋 Ключевые понятия:
- `ts`: Timestamp (число). Временная метка события в формате Unix epoch (ms). Обязательное поле.
- `level`: Уровень события (строка). Определяет важность события. Стандартные уровни:
* `WARN`: Предупреждение о потенциальной проблеме, не влияющей на работу системы (например, "ответ от датчика пришел с задержкой").
* `ERROR`: Ошибка в работе компонента, которая была обработана (например, "не удалось записать значение в Modbus-устройство, попытка №1"). Это то, что мы перехватываем узлом `Catch`.
* `CRITICAL`: Критическая ошибка, ведущая к отказу части системы.
- `source_id`: Идентификатор источника (строка). Уникальный стандартизированный ID сценария (например, `FLOW-AUTO-LIGHT-012`) или потока, сгенерировавшего событие.
- `source_name`: Человекочитаемое имя источника (строка).
- `message`: Основное сообщение (строка). Краткое и ясное описание того, что произошло.
- `details`: Дополнительные данные (объект). Контекстная информация, полезная для глубокого анализа. Сюда можно поместить исходный `msg`, информацию о пользователе и т.д.
Опции хранения логов
Контроллер HI предоставляет несколько встроенных возможностей для хранения журналов аудита:
На начальном этапе рекомендуется использовать комбинацию записи в файл (для всех событий) и отправки оповещений (для `ERROR` и `CRITICAL`).
---
Практика: Создание потока Audit Log
Создадим базовый, но полнофункциональный поток для журналирования, который будет принимать события, стандартизировать их, маршрутизировать в зависимости от уровня и сохранять в файл.
> ⚠️ Внимание: Никогда не создавайте циклы в логировании! Убедитесь, что ваш поток Audit Log не может сам генерировать ошибки, которые он же пытается перехватить через узел `Catch` на этой же вкладке. Это может привести к бесконечному циклу, который исчерпает ресурсы контроллера. Поток аудита должен быть максимально простым и "самодостаточным".
Пошаговая инструкция
* Перетащите на поле узел `link in`.
* В его настройках нажмите кнопку "Создать новую ссылку".
* Назовите ссылку `Audit Input` и добавьте описание "Центральный вход для всех событий аудита".
* Назовите сам узел `[AUDIT] Log Ingest`.
* Добавьте узел `function` и соедините его с выходом `link in`.
* Назовите узел `Standardize Audit Message`.
* Вставьте следующий код. Этот код проверяет наличие `msg.audit`, добавляет `timestamp` и преобразует все сообщение в одну строку JSON для записи в файл.
// Проверяем, есть ли объект msg.audit. Если нет, создаем его.
// Это защита от неправильно сформированных вызовов.
if (typeof msg.audit !== 'object' || msg.audit === null) {
msg.audit = {
level: 'WARN',
source_id: 'unknown',
message: 'Received a non-standard audit message.',
details: { original_payload: msg.payload }
};
}
// Убеждаемся, что ключевые поля существуют
msg.audit.ts = msg.audit.ts || Date.now();
msg.audit.level = msg.audit.level || 'INFO';
msg.audit.message = msg.audit.message || 'No message provided.';
// Преобразуем объект msg.audit в одну строку формата JSON
// Эта строка будет записана в файл.
// null и 2 используются для форматирования с отступом, для логов лучше писать в одну строку
// поэтому используем JSON.stringify(msg.audit) без аргументов.
msg.payload = JSON.stringify(msg.audit);
// Добавляем символ новой строки для корректной записи в файл
msg.payload += "\n";
return msg;
* Добавьте узел `switch` после `function`.
* Назовите его `Route by Level`.
* Настройте его для проверки свойства `msg.audit.level`.
* Добавьте правило: `==` (строка) `ERROR`. Это создаст первый выход.
* Добавьте еще одно правило `==` (строка) `CRITICAL`. Оно будет указывать на тот же первый выход.
* Ниже оставьте правило `otherwise`, которое создаст второй выход для всех остальных уровней.
* Запись в файл:
* Добавьте узел `file` (из секции "storage").
* Соедините его со вторым выходом (`otherwise`) узла `switch`, чтобы записывать ВСЕ события.
* В настройках узла укажите:
* Filename: `/home/node-red/logs/audit.log` (путь может отличаться, убедитесь, что у Node-RED есть права на запись в эту директорию).
* Action: `Append to file`.
* Add newline (\n) to each payload: Убедитесь, что эта галочка СНЯТА, так как мы уже добавили `\n` в `function`.
* Назовите узел `Save to File`.
* Оповещение о критических ошибках:
* Добавьте узел для отправки уведомлений, например `pushover` или `e-mail`.
* Соедините его с первым выходом (`ERROR`/`CRITICAL`) узла `switch`.
* Настройте его для отправки `msg.audit.message` на ваше устройство. Назовите узел `Notify Admin`.
В результате у вас должен получиться компактный и мощный поток, готовый к приему и обработке событий со всей системы.
---
Интеграция с рабочими потоками
Теперь, когда у нас есть централизованный "сборщик" логов, интегрировать его в существующие рабочие потоки очень просто. Для этого используется узел `link out`.
> 🔗 Связанный материал: Этот паттерн идеально дополняет «Операционный слой» (Ops Layer), который мы рассматривали в уроке COURSE-06-M04-L04. Централизованный аудит — ключевой элемент надежной и наблюдаемой системы.
Основной принцип: в любой точке вашего потока, где происходит значимое событие, вы добавляете небольшой блок, который формирует `msg.audit` и отправляет его в наш логгер.
Пример №1: Логирование действия пользователя
Представим поток, который управляет освещением. Пользователь нажимает кнопку, и реле включается. 我们 нужно залогировать этот факт.
// Поток управления освещением
[hi-di: Wall Switch] -> [Toggle Logic] -> [Function: Prepare Audit] -> [link out: Audit]
| ^
| |
+------------------------------------------+
|
v
[hi-do: Relay]
Код для узла `Function: Prepare Audit`:
// Входящий msg.payload = true (включить)
const state = msg.payload ? "ON" : "OFF";
msg.audit = {
// ts будет добавлено автоматически в потоке аудита
level: "INFO",
source_id: "SCN-LIGHT-023",
source_name: "Гостиная: Основной свет",
message: `Свет переключен в состояние ${state} по сигналу от настенного выключателя`,
details: {
relay_id: "RL-01"
}
};
// Возвращаем оригинальный msg, чтобы он пошел дальше к узлу link out.
// Узел link out передаст копию msg в поток аудита, не прерывая основной поток.
return msg;
Узел `link out` настраивается на отправку в наш `Audit Input`. Важно: он не прерывает поток, а отправляет копию сообщения "в сторону". Основной `msg` с `payload` без изменений пойдет дальше, если это необходимо.
Пример №2: Логирование ошибки от `Catch`
Это наиболее важный сценарий использования. Мы уже создали централизованный узел `Catch` для перехвата всех ошибок. Теперь направим эти ошибки в наш новый Audit Log.
Поток обработки ошибок (часть "Operational Layer"):
[Catch: All nodes on all flows] -> [Function: Format Error for Audit] -> [link out: Audit Input]
Код для узла `Function: Format Error for Audit`:
// msg.error содержит информацию об ошибке
const errorInfo = msg.error;
msg.audit = {
level: "ERROR",
source_id: errorInfo.source.id, // ID узла, вызвавшего ошибку
source_name: errorInfo.source.name || 'N/A', // Имя узла
message: "Произошла ошибка в потоке: " + errorInfo.message,
details: {
error_type: errorInfo.source.type,
original_msg: msg // Сохраняем все исходное сообщение для анализа
}
};
// Этот поток только для логирования, поэтому просто возвращаем msg
return msg;
Таким образом, любая ошибка, пойманная узлом `Catch` в любом месте проекта, будет автоматически отформатирована и отправлена в единый журнал аудита, а также вызовет отправку уведомления администратору.
---
Итоги и лучшие практики
Мы рассмотрели паттерн Audit Log — один из самых важных для создания профессиональных и надежных систем автоматизации на базе Node-RED. Он выводит наши проекты из категории "хобби" в категорию инженерных решений.
Краткий обзор
- Audit Log — это централизованный, структурированный журнал для записи всех значимых системных событий.
- Он служит для подотчетности, отладки и обеспечения безопасности.
- Архитектура паттерна строится на паре узлов `link in` / `link out` для создания единой точки входа.
- Все сообщения должны соответствовать строгому контракту `msg.audit` с полями `level`, `source_id`, `message` и др.
Лучшие практики
* Действия пользователя (нажатие кнопок, изменение уставок).
* Смена ключевых состояний системы (режим "охрана", "день/ночь").
* Запуск и завершение важных сценариев.
* Все ошибки и предупреждения (`WARN`, `ERROR`).
* События старта и остановки контроллера.
Что дальше?
В следующих уроках мы перейдем к более сложным темам, таким как управление состоянием (Finite State Machines) и создание пользовательских панелей управления (Dashboard). Наличие надежного механизма журналирования, который мы создали сегодня, станет прочным фундаментом для этих продвинутых практик, позволяя нам отслеживать и отлаживать сложную логику состояний и взаимодействия с пользователем.