Представляємо ourray.app: debugging краще разом

Перекладено ШІ 0 Spatie 30 липня, 2026

Популярний інструмент Ray виходить за межі локальної розробки та перетворює дебаг на колективний досвід у реальному часі. Дізнайтеся, як транслювати стан вашого коду для всієї спільноти за допомогою Laravel Reverb та ClickHouse.

Ray — один із найулюбленіших інструментів для дебагу в спільноті Laravel та PHP, який дедалі частіше виходить за їхні межі. Тисячі розробників використовують його щодня, проте досі це був виключно приватний, локальний досвід.

Під час нещодавнього хакатону ми замислилися: а що, якби дебаг перестав бути справою одинаків? Що, якби ви могли ділитися своїми викликами ray() з усім світом?

Так з’явився ourray.app — спільний хмарний інстанс Ray для всієї спільноти. Один дашборд, дампи кожного розробника і все це в реальному часі. До того ж, абсолютно безкоштовно.

З чого почати

Встановіть пакет:

composer require spatie/our-ray

Просто додайте префікс our() перед вашими викликами ray():

our()->ray('Hello from my codebase!');

Ваші дані для дебагу транслюватимуться на ourray.app, де будь-хто зможе побачити їх у прямому ефірі. Той самий API, до якого ви звикли, але тепер... публічний.

Повний API у вашому розпорядженні

Кольори працюють саме так, як ви очікуєте:

our()->ray('All systems go')->green();
our()->ray('Hmm, suspicious')->orange();
our()->ray('Something is definitely wrong')->red();

Мітки (Labels):

our()->ray($user)->label('Current user');
our()->ray($order->total)->label('Order total')->green();

І так, навіть конфеті працює. Коли ви викликаєте our()->ray()->confetti(), кожен відвідувач ourray.app бачить дощ із конфеті на своєму екрані. Чудовий спосіб підняти комусь настрій.

Ви можете надсилати на ourray.app усе те саме, що й у звичайний Ray: моделі, запити, винятки, колекції — що завгодно. Тільки пам'ятайте: це бачать усі. Ми віримо у вашу розсудливість.

Building ourray.app

Коли це дійсно корисно?

Окрім розважального аспекту спостереження за світовим потоком дебагу (це дивним чином заспокоює, наче відео з каміном), є цілком реальні сценарії використання:

Парний дебаг крізь часові пояси. Ваш колега в Токіо може наживо стежити за вашими викликами ray(), поки ви шукаєте проблему. Жодного поширення екрана чи відеодзвінків — лише чистий потік даних.

Навчання та менторство. Покажіть джуніор-розробнику, як ви відстежуєте проблему. Вони бачитимуть ваші ray() один за одним, поки ви проходите по коду.

Виступи на конференціях. Виведіть ourray.app на великий екран і дебажте наживо перед аудиторією. Жодних затримок трансляції чи проблем із роздільною здатністю.

Хакатони. Вся команда бачить кожен ray() з будь-якої машини. Коли хтось знаходить баг — про це дізнаються всі. Максимальний хаос, максимальна колаборація.

Дебаг в Open Source. Замість того, щоб копіювати логи в GitHub Issue, просто запропонуйте мейнтейнеру зайти на ourray.app, поки ви відтворюєте помилку.

Або просто для вайбу. Іноді хочеться відчути зв'язок з іншими розробниками. Поспостерігайте за стрімом. Подивіться, як хтось о другій ночі дебажить інтеграцію оплат. І подумки побажайте йому успіху.

OurRay Refinement Display

The OurRay Refinement Display

Ми також запускаємо наш перший фізичний продукт — OurRay Refinement Display. Це Raspberry Pi, підключений до екрана зі спеціальним контролером, створений виключно для стрімінгу ourray.app у реальному часі.

Що всередині:

  • Кнопка Play/Pause зі світлодіодом;
  • Кнопка очищення;
  • Фільтри за кольорами для помаранчевих та червоних записів;
  • Поворотний енкодер для гортання стрічки.

