Advanced Laravel
Laravel Pulse & Monitoring
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.