Сервер для дебага Laravel приложений и не только.

Pavel Buchnev
8 min readAug 24, 2021

--

Из-за политических воин RayServer приобрел новое имя и стал называться просто Debugger. Четко и понятно!

Буквально месяц назад я наткнулся на любопытное приложение ray app — приложение с минималистичным интерфейсом для вывода отладочной информации (значения переменных, ошибок, логов и т.д.) в момент разработки приложения без привязки к фреймворку, языку программирования и окружению. На PHP работает в связке с пакетом spatie/ray. На других языках также имеются библиотеки, с помощью которых можно отправлять данные в ray app.

Ray app

Захотелось посмотреть, как оно работает изнутри, и мне настолько понравилась идея, что я написал его работающий аналог — имеющий практически весь функционал из Ray app, а также дополнительные возможности, которых нет там. Получилась достойная бесплатная альтернатива для тех, кто хочет запускать на сервере без графической оболочки, или не может позволить себе платную версию.

Небольшая предыстория — как, что и зачем

Как я говорил выше, Ray app не привязан к конкретному фреймворку и языку программирования и является универсальным инструментом, но мне захотелось сделать его немного другим, похожим на MailHog. Если кто-то не знает о нём, то это SMTP-сервер для приёма тестовых писем во время разработки, он добавляется в проект за считанные секунды через Docker и не требует дополнительных настроек со стороны проекта. Т.е. хотелось чтобы RayServer запускался через Docker, и также легко мог стать частью любого проекта, даже в ОС без графической оболочки.

Ну а теперь про техническую составляющую ray app.

Ray app принимает события и логи из различных источников, и затем выводит их в приятном глазу виде, предоставляя для каждого типа события (лога) подробную информацию.

При этом схема работы программы оказалась довольно простой:

  1. При старте ray app запускает веб-сервер на порту 23517.
  2. Пакет spatie/ray по HTTP отправляет данные на этот веб-сервер и они выводятся в окне приложения.

Всё это выглядело понятным, и я подумал: “А почему бы не попробовать мне самому написать что-то похожее, только в виде сервера?” Помимо этого хотелось ограничиться Laravel и Docker для удобного запуска. Итак, перед сервером я ставил следующие задачи:

  • выводить значения переменных, и логи и ошибки — это одна из основных фичей;
  • причём показывать их не нечитаемым полотном, а с разметкой, чтобы можно было использовать фильтры;
  • быть доступным для всех желающих (Open Source проект);
  • простота использования во время разработки;
  • независимость от языка программирования и фреймворка;
  • и ещё пара фичей, о которых расскажу ниже.

Родился план:

  1. Взять Lumen для приема HTTP запросов от spatie/ray.
  2. Поднять Websocket сервер для вывода событий в браузер.
  3. Взять VueJS и TailwindCss для фронта.
  4. Упаковать в докер, чтобы запускать где угодно без необходимости готовить инфраструктуру.

Создание ray server

Для начала я изучил какие вообще есть варианты для запуска Websocket (WS) сервера на php. Под эти цели подходит куча пакетов, но от всех них можно отказаться, в пользу php расширения Swoole, либо приложения RoadRunner, написанного на go. Они оба имеют возможность из коробки поднять высокопроизводительный HTTP/WS-сервер без особых сложностей. Для этого проекта я выбрал Swoole, т.к. для него необходим только php, а цель была обойтись минимальными средствами.

HTTP-сервер есть, WS-сервер есть, и время первой пробы получать события с помощью пакета spatie/ray.

  • Данные приходят на HTTP-сервер;
  • из контроллера с помощью WS клиента подключаемся к нашему WS-серверу;
  • передаём полученные данные WS серверу;
  • он рассылает их остальным клиентам.

Выглядело это примерно так:

// Http server
$route->post('/', function (Request $request) {
$client = new \WebSocket\Client(sprintf('ws://%s:%d/', config('websocket.host'), config('websocket.port')));
$client->send(json_encode($request->all()));
});
.............// WS server
$server->on('Message', function (Server $server, Frame $frame) use ($connections) {
foreach ($connections as $client) {
// Отправителя исключаем
if ($client['client'] == $frame->fd) {
continue;
}
$server->push($client['client'], $frame->data);
}
});

Мне в этой схеме не понравилось наличие WS клиента на стороне php, да и вообще наличие двух серверов HTTP и WS.

Во-первых, это требует внутри докера запуска супервизора. Во-вторых, сам факт передачи данных из HTTP в WS, ну и наконец пользователь может захотеть изменить номер порта WS-сервера, и в браузере нужно будет добавлять настройки для указания порта для подключения, а это усложняет работу фронта.

Тут на помощь приходит всё тот же Swoole. Согласно их документации, WS-сервер это надстройка над HTTP-сервером и более того, запущенный WS-сервер может спокойно принимать HTTP запросы, в этом случае данные из HTTP в WS можно передавать в рамках одного запущенного процесса.

Звучит круто и для Laravel есть Octane, но есть два но:

  1. он не работает с Lumen;
  2. он запускает HTTP-сервер, а не WS-сервер.

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