Поставте його на стіл і спостерігайте за нескінченним потоком таємничих даних від незнайомих розробників. Ви не знаєте, над чим вони працюють чи чому ця змінна раптом стала null. Але тепер це ваша робота — стежити за цим.

Пишіть нам на lumon@there-there.app для детальнішої інформації.

Технічна частина

Архітектура ourray.app виявилася доволі цікавою. Ось як це влаштовано зсередини.

Як пакет our-ray взаємодіє з Ray

Головний рушій проєкту — новий пакет spatie/our-ray. Після встановлення файл helpers.php автоматично завантажується через Composer. Він реєструє колбек у Ray::$afterSendCallbacks (статичний масив, що спрацьовує після кожного виклику ray()), ініціалізує CloudClient, спрямований на https://ourray.app/api, та визначає помічник our().

Клас OurRay максимально лаконічний:

class OurRay
{
    public function ray(...$args)
    {
        $instance = ray();

        CloudState::enable($instance->uuid);

        if (count($args)) {
            return $instance->send(...$args);
        }

        return $instance;
    }
}

Коли ви викликаєте our()->ray('something'), створюється звичайний інстанс Ray, його UUID позначається як "cloud-enabled" у CloudState, і дані надсилаються. Ray працює локально, як завжди. Потім спрацьовує колбек afterSend, перевіряє статус UUID і передає payload до CloudClient.

CloudClient буферизує дані та надсилає їх пачками по 5 штук через cURL. Функція register_shutdown_function гарантує відправку залишків після завершення PHP-процесу.

Уся взаємодія з хмарою обгорнута в try-catch. Якщо ourray.app "впаде", локальний Ray продовжить працювати без жодних перешкод.

Cloudflare Worker на "краю" мережі

Ми очікуємо великий обсяг трафіку на ендпоінт для payload. Потенційно тисячі розробників можуть слати ray() одночасно. Ми хотіли, щоб наш застосунок на Laravel займався відображенням сторінки, а не обробкою вхідних даних.

Для цього ми використали Cloudflare Worker. Це невеликий код, що працює в мережі Cloudflare (edge) максимально близько до користувача. Усі вхідні дані спочатку потрапляють до нього, а не в Laravel.

Worker виконує три завдання:

Rate limiting за допомогою Durable Objects. Durable Objects — це невеликі інстанси зі станом на edge. Ми створюємо по одному на кожну IP-адресу, де ведеться лічильник запитів за останні 30 секунд. Після 20 запитів вмикається обмеження. Жодних запитів до бази даних, усе в пам'яті.

Обрізання даних. Усе, що перевищує 65 КБ, відсікається.

Асинхронна розсилка. Після фільтрації Worker робить дві речі: зберігає payload у ClickHouse (нашу базу даних) для історії та пушить у Laravel Reverb (наш WebSocket-сервер). Обидва запити йдуть паралельно через ctx.waitUntil(), а Worker миттєво повертає 200 OK.

Building OurRay Refinement Display

Laravel Reverb для WebSocket у реальному часі

Для WebSocket-шару ми обрали Laravel Reverb. Найцікавіше — як дані потрапляють до Reverb. Cloudflare Worker не використовує Laravel SDK, він спілкується безпосередньо з HTTP-ендпоінтом batch_events у Reverb за протоколом Pusher.

Це означає, що Worker має власноруч створювати підпис автентифікації HMAC-SHA256. Спочатку ми формуємо пачку подій:

const batch = requests.map((data) => ({
    name: 'App\\Events\\PayloadReceived',
    channel: 'payloads',
    data: JSON.stringify({ request: data }),
}));

const body = JSON.stringify({ batch });

Потім обчислюємо MD5-хеш тіла запиту та формуємо рядок для підпису:

const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyMd5 = toHex(await crypto.subtle.digest('MD5', encoder.encode(body)));

const path = `/apps/${env.REVERB_APP_ID}/batch_events`;
const queryParams = [
    `auth_key=${env.REVERB_APP_KEY}`,
    `auth_timestamp=${timestamp}`,
    `auth_version=1.0`,
    `body_md5=${bodyMd5}`,
].join('&');

