Advanced Laravel

Laravel Pulse & Monitoring

15 min Lesson 29 of 40

Laravel Pulse & Monitoring

Laravel Pulse is a real-time application performance monitoring dashboard that provides insights into your application's health, performance bottlenecks, and user activity. It captures metrics like slow queries, exceptions, queue performance, and server resource usage.

Why Pulse? Unlike external monitoring tools, Pulse is built directly into Laravel, offering zero-configuration insights with beautiful, real-time dashboards accessible from within your application.

Installing and Configuring Pulse

<?php // Install Pulse // composer require laravel/pulse // Publish configuration and migrations // php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider" // php artisan migrate // config/pulse.php return [ 'domain' => env('PULSE_DOMAIN'), 'path' => env('PULSE_PATH', 'pulse'), 'enabled' => env('PULSE_ENABLED', true), 'storage' => [ 'driver' => env('PULSE_STORAGE_DRIVER', 'database'), 'database' => [ 'connection' => env('PULSE_DB_CONNECTION', null), 'chunk' => 1000, ], ], 'ingest' => [ 'driver' => env('PULSE_INGEST_DRIVER', 'storage'), 'buffer' => env('PULSE_INGEST_BUFFER', 5000), 'trim' => [ 'keep' => env('PULSE_INGEST_TRIM_KEEP', '7 days'), ], ], 'recorders' => [ Pulse\Recorders\CacheInteractions::class => [ 'enabled' => env('PULSE_CACHE_INTERACTIONS_ENABLED', true), 'sample_rate' => env('PULSE_CACHE_INTERACTIONS_SAMPLE_RATE', 1), ], Pulse\Recorders\Exceptions::class => [ 'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true), 'sample_rate' => env('PULSE_EXCEPTIONS_SAMPLE_RATE', 1), 'ignore' => [ // Exceptions to ignore ], ], Pulse\Recorders\Queues::class => [ 'enabled' => env('PULSE_QUEUES_ENABLED', true), 'sample_rate' => env('PULSE_QUEUES_SAMPLE_RATE', 1), ], Pulse\Recorders\SlowQueries::class => [ 'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true), 'sample_rate' => env('PULSE_SLOW_QUERIES_SAMPLE_RATE', 1), 'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000), ], Pulse\Recorders\Servers::class => [ 'enabled' => env('PULSE_SERVERS_ENABLED', true), ], Pulse\Recorders\UserRequests::class => [ 'enabled' => env('PULSE_USER_REQUESTS_ENABLED', true), 'sample_rate' => env('PULSE_USER_REQUESTS_SAMPLE_RATE', 1), ], Pulse\Recorders\UserJobs::class => [ 'enabled' => env('PULSE_USER_JOBS_ENABLED', true), 'sample_rate' => env('PULSE_USER_JOBS_SAMPLE_RATE', 1), ], ], ]; // Access dashboard at /pulse // Protect with middleware in routes Route::get('/pulse', function () { return redirect('/pulse'); })->middleware(['auth', 'admin']);

Pulse Dashboard Overview

The default Pulse dashboard includes several cards displaying real-time metrics:

<?php // Default dashboard cards: // 1. Server Resources - CPU, Memory, Disk usage // 2. Slow Queries - Database queries exceeding threshold // 3. Exceptions - Application errors and stack traces // 4. Queue Throughput - Jobs processed per minute // 5. Cache Performance - Hit/miss ratios // 6. User Requests - Most active users // 7. Slow Routes - HTTP requests by duration // Customize dashboard in resources/views/vendor/pulse/dashboard.blade.php <x-pulse> <livewire:pulse.servers cols='4' /> <livewire:pulse.usage cols='4' rows='2' /> <livewire:pulse.queues cols='4' /> <livewire:pulse.cache cols='4' /> <livewire:pulse.slow-queries cols='8' /> <livewire:pulse.exceptions cols='8' /> <livewire:pulse.slow-requests cols='8' /> <livewire:pulse.slow-jobs cols='8' /> </x-pulse>
Performance Impact: Pulse is designed to have minimal overhead. Use sample rates to reduce data collection if needed. For high-traffic sites, consider using Redis for Pulse storage instead of database.

Creating Custom Pulse Cards

