Когда приложение растёт, рано или поздно встаёт вопрос: что у нас тормозит, какие запросы в БД самые тяжёлые, какие фоновые задачи зависают, кто из пользователей создаёт основную нагрузку. Раньше под это приходилось поднимать отдельный стек — 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. Поднять его — дело часа, а выгоды видны сразу: вы перестаёте угадывать, что тормозит, и начинаете видеть это на дашборде.