Вуаля, и теперь мы можем сделать так:

// Http server
$route->post('/', function (Request $request, Swoole\Http\Server $server) {
foreach ($connections as $client) {
$server->push($client, json_encode($request->all()));
}
});

Laravel после запуска сервера связывает инстанс запущенного сервера с классом Swoole\Http\Server, который в моем случае является Swoole\Websocket\Server и в него дополнительно можно пушить сообщения. Бонусом получилось увязать их на один порт, и поэтому отпадает проблема с уточнением порта для WS, также как и необходимость использовать разные порты!

Что получилось в результате

Все задачи, которые я ставил в начале своего путешествия в волшебный мир дебага мне удалось решить. Я написал свой, OpenSource сервер, который показывает события в красивом, удобочитаемом виде, словом, решает задачу дебаггинга приложений. При этом в запуске и настройке сервер максимально упрощен, а открытый код позволяет улучшать его работу всем желающим.

Хочу добавить, что мне неожиданно понравилось работать с HTTP + WS в рамках одного процесса, когда они запущены на одном порту. Конечно, имеется ряд ограничений, например процесс не масштабируется на несколько серверов, но для простых задач связка работает как швейцарские часы.

Несколько примеров того, что умеет делать RayServer

Выводить значения переменных

Показывать ошибки приложения. API RayServer совместим с Sentry, что позволяет использовать его в качестве легкой альтернативы для локальной разработки.

Сервер совместим с composer пакетом monolog/monolog, и может выводить логи от него.

У RayServer легкий и приятный интерфейс, который позволяет просматривать события, фильтровать их по лейблам, типам и цветам.

RayServer полностью совместим со всеми возможностями пакета spatie/ray, spatie/laravel-ray

Интерфейс имеет отзывчивый дизайн и одинаково хорошо отображается как на экране монитора, так и смартфоне/планшете. Мобильное устройство может выступать в качестве дополнительного экрана для просмотра истории событий.

Серверная часть написана на Laravel + Octane HTTP/Websocket без использования сторонних инструментов и имеет открытый исходный код по лицензии MIT. Любой желающий может использовать его для своих нужд, а также участвовать в развитии продукта.

Примеры использования можно посмотреть в репозитории проекта.

Установка и запуск сервера

Docker

Самый простой способ запустить сервер с помощью docker. После запуска Docker контейнера RayServer готов к работе, и не требует дополнительных настроек.

// Запуск последней версии (при выходе новой, образ будет обновлен)
docker run --pull always -p 23517:8000 butschster/debugger:latest
// Запуск конкретной версии
docker run -p 23517:8000 butschster/debugger:v1.10

После запуска сервер готов к приёму событий. Dashboard доступен по адресу http://127.0.0.1:23517

По умолчанию сервер хранит все события в sqlite в файловой БД (об этом я писал подробнее чуть выше). При желании можно использовать внешнюю БД:

// docker-compose.yml
version: "2"
services:
php:
image: butschster/debugger:latest
environment:
DB_CONNECTION: pgsql
DB_HOST: db
DB_DATABASE: homestead
DB_USERNAME: homestead
DB_PASSWORD: secret
ports:
- 23517:8000
depends_on:
- db
db:
image: postgres
environment:
POSTGRES_USER: homestead
POSTGRES_DB: homestead
POSTGRES_PASSWORD: secret

Поскольку сервер работает на базе Laravel, его настройка не должна вызывать сложности, т.к. для большинства из вас это главный инструмент.

Настройка пакетов для работы с RayServer

spatie/ray

Т.к. spatie/ray по умолчанию использует этот порт, то его настраивать нет необходимости.

// Для Laravel
RAY_HOST=127.0.0.1
RAY_PORT=23517

sentry

Сервер совместим с https://sentry.io/, поэтому в своём приложении можно указать DSN:

// Для Laravel
SENTRY_LARAVEL_DSN=http://sentry@127.0.0.1:23517/1

monolog

С monolog все немного сложнее — у них из коробки не так много драйверов, которые позволяют отправлять логи по HTTP, но есть вариант \Monolog\Handler\SlackWebhookHandler, который позволяет указать url для отправки данных.

// Для Laravel
LOG_CHANNEL=slack
LOG_SLACK_WEBHOOK_URL=http://127.0.0.1:23517/slack

Запуск без Docker

Для запуска необходимо установить php расширение swoole.

Шаги

  1. Клонировать код проекта в директорию.
  2. Выполнить команду composer install.
  3. Запустить сервер .
php artisan server:start --host=127.0.0.1 --port=23517

Поздравляю всех, кто добрался до финала нашего путешествия в мир дебаггинга, и спасибо, что дочитали!

Буду рад, если мой скромный, но любимый RayServer вас заинтересовал. Всю информацию о нём можно найти по ссылкам:

Следите за моими публикациями в моем Тви: https://twitter.com/ButscH

Редактор текста — Ольга Т.

--

--

Pavel Buchnev

Senior PHP Developer | Contributor to Spiral Framework 🚀 | Enthusiast of RoadRunner & long-running applications | Creator of Buggregator