Логирование фреймов

В протоколе STOMP вся коммуникация между клиентом и брокером строится вокруг текстовых фреймов. Каждый фрейм представляет собой структурированное сообщение, включающее команду, заголовки и тело. При разработке клиентской логики на STOMP.js именно эти фреймы становятся ключевой точкой наблюдения за системой: подписки, отправка сообщений, подтверждения доставки и ошибки проходят через них.

Логирование фреймов позволяет увидеть реальную картину обмена данными, а не только абстракции уровня API библиотеки. Это особенно важно при работе с брокерами сообщений (ActiveMQ, RabbitMQ, Artemis), где поведение может зависеть от заголовков, формата payload и состояния соединения.


Структура STOMP-фрейма как основа логирования

Каждый STOMP-фрейм имеет строго определённую структуру:

  • Команда (например, CONNECT, SEND, SUBSCRIBE, MESSAGE, ERROR)
  • Заголовки (ключ-значение)
  • Пустая строка-разделитель
  • Тело сообщения (опционально)

Пример фрейма:

SEND
destination:/queue/orders
content-type:application/json

{"id":1,"status":"created"}

При логировании важно разделять эти части, поскольку анализ только тела сообщения часто не даёт полной картины происходящего. Ошибки нередко скрываются в заголовках: неверный destination, отсутствие ack, неправильный content-type.


Встроенный механизм debug в STOMP.js

STOMP.js предоставляет встроенный механизм отладочного вывода через функцию debug. Она вызывается при каждом значимом событии клиента: подключение, отправка, получение сообщений, ошибки.

Базовое включение логирования:

import { Client } from '@stomp/stompjs';

const client = new Client({
  brokerURL: 'ws://localhost:15674/ws',
  debug: function (str) {
    console.log('[STOMP]', str);
  }
});

Функция debug получает строковые сообщения, которые уже сериализованы библиотекой. Это самый простой уровень наблюдения, но он не даёт доступа к структурированному представлению фрейма.


Логирование входящих и исходящих сообщений

Для более глубокого контроля используется разделение логики на входящие и исходящие события клиента.

Исходящие фреймы

Исходящие данные проходят через методы:

  • activate()
  • publish()
  • subscribe()
  • deactivate()

Пример перехвата отправки сообщений:

client.publish({
  destination: '/queue/orders',
  body: JSON.stringify({ id: 1 }),
  headers: {
    'content-type': 'application/json'
  }
});

Для логирования можно обернуть метод publish:

const originalPublish = client.publish.bind(client);

client.publish = function (frame) {
  console.log('[OUT]', frame.destination, frame.headers, frame.body);
  return originalPublish(frame);
};

Такой подход позволяет фиксировать фактические данные до их сериализации в STOMP-фрейм.


Входящие сообщения

Подписка на топики создаёт поток входящих сообщений:

client.subscribe('/topic/updates', (message) => {
  console.log('[IN]', {
    destination: message.headers.destination,
    headers: message.headers,
    body: message.body
  });
});

Объект message уже является распакованным представлением STOMP-фрейма. Однако важно учитывать, что:

  • body всегда строка
  • заголовки могут содержать брокер-специфичные поля
  • ACK может требовать явного подтверждения

Расширенное логирование через перехват фреймов

STOMP.js не предоставляет прямого API для низкоуровневого перехвата сырого фрейма, но это можно реализовать через модификацию WebSocket.

Перехват WebSocket send

const ws = new WebSocket('ws://localhost:15674/ws');

const originalSend = ws.send.bind(ws);

ws.send = function (data) {
  console.log('[WS OUT]', data);
  return originalSend(data);
};

Такой подход позволяет видеть реальные STOMP-фреймы до их передачи на сервер.


Перехват incoming сообщений WebSocket

ws.addEventListener('message', (event) => {
  console.log('[WS IN]', event.data);
});

На этом уровне данные ещё не преобразованы STOMP.js, что даёт возможность анализировать протокол в чистом виде.


Форматирование логов для STOMP-фреймов

При работе с большим количеством сообщений простого console.log недостаточно. Логи должны быть структурированы.

Рекомендуемый формат:

[STOMP OUT] COMMAND
destination: /queue/orders
headers: {...}
body: {...}

Пример реализации форматтера:

