MorphCluster Logger
Подсистема логирования состоит из двух пакетов:
- @morphcluster/logger — серверная часть, предоставляющая сервисы для сбора, хранения и администрирования логов.
-
@morphcluster/logger-client — клиентский бэкенд для
Loggerиз ядра, обеспечивающий передачу логов на сервер логирования.
Серверная часть (@morphcluster/logger)
Пакет регистрирует в ServiceHost четыре сервиса: LogStoreBackend, LogStoreAdmin, LogOraList и вспомогательный PostgresMigrator (из отдельного пакета). Основное хранилище — PostgreSQL, взаимодействие с которым вынесено в сервис LogPgStorage2.
LogStoreBackend
Главный сервис приёма логов. Реализует два запроса: write и close. Полученные данные не пишутся в базу сразу, а накапливаются в оперативной памяти в виде дерева объектов LogRecord (класс ActiveLogs). При закрытии корневого лога вся ветка отправляется в LogPgStorage2.insertLogTree().
// Пример использования (происходит автоматически при подключении LoggerBackendStore)
await logStoreBackend.write({ id, parentId, message, level, ... });
await logStoreBackend.close({ id, timestamp });
Жизненный цикл активного лога:
- Клиент вызывает
writeпри создании лога и для каждого дочернего сообщения. - Сообщения собираются в древовидную структуру
LogRecordвнутриActiveLogs. - Когда клиент вызывает
closeдля записи (или по тайм-ауту),ActiveLogsпроверяет, все ли дочерние записи закрыты. Если да — вся ветка помечается на архивацию и через 5 секунд (flushLogTime) вызываетсяonArchive, передающий корневойLogRecordвLogPgStorage2. -
LogPgStorage2вставляет всё дерево одной массовой вставкой в таблицуlogs2.
Свойства и зависимости:
-
LogPgStorage2— обязательный сервис для работы с БД. -
RegistryHelper— для получения настроек логирования из общего реестра (minLevel,maxRecords). -
activeLogs: ActiveLogs— хранилище незавершённых логов.
LogRecord
Модель одной записи лога. Образует древовидную структуру: родитель (parent) и массив дочерних записей (children).
const record = new LogRecord({
id: 'uuid',
message: 'Запрос выполнен',
level: 3,
service: 'MyService',
payload: { ... }
});
record.addChild(childRecord);
Основные поля:
-
id,message,level,host,service,username,ip -
payload— произвольные данные -
order— порядок среди дочерних одного родителя -
duration— длительность (заполняется при закрытии) -
timestamp— временная метка -
closed— флаг завершения записи -
parent/children— связи дерева -
serverTimestamp— момент попадания на сервер (для детекта зависших логов) -
dontArchive— еслиtrue, запись не будет сохранена в БД (например, автоматически созданный родитель «Unknown Parent»).
Методы:
-
addChild(record)— добавить дочернюю запись (только если текущая ещё не закрыта). -
levelToRoot()— протолкнуть максимальный уровень вверх по дереву до корня. -
findRecordById(id)— рекурсивный поиск записи по идентификатору. -
getPath()— путь от корня до текущей записи. -
getRoot()— корневая запись. -
sortChildren()— сортировка детей поorder(рекурсивно). -
getJson()— сериализация всего поддерева в объект (для ответа API). -
forEach(callback)— обход всех узлов дерева в ширину.
ActiveLogs
Хранилище всех активных (ещё не заархивированных) записей в памяти. Управляет их жизненным циклом: вставка, закрытие, автоматическое закрытие по тайм-ауту, архивация.
const activeLogs = new ActiveLogs();
activeLogs.onArchive = (rootLog) => { /* сохранить в БД */ };
activeLogs.insert({ id: '...', message: 'Старт' });
activeLogs.close('id', Date.now());
Основные методы и логика:
-
insert(request)— создаёт или обновляетLogRecord.- Если передан
parentId, находит или создаёт родителя. При отсутствии реального родителя создаётся запись-заглушка сdontArchive = trueи сообщением'Unknown Parent'. - Вызывает
levelToRoot()для подъёма уровня. - Обрабатывает ситуацию, когда
closeпришёл раньшеwrite(через очередьunknownCloses).
- Если передан
-
close(id, timestamp)— закрывает запись. Если запись ещё не известна, сохраняет «закрытие» вunknownClosesна 10 секунд, ожидая появления записи. После закрытия рекурсивно проверяет родительские ветки (checkBranch); если корень полностью закрыт, запускает таймер (5 секунд), после которого ветка удаляется из памяти и передаётся вonArchive. -
checkTimeouts()— периодическая проверка (каждую секунду):- Закрытые ветки, готовые к архивации, отправляются в
onArchive. - Неиспользованные
unknownClosesудаляются через 10 секунд. - Незакрытые логи, неактивные более 15 минут (
autoCloseTime), автоматически закрываются с уровнем 50 и сообщением'Log Timeout'.
- Закрытые ветки, готовые к архивации, отправляются в
-
removeBranch(curLog)— удаляет всё поддерево изlogsPlain. -
getRootLogs()— возвращает только корневые логи (дляlistActive).
Свойства:
-
logsPlain— плоский массив всех известных записей (и корневых, и дочерних). -
unknownCloses— очередь «закрытий», ожидающих свои записи. -
onArchive— колбэк, вызываемый при готовности корневого лога к сохранению.
LogPgStorage2
Сервис для взаимодействия с PostgreSQL. Выполняет миграции, создаёт пул соединений, вставляет деревья логов, предоставляет методы для чтения и очистки.
await logPgStorage2.insertLogTree(rootLogRecord);
const result = await logPgStorage2.list({ service: 'MyService', limit: 20 });
const log = await logPgStorage2.byId('uuid');
Управление:
-
minLevel: number— логи с уровнем ниже не сохраняются (по умолчанию 0, берётся из реестра). -
maxRecords: number— после вставки, если счётчик корневых записей превышает лимит, вызываетсяcleanup()(полная очистка таблицы). Значение по умолчанию 500. -
recordsCount— текущее количество корневых записей.
Методы:
-
insertLogTree(rootLog)— рекурсивно обходит дерево, собирает массив записей для вставки. Игнорирует записи сlevel < minLevel. Выполняет массовыйINSERT. При дубликатеidкорневого лога переименовывает его, генерирует новыйidи ставит уровень 50. -
list(request)— выборка корневых логов с фильтрацией (service,username,minLevel,dateFrom/dateToи др.) и пагинацией. -
listChildren(parentId)/listChildrenRecursive(parentId)— получение дочерних записей. -
byId(logId)— одна запись по идентификатору. -
cleanup()—TRUNCATEтаблицы. -
tableSize()— размер таблицы в человекочитаемом виде. -
serviceOptions()— список уникальных сервисов, встречающихся в логах. -
start(log)— инициализирует миграции, создаёт пул соединений, проверяет подключение (PGTest) и считает количество корневых записей.
Схема БД (подразумевается): таблица logs2 с колонками id, parent_id, message, level, duration, timestamp, order, host, service, username, ip, created, payload, num (автоинкрементный номер для курсорной пагинации).
LogStoreAdmin
Сервис для административного доступа к логам. Все запросы требуют прав администратора (needAdmin: true).
Запросы:
-
list(request)— делегирует вLogPgStorage2.list. -
listActive(req)— возвращает текущие незавершённые корневые логи изactiveLogs. Поддерживает фильтрацию поminLevelиservice. -
details(request)— сначала ищет лог в активных (activeLogs), если не найден — загружает из базы с рекурсивными дочерними записями. -
stats— возвращаетrecordsCountи размер таблицы. -
cleanup— принудительная очистка таблицы логов. -
serviceOptions— делегирует вLogPgStorage2.
Зависимости: LogStoreBackend, LogPgStorage2.
LogOraList
Специализированное представление для получения логов запросов к Oracle (тип сообщения 'OraQueries/exec' или 'OraQueries/execFunc'). Извлекает из древовидной структуры входные и выходные параметры, собирая их из фиксированной иерархии дочерних записей (4 уровня вложенности). Возвращает плоский список с полями queryName, duration, in_payload, out_payload.
Запрос:
-
list(filter)—filterможет содержатьusername,payload(поиск по тексту во входном payload),minLevel,minDuration,dateFrom/dateTo, плюс стандартныеlimitиoffset.
Схемы сервисов
-
LogStoreBackend — два внутренних запроса
writeиclose(не требуют аутентификации, не логируются сами). -
LogStoreAdmin — запросы
list,listActive,details,serviceOptions,stats,cleanup(требуют прав администратора). -
LogOraList — запрос
listс фильтром.
Клиентская часть (@morphcluster/logger-client)
LoggerBackendStore
Реализация бэкенда LoggerBackend из ядра, которая отправляет все логи на серверный LogStoreBackend. Обеспечивает прозрачную буферизацию логов до момента появления сервиса логирования в хосте.
import { LoggerBackendStore } from '@morphcluster/logger-client';
const logStoreBackend = new LoggerBackendStore(host);
logger.backends.push(logStoreBackend);
Принцип работы:
- При создании получает ссылку на
ServiceHostи подписывается на событиеonServiceStarted. - Как только в хосте появляется сервис с именем
LogStoreBackend(илиLogStoreAcc, если есть), он сохраняет ссылку на него. - До этого момента все вызовы
writeиcloseпомещаются в очередь (queue). - При обнаружении сервиса очередь «сбрасывается» — все накопленные операции последовательно отправляются в реальный сервис. После этого
flushedустанавливается вtrue, и дальнейшие вызовы выполняются напрямую, без очереди.
Таким образом, логирование не теряется даже на этапе запуска приложения, когда сервис логирования ещё не готов.
Свойства:
-
host: ServiceHost -
LogStore— ссылка на обнаруженный сервис (например,LogStoreBackend). -
queue: Array— очередь отложенных вызовов{ method, data, resolve, reject }. -
flushed: boolean— флаг завершения «слива» очереди.
Взаимодействие компонентов
- Сервис приложения использует обычный
Loggerиз ядра, добавив в него бэкендLoggerBackendStore. -
LoggerBackendStoreотправляет запросыwriteиcloseв локальный или удалённыйLogStoreBackend(черезGlobalService). -
LogStoreBackendсохраняет все записи вActiveLogsв оперативной памяти. - Когда ветка лога полностью закрыта,
ActiveLogsвызываетLogPgStorage2.insertLogTree(), который одной массовой вставкой записывает всё дерево в PostgreSQL. - Для чтения логов используются сервисы
LogStoreAdmin(универсальный) иLogOraList(специализированный для Oracle-запросов). Они читают данные напрямую изActiveLogs(активные) и изLogPgStorage2(архивные).
Такая архитектура минимизирует количество обращений к базе данных и позволяет гибко настраивать уровни логирования через центральный реестр.