Skip to content

Отладка в реальном времени с Server-Sent Events

deep-divessearchitecture

Традиционные инструменты отладки PHP требуют обновления страницы для просмотра новых данных. Вы выполняете запрос, переключаетесь на профилировщик, нажимаете обновить, находите последнюю запись и открываете её. Повторяете десятки раз за сессию отладки. ADP устраняет эту проблему с помощью Server-Sent Events (SSE) — отладочные данные поступают в панель в момент их сбора.

В этой статье мы разберём, как система SSE работает изнутри и почему мы выбрали именно её.

Почему SSE, а не WebSockets?

При проектировании слоя реального времени ADP мы рассмотрели три варианта: поллинг, WebSockets и SSE. У каждого свои компромиссы.

Поллинг — самый простой подход: фронтенд периодически запрашивает у сервера новые данные. Работает везде, но вносит задержку (новые данные видны только через интервал опроса) и генерирует лишний трафик, когда ничего не изменилось.

WebSockets обеспечивают полнодуплексную связь. Фронтенд и сервер могут отправлять сообщения в обоих направлениях в любое время. Это мощно, но добавляет сложность: WebSocket-соединения требуют постоянного серверного процесса, усложняют деплой за обратными прокси и нуждаются в явной логике переподключения.

SSE находится посередине. Сервер отправляет события клиенту через стандартное HTTP-соединение. Это однонаправленный канал (только от сервера к клиенту), что полностью соответствует потребностям панели отладки — сервер имеет новые записи, клиент должен о них узнать. SSE-соединения автоматически переподключаются при сбое, работают через стандартную HTTP-инфраструктуру и не требуют специальной настройки сервера.

Для инструмента отладки SSE — правильный выбор. Нам не нужно, чтобы клиент отправлял данные серверу в реальном времени. Нам нужно, чтобы сервер уведомлял клиента о поступлении новых отладочных данных. SSE делает это с минимальной сложностью.

Обзор архитектуры

Реализация SSE в ADP состоит из трёх компонентов:

  1. События хранилища — Когда отладчик сбрасывает данные коллекторов в хранилище, он генерирует событие, сигнализирующее о наличии новых данных.
  2. SSE-эндпоинт — HTTP-эндпоинт, удерживающий соединение открытым и передающий события клиенту по мере их возникновения.
  3. Слушатель на фронтенде — JavaScript EventSource, который принимает события и обновляет Redux-хранилище.

Поток выглядит так:

Приложение → Сброс отладчика → Запись в хранилище → SSE-событие

                                                  SSE-эндпоинт

                                                  EventSource (браузер)

                                                  Обновление Redux Store

                                                  Перерисовка UI

SSE-эндпоинт

API-модуль предоставляет SSE-эндпоинт по адресу /debug/stream. Когда фронтенд подключается, сервер удерживает соединение открытым и отправляет события в стандартном формате SSE:

event: debug.entry
data: {"id":"abc123","url":"/api/users","method":"GET","status":200,"time":1.23}

event: debug.entry
data: {"id":"def456","url":"/api/orders","method":"POST","status":201,"time":0.87}

Каждое событие содержит метаданные новой отладочной записи — достаточно для отрисовки в списке без загрузки полного payload. Когда пользователь кликает на запись, фронтенд запрашивает полные данные через REST-вызов.

Такой подход сохраняет SSE-поток лёгким. Мы отправляем только сводки записей (обычно менее 1 КБ каждая), а не полные данные коллекторов, которые могут быть значительно больше.

Интеграция с фронтендом

На фронтенде соединение управляется через стандартный EventSource:

typescript
const source = new EventSource('/debug/stream');

source.addEventListener('debug.entry', (event: MessageEvent) => {
  const entry = JSON.parse(event.data);
  dispatch(addDebugEntry(entry));
});

source.addEventListener('error', () => {
  // EventSource автоматически переподключается.
  // Мы обновляем состояние UI для отображения статуса переподключения.
  dispatch(setConnectionStatus('reconnecting'));
});

source.addEventListener('open', () => {
  dispatch(setConnectionStatus('connected'));
});

API EventSource обрабатывает переподключение автоматически. Если соединение прерывается — из-за сетевого сбоя, перезапуска сервера или деплоя — браузер переподключается и возобновляет приём событий. Сервер может опционально отправлять Last-Event-ID для восстановления пропущенных событий.

Работа с несколькими приложениями

ADP может одновременно мониторить несколько приложений. Каждое приложение записывает в собственное пространство имён хранилища. SSE-эндпоинт принимает параметр фильтра для подписки на события от конкретных приложений:

GET /debug/stream?app=my-api
GET /debug/stream?app=my-worker
GET /debug/stream              # все приложения

Интерфейс панели позволяет переключаться между приложениями или просматривать все записи в единой временной шкале, что особенно полезно при отладке распределённых систем, где запрос фронтенда порождает API-вызовы, запускающие фоновые задачи.

Консольные команды и обработчики очередей

SSE особенно ценен для отладки консольных команд и обработчиков очередей. Эти процессы не генерируют HTTP-ответы, которые может перехватить традиционный профилировщик. С ADP отладчик сбрасывает данные в конце выполнения каждой команды или задачи, а SSE-поток немедленно доставляет их в панель.

Это означает, что вы можете наблюдать за обработкой очереди в реальном времени — видеть запросы к БД, сообщения логов и время выполнения каждой задачи по мере их обработки.

Производительность

SSE-соединения — это долгоживущие HTTP-соединения. Каждая открытая вкладка браузера удерживает одно соединение. Для инструмента разработки это не проблема — обычно открыта одна-две вкладки панели. Но мы предприняли шаги для минимизации потребления ресурсов:

  • Буферизация событий — События группируются, когда несколько отладочных записей поступают в быстрой последовательности, уменьшая количество отдельных сообщений.
  • Heartbeat — Сервер отправляет комментарий (: heartbeat) каждые 30 секунд для поддержания соединения через прокси и балансировщики нагрузки.
  • Корректное завершение — Когда сервер отладки останавливается, он отправляет событие закрытия, чтобы фронтенд мог показать соответствующий статус вместо бесконечных попыток переподключения.

Попробуйте

Если у вас установлен ADP, SSE-соединение активно по умолчанию. Откройте панель, выполните несколько запросов в приложении и наблюдайте, как записи появляются в реальном времени. Настройка не требуется.

Для приложений, выполняющих консольные команды или обработчики очередей, убедитесь, что сервер отладки ADP запущен рядом с вашим приложением. CLI-модуль предоставляет простую команду для запуска:

bash
php vendor/bin/adp serve

В следующей публикации мы рассмотрим, как ADP интегрируется с ИИ-ассистентами через MCP-сервер (Model Context Protocol) — привнося интеллектуальный анализ в ваши отладочные данные.

Выпущено под лицензией MIT.