const stringToSign = `POST\n${path}\n${queryParams}`;

Зрештою, ми підписуємо це за допомогою секретного ключа Reverb через Web Crypto API та додаємо підпис до запиту. Оскільки Reverb розуміє протокол Pusher, це працює "з коробки".

На фронтенді ми використовуємо Laravel Echo. Оскільки один виклик ray() може генерувати кілька повідомлень (вміст, колір, мітка, конфеті), клієнтська частина збирає їх докупи для фінального відображення.

ClickHouse для великих обсягів даних

Нам потрібна була база даних, здатна витримати постійний потік записів. MySQL чи PostgreSQL — це круто, але вони оптимізовані для транзакцій. Наш кейс інший: багато пишемо, читаємо пачками та ніколи не оновлюємо окремі рядки.

ClickHouse ідеально підходить для цього. Це колонкова БД для аналітики та високонавантажених вставок. Вона зберігає кожну колонку окремо, що дозволяє краще стискати дані та блискавично читати великі обсяги.

Наша схема:

CREATE TABLE ray_payloads
(
    uuid String CODEC(ZSTD(1)),
    type LowCardinality(String),
    content String CODEC(ZSTD(3)),
    origin_file String CODEC(ZSTD(1)),
    origin_line_number UInt32 CODEC(Delta, ZSTD(1)),
    origin_hostname String CODEC(ZSTD(1)),
    created_at DateTime64(3, 'UTC') DEFAULT now64(3) CODEC(DoubleDelta, ZSTD(1))
)
ENGINE = MergeTree()
PARTITION BY toStartOfHour(created_at)
ORDER BY (created_at, uuid)
TTL created_at + INTERVAL 7 DAY
SETTINGS ttl_only_drop_parts = 1;

Ми використовуємо ZSTD для компресії. LowCardinality для колонки type вказує ClickHouse, що там лише кілька унікальних значень, тому їх можна зберігати як короткі цілі числа. Таблиця партиційована за годинами, а TTL на 7 днів автоматично видаляє старі дані цілими чанками. Дуже ефективно.

Чистий "вайб-кодинг"

Хоча ми обирали стек (Laravel, Cloudflare Workers, ClickHouse, Reverb), ми не написали самотужки жодного рядка коду. Ми описували бажане, а ШІ писав усе за нас.

Cloudflare Worker з Durable Objects, HMAC-SHA256 для Reverb (ми навіть не знали, що в Reverb можна писати поза контекстом Laravel), міграції ClickHouse та весь фронтенд на React. Щиро кажучи, ми не до кінця розуміємо, як працює той React-код, але він працює.

Наш дизайнер лише трохи підправив інтерфейс, бо ШІ все ж таки не ідеальний.

Наостанок

Ourray.app is live!

Ми отримали величезне задоволення під час хакатону, створюючи OurRay Refinement Display та власне ourray.app. Якщо хочете спробувати — заходьте на ourray.app.

А якщо віддаєте перевагу приватному дебагу, скористайтеся купоном D3AD5402 для знижки 20% на myray.app.

Разом дебажимо, наодинці dd-шимо.

Популярні

Інше, що варто прочитати

Використання повнотекстового пошуку в Laravel
180 Оновлено 26 червня, 2026

Використання повнотекстового пошуку в Laravel

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

22 Оновлено 26 червня, 2026

Налаштування Xdebug з Docker та PHP 8.4 всього за одну хвилину

Встановлення Xdebug може бути складним завданням, але в цій статті ми розкриємо, як швидко та просто налаштувати його за допомогою Docker на прикладі Laravel. Дочитайте до кінця, щоб дізнатися, як за кілька хвилин зробити Xdebug вашим надійним помічником у розробці

12 Оновлено 25 червня, 2025

Отримання параметрів команди в Laravel Artisan

Laravel спрощує доступ до аргументів та опцій у ваших кастомних командах Artisan, дозволяючи легко отримувати та валідувати параметри. Дізнайтеся, як ці вбудовані допоміжні методи можуть покращити ваш процес розробки!