<?php // Create a custom recorder // app/Pulse/Recorders/ApiCallRecorder.php namespace App\Pulse\Recorders; use Laravel\Pulse\Facades\Pulse; use Illuminate\Http\Client\Events\ResponseReceived; class ApiCallRecorder { public function register() { Event::listen(ResponseReceived::class, function ($event) { $duration = $event->response->handlerStats()['total_time'] ?? 0; Pulse::record( type: 'api_call', key: $event->request->url(), value: $duration * 1000, // Convert to milliseconds )->count()->avg()->max(); }); } } // Create a custom Livewire card component // app/Livewire/Pulse/ApiCalls.php namespace App\Livewire\Pulse; use Laravel\Pulse\Livewire\Card; use Laravel\Pulse\Facades\Pulse; use Livewire\Attributes\Lazy; #[Lazy] class ApiCalls extends Card { public function render() { [$apiCalls, $time] = $this->remember( fn () => Pulse::values('api_call') ->map(function ($item) { return (object) [ 'url' => $item->key, 'count' => $item->count, 'avg_duration' => round($item->avg, 2), 'max_duration' => round($item->max, 2), ]; }), ); return view('livewire.pulse.api-calls', [ 'apiCalls' => $apiCalls, 'time' => $time, ]); } } // Create view: resources/views/livewire/pulse/api-calls.blade.php <x-pulse::card :cols="$cols" :rows="$rows" :class="$class"> <x-pulse::card-header name="API Calls"> <x-slot:icon> <x-pulse::icons.signal /> </x-slot:icon> </x-pulse::card-header> <x-pulse::scroll :expand="$expand"> @if ($apiCalls->isEmpty()) <x-pulse::no-results /> @else <div class="grid gap-3"> @foreach ($apiCalls as $call) <div class="flex items-center gap-3"> <div class="flex-1 truncate">{{ $call->url }}</div> <div class="text-gray-500"> {{ $call->count }} calls </div> <div class="text-gray-500"> Avg: {{ $call->avg_duration }}ms </div> </div> @endforeach </div> @endif </x-pulse::scroll> </x-pulse::card> // Register the recorder in config/pulse.php 'recorders' => [ App\Pulse\Recorders\ApiCallRecorder::class => [ 'enabled' => true, ], ],

Recording Custom Metrics

<?php use Laravel\Pulse\Facades\Pulse; // Record a simple counter Pulse::record( type: 'user_login', key: auth()->id(), )->count(); // Record with value (for averages, max, etc.) Pulse::record( type: 'file_upload', key: $file->name, value: $file->size, )->count()->avg()->max(); // Record with timestamp Pulse::record( type: 'payment_processed', key: $payment->id, value: $payment->amount, timestamp: $payment->processed_at, )->sum(); // Example: Track feature usage class FeatureUsageMiddleware { public function handle($request, Closure $next) { $response = $next($request); // Record feature usage if (auth()->check()) { Pulse::record( type: 'feature_usage', key: $request->route()->getName(), )->count(); } return $response; } } // Example: Track cache effectiveness Cache::macro('rememberWithPulse', function ($key, $ttl, $callback) { $hit = Cache::has($key); Pulse::record( type: 'cache_access', key: $hit ? 'hit' : 'miss', )->count(); return Cache::remember($key, $ttl, $callback); });
Data Retention: Pulse stores raw metrics for 7 days by default. For long-term analytics, export data to a dedicated analytics platform. Use the trim configuration to balance storage and retention needs.

Monitoring Slow Queries and Optimization

<?php // Configure slow query threshold in config/pulse.php Pulse\Recorders\SlowQueries::class => [ 'enabled' => true, 'threshold' => 1000, // milliseconds ], // View slow queries in dashboard // Pulse automatically captures: // - SQL query // - Execution time // - Location (file and line) // - Frequency // Example optimization workflow: // 1. Identify slow query in Pulse dashboard // 2. Analyze the query and execution plan DB::enableQueryLog(); User::with('posts.comments')->get(); dd(DB::getQueryLog()); // 3. Add appropriate indexes Schema::table('posts', function (Blueprint $table) { $table->index(['user_id', 'created_at']); }); // 4. Use eager loading to reduce N+1 queries // BEFORE (N+1 problem) $users = User::all(); foreach ($users as $user) { echo $user->posts->count(); // Separate query for each user } // AFTER (Optimized) $users = User::withCount('posts')->get(); foreach ($users as $user) { echo $user->posts_count; // Single query with join } // 5. Verify improvement in Pulse dashboard

Exception Tracking and Debugging

<?php // Pulse automatically captures all exceptions // View in Pulse dashboard with: // - Exception type and message // - Stack trace // - Occurrence frequency // - First and last seen timestamps // Ignore specific exceptions Pulse\Recorders\Exceptions::class => [ 'enabled' => true, 'ignore' => [ Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class, Illuminate\Auth\AuthenticationException::class, ], ], // Add context to exceptions try { $result = $this->processPayment($order); } catch (PaymentException $e) { Pulse::record( type: 'payment_failure', key: $order->id, )->count(); report($e); throw $e; } // Monitor exception trends // Use custom card to track exception rates over time
Exercise 1: Install Laravel Pulse and configure it for your application. Create a custom card that tracks user registration rates per hour. Display total registrations, peak hours, and trends.
Exercise 2: Build a custom recorder that monitors external API response times. Create a Pulse card showing the slowest APIs, average response times, and failure rates. Implement alerting when APIs exceed thresholds.
Exercise 3: Use Pulse to identify and optimize the 5 slowest queries in an existing application. Document the original query times, optimization strategies applied (indexes, eager loading, caching), and performance improvements achieved.