function logFrame(direction, frame) {
  console.group(`[STOMP ${direction}]`);
  console.log('destination:', frame.destination);
  console.log('headers:', frame.headers);
  console.log('body:', frame.body);
  console.groupEnd();
}

Логирование ошибок и ERROR-фреймов

Особое внимание требуется ERROR-фреймам. Они могут содержать:

  • текст ошибки брокера
  • stacktrace серверной стороны
  • причину разрыва соединения

STOMP.js обрабатывает ошибки через callback:

const client = new Client({
  brokerURL: 'ws://localhost:15674/ws',
  onStompError: (frame) => {
    console.error('[STOMP ERROR]', frame.headers, frame.body);
  }
});

Также возможны транспортные ошибки WebSocket:

client.onWebSocketEr ror = (event) => {
  console.error('[WS ERROR]', event);
};

Логирование жизненного цикла соединения

Помимо фреймов, важно отслеживать состояния клиента:

  • CONNECTING
  • OPEN
  • SUBSCRIBED
  • DISCONNECTING
  • CLOSED
client.onConn ect = () => {
  console.log('[STOMP] connected');
};

client.onDisconn ect = () => {
  console.log('[STOMP] disconnected');
};

client.onWebSocketCl ose = () => {
  console.log('[WS] closed');
};

Согласованное логирование состояния и фреймов позволяет восстановить полную картину поведения системы.


Фильтрация и маскирование чувствительных данных

При логировании STOMP-фреймов часто попадают данные, которые не должны отображаться в консоли:

  • токены авторизации
  • session-id
  • персональные данные
  • внутренние идентификаторы

Механизм фильтрации:

function sanitize(headers) {
  const copy = { ...headers };
  if (copy.authorization) {
    copy.authorization = '***';
  }
  if (copy.token) {
    copy.token = '***';
  }
  return copy;
}

Использование:

console.log('[IN]', {
  headers: sanitize(message.headers),
  body: message.body
});

Производительность логирования в высоконагруженных системах

При большом потоке сообщений логирование становится узким местом. Основные проблемы:

  • синхронный console.log блокирует event loop
  • большие JSON-объекты увеличивают нагрузку на GC
  • частые group/dir операции замедляют браузер

Оптимизированный подход:

const logQueue = [];

function enqueueLog(entry) {
  logQueue.push(entry);
}

setInterval(() => {
  if (logQueue.length > 0) {
    console.log(...logQueue.splice(0, 50));
  }
}, 200);

Такой буферизированный подход снижает нагрузку на интерфейс.


Разделение уровней логирования

Для практических систем логирование STOMP-фреймов обычно делится на уровни:

  • ERROR: ошибки соединения и брокера
  • WARN: повторные подключения, нестабильность
  • INFO: подключения, подписки
  • DEBUG: все фреймы
  • TRACE: сырой WebSocket поток

Пример конфигурации:

const LOG_LEVEL = 'DEBUG';

function log(level, ...args) {
  const levels = ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'];
  if (levels.indexOf(level) <= levels.indexOf(LOG_LEVEL)) {
    console.log(`[${level}]`, ...args);
  }
}

Практическая схема централизованного логгера

Единая точка логирования упрощает сопровождение:

class StompLogger {
  constructor(prefix = 'STOMP') {
    this.prefix = prefix;
  }

  out(frame) {
    console.log(`[${this.prefix} OUT]`, frame);
  }

  in(message) {
    console.log(`[${this.prefix} IN]`, {
      headers: message.headers,
      body: message.body
    });
  }

  error(err) {
    console.error(`[${this.prefix} ERROR]`, err);
  }
}

Интеграция:

const logger = new StompLogger();

client.subscribe('/topic/data', (msg) => logger.in(msg));
client.publish({ destination: '/queue/test', body: 'data' });

Анализ логов STOMP как диагностический инструмент

Системный анализ логов позволяет выявлять:

  • потерю сообщений (отсутствие MESSAGE фреймов)
  • неправильные маршруты (ошибочный destination)
  • дублирование подписок
  • нестабильные reconnect-циклы
  • несоответствие payload формату

При достаточном уровне детализации логов STOMP-фреймов можно восстановить полную карту взаимодействия клиента и брокера, включая скрытые ошибки транспортного уровня и протокольные несоответствия.