Laravel Pulse: real-time мониторинг приложения без сторонних сервисов — PROG-TIME

Laravel Pulse: real-time мониторинг приложения без сторонних сервисов

31.05.2026

Когда приложение растёт, рано или поздно встаёт вопрос: что у нас тормозит, какие запросы в БД самые тяжёлые, какие фоновые задачи зависают, кто из пользователей создаёт основную нагрузку. Раньше под это приходилось поднимать отдельный стек — Grafana, Prometheus, экспортёры, агенты. Для не очень большого Laravel-проекта это перебор и по ресурсам, и по поддержке. Laravel Pulse — официальный пакет, который даёт первый, самый нужный уровень мониторинга прямо внутри приложения, без сторонних сервисов и почти без накладных расходов.

В этой статье разберём, как поставить Pulse, что он умеет «из коробки», как настроить пороги и сэмплирование, как разнести нагрузку через отдельную БД и Redis-ingest, и как написать свою карточку под нужды конкретного проекта.

Что такое Laravel Pulse и зачем он нужен

Pulse — это пакет от команды Laravel, который собирает метрики приложения и показывает их на дашборде по адресу /pulse. По сути это лёгкий APM, заточенный именно под Laravel: он подписывается на стандартные события фреймворка (запросы, очереди, кэш, исключения, HTTP-клиент) и пишет агрегированные данные в обычную БД проекта или, при желании, в отдельную.

Pulse не заменяет Sentry или Telescope. Telescope нужен для подробного разбора конкретного запроса в dev/staging, Sentry — для трекинга ошибок в проде с алертами. Pulse — это «приборная панель»: быстрый ответ на вопросы «что у нас сейчас тормозит» и «куда уходят ресурсы».

Установка и базовая настройка

Pulse требует MySQL, MariaDB или PostgreSQL для хранения данных. Если у вас другая БД — придётся завести отдельное подключение специально для Pulse (об этом ниже). Ставим пакет через Composer:

composer require laravel/pulse

Дальше публикуем конфиг и миграции и накатываем их:

php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider"
php artisan migrate

После миграций дашборд уже доступен по /pulse. По умолчанию в окружении local к нему пускают всех, а в проде доступ закрыт — нужно явно описать gate. Самый простой вариант — в AppServiceProvider::boot():

use App\Models\User;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::define('viewPulse', function (User $user) {
        return $user->isAdmin();
    });
}

Если нужно подправить состав карточек или их размещение, публикуем дашборд-вью:

php artisan vendor:publish --tag=pulse-dashboard

Получим файл resources/views/vendor/pulse/dashboard.blade.php — там обычные Livewire-компоненты, которые можно переставлять, удалять и добавлять, не пересобирая JS-ассеты.

Что Pulse показывает «из коробки»

После установки на дашборде появляется набор стандартных карточек, и для типичного веб-приложения этого уже достаточно:

  • Slow Requests — входящие HTTP-запросы, которые превысили порог (по умолчанию 1000 мс), сгруппированные по маршруту.
  • Slow Queries — медленные запросы к БД, сгруппированные по тексту SQL (без bindings) и месту вызова.
  • Slow Jobs — фоновые задачи, которые исполняются дольше порога.
  • Slow Outgoing Requests — медленные исходящие HTTP-запросы через Laravel HTTP Client.
  • Exceptions — частота и свежесть исключений, сгруппированных по классу и месту.
  • Queues — пропускная способность очередей: сколько задач в ожидании, выполняется, обработано, отпущено, упало.
  • Cache — статистика hit/miss кэша, как глобально, так и по конкретным ключам.
  • Application Usage — топ-10 пользователей по запросам, задачам и «медленным» обращениям.
  • Servers — CPU, память и диск серверов, на которых крутится pulse:check.

Карточка Servers — единственная, требующая фонового процесса. На каждом сервере нужно держать запущенным:

php artisan pulse:check

Это длинноживущий процесс, его держат под Supervisor. При деплое корректно перезапускаем командой php artisan pulse:restart — Pulse сохранит сигнал перезапуска в кэше, и worker сам подхватит изменения.

Конфигурация: пороги, сэмплирование и фильтры

Все настройки живут в config/pulse.php. Опубликовать файл можно командой:

php artisan vendor:publish --tag=pulse-config

Самое полезное на старте — настроить пороги для конкретных маршрутов и задач. Например, страница админки заведомо тяжёлая, и нет смысла каждый её рендер считать «медленным»:

use Laravel\Pulse\Recorders;

Recorders\SlowRequests::class => [
    'threshold' => [
        '#^/admin/#' => 5000,
        'default'   => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000),
    ],
],

Аналогично — для медленных задач, запросов и исходящих вызовов. Pulse поддерживает регулярки, и первый совпавший паттерн выигрывает; если ничего не подошло — берётся default.

Под высокий трафик включают сэмплирование: например, сохранять только 10% запросов пользователей. В дашборде значения будут масштабироваться и помечаться знаком ~:

Recorders\UserRequests::class => [
    'sample_rate' => 0.1,
    'ignore' => ['#^/health#', '#^/pulse#'],
],

Если нужно отфильтровать записи по более сложной логике — например, не писать данные админов в общую статистику, — используем Pulse::filter() в AppServiceProvider:

use Illuminate\Support\Facades\Auth;
use Laravel\Pulse\Entry;
use Laravel\Pulse\Facades\Pulse;
use Laravel\Pulse\Value;

Pulse::filter(function (Entry|Value $entry) {
    return Auth::user()?->isNotAdmin() ?? true;
});

Производительность: отдельная БД и Redis-ingest

На небольшом проекте Pulse спокойно живёт в основной базе. На высоконагруженном — лучше унести его в отдельное подключение, чтобы аналитика не лезла в горячие транзакции бизнес-логики. Делается это одной переменной окружения:

PULSE_DB_CONNECTION=pulse

В config/database.php заводим обычное соединение с именем pulse и накатываем туда миграции Pulse.

Второй уровень оптимизации — Redis-ingest. По умолчанию Pulse пишет запись в БД сразу после ответа клиенту или после обработки задачи. Если трафика много, это даёт лишний синхронный поход в базу. Включаем буфер через Redis:

PULSE_INGEST_DRIVER=redis
PULSE_REDIS_CONNECTION=pulse

Требования — Redis 6.2+ и драйвер phpredis или predis. Под Pulse рекомендуется отдельное Redis-соединение, не то же самое, что используется для очередей. После включения нужно запустить worker, который читает stream и переносит данные в БД:

php artisan pulse:work

Его тоже держат под Supervisor и перезапускают через pulse:restart при деплое. Старые записи Pulse подрезает сам через систему лотереи при ingest — параметры trimming тоже настраиваются в конфиге.

Если в момент записи возникла ошибка — например, Pulse не смог достучаться до своей БД — пакет молча её проглотит, чтобы не уронить запрос пользователя. Это поведение можно перехватить и, например, отправить в лог:

use Illuminate\Support\Facades\Log;
use Laravel\Pulse\Facades\Pulse;

Pulse::handleExceptionsUsing(function ($e) {
    Log::debug('Pulse exception', [
        'message' => $e->getMessage(),
    ]);
});

Кастомная карточка: считаем то, что нужно бизнесу

Стандартных карточек хватает для технического мониторинга, но часто хочется видеть и бизнес-метрики: топ продавцов, число оформленных заказов в час, конверсию по тарифам. Pulse даёт для этого готовый механизм — связку Pulse::record() и кастомного Livewire-компонента.

В месте, где случается интересующее нас событие (например, в обработчике оплаты), пишем запись:

use Laravel\Pulse\Facades\Pulse;

Pulse::record('user_sale', $user->id, $sale->amount)
    ->sum()
    ->count();

Первый аргумент — тип записи, второй — ключ группировки (здесь id пользователя), третий — значение для агрегации. Методы sum() и count() говорят Pulse заранее раскладывать значения по «бакетам», чтобы потом дашборд не считал миллионы строк на каждый poll.

Карточка — это Livewire-компонент, наследник Laravel\Pulse\Livewire\Card:

namespace App\Livewire\Pulse;

use Laravel\Pulse\Livewire\Card;
use Livewire\Attributes\Lazy;

#[Lazy]
class TopSellers extends Card
{
    public function render()
    {
        return view('livewire.pulse.top-sellers', [
            'topSellers' => $this->aggregate('user_sale', ['sum', 'count']),
        ]);
    }
}

В шаблоне используем готовые Blade-компоненты Pulse, чтобы карточка органично смотрелась рядом со стандартными:

<x-pulse::card :cols="$cols" :rows="$rows" :class="$class" wire:poll.5s="">
    <x-pulse::card-header name="Top Sellers" />

    <x-pulse::scroll :expand="$expand">
        @foreach ($topSellers as $seller)
            <div>{{ $seller->key }} — сумма {{ $seller->sum }}, заказов {{ $seller->count }}</div>
        @endforeach
    </x-pulse::scroll>
</x-pulse::card>

Подключаем карточку в dashboard.blade.php:

<x-pulse>
    <livewire:pulse.top-sellers cols="4" />
</x-pulse>

Итоги

Laravel Pulse закрывает базовую потребность в мониторинге без внешних сервисов: ставится одной командой, использует ту же БД, что и приложение, и сразу даёт ответ на главные вопросы — что тормозит, что падает, кто грузит систему. Для прод-нагрузки есть честные инструменты: отдельное подключение БД, Redis-ingest с воркером, сэмплирование, фильтры и регулируемые пороги. А механизм Pulse::record() плюс кастомные Livewire-карточки позволяют добавлять собственные метрики без какого-либо стороннего стека.

Если у вас Laravel-проект, в котором ещё нет вообще никакого мониторинга, начинать стоит именно с Pulse. Поднять его — дело часа, а выгоды видны сразу: вы перестаёте угадывать, что тормозит, и начинаете видеть это на дашборде.