# Laravel Pulse
Laravel Pulse is a real-time application performance monitoring (APM) tool and dashboard designed for Laravel applications. It provides insights into slow requests, slow database queries, exceptions, queue performance, cache usage, and server health, all displayed in a beautiful Livewire-powered dashboard. The package automatically records metrics through event listeners and provides aggregation over configurable time periods.
Pulse stores performance data in dedicated database tables and supports multiple ingest drivers (direct database storage, Redis buffering) for high-performance applications. It integrates seamlessly with Laravel's authentication system to track user activity and supports Laravel Octane for high-concurrency environments. The dashboard is protected by authorization gates and can be customized through publishable views and configuration.
## Installation and Setup
### Installing via Composer
Install Laravel Pulse using Composer and publish the migrations to set up the required database tables.
```bash
# Install the package
composer require laravel/pulse
# Publish and run migrations
php artisan vendor:publish --tag=pulse-migrations
php artisan migrate
# Optionally publish the configuration
php artisan vendor:publish --tag=pulse-config
# Optionally publish the dashboard view for customization
php artisan vendor:publish --tag=pulse-dashboard
```
## Configuration
### Basic Configuration (config/pulse.php)
The main configuration file controls the dashboard path, storage driver, ingest method, and recorder settings.
```php
env('PULSE_PATH', 'pulse'),
// Subdomain for the dashboard (optional)
'domain' => env('PULSE_DOMAIN'),
// Master switch to enable/disable all recording
'enabled' => env('PULSE_ENABLED', true),
// Storage configuration
'storage' => [
'driver' => env('PULSE_STORAGE_DRIVER', 'database'),
'trim' => [
'keep' => env('PULSE_STORAGE_KEEP', '7 days'),
],
'database' => [
'connection' => env('PULSE_DB_CONNECTION'),
'chunk' => 1000,
],
],
// Ingest configuration (how data is captured)
'ingest' => [
'driver' => env('PULSE_INGEST_DRIVER', 'storage'),
'buffer' => env('PULSE_INGEST_BUFFER', 5_000),
'trim' => [
'lottery' => [1, 1_000],
'keep' => env('PULSE_INGEST_KEEP', '7 days'),
],
'redis' => [
'connection' => env('PULSE_REDIS_CONNECTION'),
'chunk' => 1000,
],
],
// Cache driver for Pulse internals
'cache' => env('PULSE_CACHE_DRIVER'),
// Middleware for dashboard routes
'middleware' => [
'web',
\Laravel\Pulse\Http\Middleware\Authorize::class,
],
];
```
### Recorder Configuration
Configure individual recorders to control what metrics are captured and their thresholds.
```php
[
// Cache hit/miss tracking
Recorders\CacheInteractions::class => [
'enabled' => env('PULSE_CACHE_INTERACTIONS_ENABLED', true),
'sample_rate' => env('PULSE_CACHE_INTERACTIONS_SAMPLE_RATE', 1),
'ignore' => [
...Pulse::defaultVendorCacheKeys(),
],
'groups' => [
'/^job-exceptions:.*/' => 'job-exceptions:*',
],
],
// Exception tracking
Recorders\Exceptions::class => [
'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true),
'sample_rate' => env('PULSE_EXCEPTIONS_SAMPLE_RATE', 1),
'location' => env('PULSE_EXCEPTIONS_LOCATION', true),
'ignore' => [
'/^App\\\\Exceptions\\\\IgnoredException/',
],
],
// Queue monitoring
Recorders\Queues::class => [
'enabled' => env('PULSE_QUEUES_ENABLED', true),
'sample_rate' => env('PULSE_QUEUES_SAMPLE_RATE', 1),
'ignore' => [],
],
// Server metrics (CPU, memory, disk)
Recorders\Servers::class => [
'server_name' => env('PULSE_SERVER_NAME', gethostname()),
'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')),
],
// Slow jobs (threshold in milliseconds)
Recorders\SlowJobs::class => [
'enabled' => env('PULSE_SLOW_JOBS_ENABLED', true),
'sample_rate' => env('PULSE_SLOW_JOBS_SAMPLE_RATE', 1),
'threshold' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000),
'ignore' => [],
],
// Slow outgoing HTTP requests
Recorders\SlowOutgoingRequests::class => [
'enabled' => env('PULSE_SLOW_OUTGOING_REQUESTS_ENABLED', true),
'sample_rate' => env('PULSE_SLOW_OUTGOING_REQUESTS_SAMPLE_RATE', 1),
'threshold' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000),
'ignore' => [],
'groups' => [
'#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*',
],
],
// Slow database queries
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),
'location' => env('PULSE_SLOW_QUERIES_LOCATION', true),
'max_query_length' => env('PULSE_SLOW_QUERIES_MAX_QUERY_LENGTH'),
'ignore' => [
'/(["`])pulse_[\w]+?\1/',
'/(["`])telescope_[\w]+?\1/',
],
],
// Slow HTTP requests
Recorders\SlowRequests::class => [
'enabled' => env('PULSE_SLOW_REQUESTS_ENABLED', true),
'sample_rate' => env('PULSE_SLOW_REQUESTS_SAMPLE_RATE', 1),
'threshold' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000),
'ignore' => [
'#^/pulse$#',
'#^/telescope#',
],
],
// User job activity
Recorders\UserJobs::class => [
'enabled' => env('PULSE_USER_JOBS_ENABLED', true),
'sample_rate' => env('PULSE_USER_JOBS_SAMPLE_RATE', 1),
'ignore' => [],
],
// User request activity
Recorders\UserRequests::class => [
'enabled' => env('PULSE_USER_REQUESTS_ENABLED', true),
'sample_rate' => env('PULSE_USER_REQUESTS_SAMPLE_RATE', 1),
'ignore' => [
'#^/pulse$#',
],
],
],
```
## Dashboard Authorization
### Configuring Access via Gate
Define authorization for accessing the Pulse dashboard in your `AuthServiceProvider`.
```php
email, [
'admin@example.com',
'developer@example.com',
]);
});
// Or allow based on role/permission
Gate::define('viewPulse', function (User $user) {
return $user->hasRole('admin');
});
}
}
```
## Pulse Facade API
### Recording Custom Entries
Use the `Pulse` facade to record custom metrics with aggregation support.
```php
count();
// Record with multiple aggregations
Pulse::record(
type: 'payment_processed',
key: 'stripe',
value: 9999, // amount in cents
)->count()->sum()->avg()->max()->min();
// Record only bucket aggregates (no raw entries saved)
Pulse::record(
type: 'memory_usage',
key: 'web-server-1',
value: 2048, // MB
)->avg()->onlyBuckets();
// Record using enum types (Laravel Pulse v1.7+)
enum MetricType: string {
case ApiLatency = 'api_latency';
case CacheSize = 'cache_size';
}
Pulse::record(
type: MetricType::ApiLatency,
key: 'users/show',
value: 45,
)->count()->avg();
```
### Setting Values (Key-Value Storage)
Store snapshot values that represent the current state rather than aggregated metrics.
```php
'web-server-1',
'cpu' => 45,
'memory_used' => 8192,
'memory_total' => 16384,
'storage' => [
['directory' => '/', 'total' => 100000, 'used' => 45000],
],
]),
);
// Store deployment information
Pulse::set(
type: 'deployment',
key: 'latest',
value: json_encode([
'version' => 'v2.3.1',
'deployed_at' => now()->toIso8601String(),
'commit' => 'abc123',
]),
);
```
### Lazy Recording
Defer recording until the end of the request to avoid performance impact during execution.
```php
count();
});
// Useful for capturing request-scoped data
$startTime = microtime(true);
// ... your code ...
Pulse::lazy(function () use ($startTime) {
$duration = (microtime(true) - $startTime) * 1000;
Pulse::record(
type: 'custom_operation',
key: 'data_sync',
value: (int) $duration,
)->count()->max();
});
```
### Controlling Recording
Temporarily disable recording or ignore specific operations.
```php
where('status', 'pending')
->get();
});
// Manually stop and start recording
Pulse::stopRecording();
// ... code that shouldn't be monitored ...
Pulse::startRecording();
// Filter entries before storage
Pulse::filter(function ($entry) {
// Don't record entries for health checks
if ($entry->type === 'slow_request' && str_contains($entry->key, 'health')) {
return false;
}
return true;
});
```
### Reporting Exceptions
Manually report exceptions to Pulse.
```php
$e]);
// Optionally report to error tracking
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
```
## Querying Data
### Retrieving Aggregate Data
Query aggregated metrics for use in custom cards or API endpoints.
```php
'["GET","/api/users","App\\Http\\Controllers\\UserController"]', 'count' => 150, 'max' => 2500],
// (object) ['key' => '["POST","/api/orders","App\\Http\\Controllers\\OrderController"]', 'count' => 45, 'max' => 1800],
// ]
foreach ($slowRequests as $request) {
[$method, $path, $controller] = json_decode($request->key);
echo "{$method} {$path}: {$request->count} requests, max {$request->max}ms\n";
}
```
### Retrieving Graph Data
Get time-series data for plotting charts.
```php
[
// 'cpu' => ['2024-01-01 00:00:00' => 45, '2024-01-01 00:06:00' => 52, ...],
// 'memory' => ['2024-01-01 00:00:00' => 8192, '2024-01-01 00:06:00' => 8456, ...],
// ],
// 'web-server-2' => [...],
// ]
// Use in a custom Livewire card
foreach ($graphData as $serverKey => $metrics) {
$cpuData = $metrics['cpu']->values()->all();
$memoryData = $metrics['memory']->values()->all();
}
```
### Retrieving Values
Get key-value stored data like server status.
```php
(object) [
// 'timestamp' => 1704067200,
// 'key' => 'web-server-1',
// 'value' => '{"name":"web-server-1","cpu":45,...}',
// ],
// ]
foreach ($servers as $key => $server) {
$data = json_decode($server->value);
echo "{$data->name}: CPU {$data->cpu}%, Memory {$data->memory_used}MB/{$data->memory_total}MB\n";
}
```
### Aggregate Totals
Get total aggregated values across all keys.
```php
15000, 'cache_miss' => 500]
$hitRate = $cacheTotals['cache_hit'] / ($cacheTotals['cache_hit'] + $cacheTotals['cache_miss']) * 100;
echo "Cache hit rate: {$hitRate}%\n";
// Get single type total
$totalExceptions = Pulse::aggregateTotal(
types: 'exception',
aggregate: 'count',
interval: CarbonInterval::day(),
);
// Returns float: 42.0
```
## Custom User Resolution
### Customizing User Display
Configure how users are displayed in the dashboard.
```php
$user->full_name,
'extra' => $user->department,
'avatar' => $user->profile_photo_url,
];
});
}
```
### Custom User Resolver
Implement a custom user resolver for complex scenarios.
```php
getAuthIdentifier();
}
public function load(Collection $keys): self
{
// Load from external service or cache
$this->users = User::whereIn('id', $keys)
->with('department')
->get();
return $this;
}
public function find(int|string|null $key): object
{
$user = $this->users->firstWhere('id', $key);
return (object) [
'name' => $user?->name ?? "Unknown (ID: {$key})",
'extra' => $user?->department?->name ?? '',
'avatar' => $user?->avatar_url ?? 'https://gravatar.com/avatar?d=mp',
];
}
}
// Register in AppServiceProvider
use App\Pulse\CustomUserResolver;
use Laravel\Pulse\Contracts\ResolvesUsers;
$this->app->bind(ResolvesUsers::class, CustomUserResolver::class);
```
## Custom Dashboard Cards
### Creating a Custom Livewire Card
Build custom dashboard cards that extend the base Card class.
```php
remember(fn () => [
$this->aggregate('api_call', ['count', 'avg', 'max']),
now()->toDateTimeString(),
]);
return View::make('livewire.pulse.custom-api-metrics', [
'apiCalls' => $apiCalls,
'time' => $time,
'runAt' => $runAt,
]);
}
}
```
```blade
{{-- resources/views/livewire/pulse/custom-api-metrics.blade.php --}}
@if ($apiCalls->isEmpty())
@else
Endpoint
Count
Avg
Max
@foreach ($apiCalls as $call)
{{ $call->key }}
{{ number_format($call->count) }}
{{ number_format($call->avg) }}ms
{{ number_format($call->max) }}ms
@endforeach
@endif
```
### Registering Custom Cards
Register your custom card with Livewire.
```php
{{-- Custom card --}}
```
## Artisan Commands
### pulse:work Command
Process incoming Pulse data from the ingest stream (used with Redis ingest driver).
```bash
# Start the worker process
php artisan pulse:work
# Stop when the stream is empty (useful for testing)
php artisan pulse:work --stop-when-empty
# Run with Supervisor (recommended for production)
# /etc/supervisor/conf.d/pulse.conf
[program:pulse-worker]
process_name=%(program_name)s
command=php /var/www/html/artisan pulse:work
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/pulse-worker.log
```
### pulse:check Command
Capture server metrics (CPU, memory, disk) on a regular interval.
```bash
# Take continuous snapshots (every second)
php artisan pulse:check
# Take a single snapshot
php artisan pulse:check --once
# Run with Supervisor for continuous monitoring
# /etc/supervisor/conf.d/pulse-check.conf
[program:pulse-check]
process_name=%(program_name)s
command=php /var/www/html/artisan pulse:check
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/pulse-check.log
```
### pulse:restart Command
Gracefully restart the pulse:work and pulse:check processes.
```bash
# Signal workers to restart after next iteration
php artisan pulse:restart
```
### pulse:clear Command
Clear all Pulse data from the database.
```bash
# Clear all Pulse data
php artisan pulse:clear
# Clear specific types only
php artisan pulse:clear --type=slow_query --type=exception
```
## Custom Server Metrics
### Custom CPU Detection
Override the default CPU detection for specialized environments.
```php
(int) ($memLimit / 1024 / 1024), // MB
'used' => (int) ($memUsage / 1024 / 1024), // MB
];
});
}
```
## Ignoring Routes
### Disabling Automatic Routes
Disable Pulse's automatic route registration to define custom routes.
```php
group(function () {
Route::get('/admin/pulse', function () {
return view('pulse::dashboard');
})->name('admin.pulse');
});
```
## Adding Custom CSS
### Including Additional Styles
Add custom CSS to the Pulse dashboard.
```php
.pulse-card { border-radius: 1rem; }';
}
});
// Add multiple CSS files
Pulse::css([
resource_path('css/pulse-custom.css'),
resource_path('css/pulse-dark.css'),
]);
}
```
## Production Deployment
### Using Redis Ingest Driver
Configure Redis as the ingest driver for high-traffic applications.
```env
# .env
PULSE_INGEST_DRIVER=redis
PULSE_REDIS_CONNECTION=default
PULSE_INGEST_BUFFER=5000
```
```php
[
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
],
```
### Supervisor Configuration
Run Pulse workers in production with Supervisor.
```ini
; /etc/supervisor/conf.d/pulse.conf
[program:pulse-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan pulse:work
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/pulse/worker.log
stopwaitsecs=3600
[program:pulse-check]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan pulse:check
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/pulse/check.log
```
Laravel Pulse is designed for developers who need real-time visibility into their Laravel application's performance without the complexity of external APM services. It excels at identifying slow database queries, tracking exception frequency, monitoring queue throughput, and observing server resource utilization. The package's Livewire-powered dashboard provides instant updates and can be customized with additional cards for application-specific metrics.
For production deployments, Pulse supports high-traffic scenarios through its Redis ingest driver, which buffers metrics before batch processing. The package integrates seamlessly with Laravel Octane for high-concurrency applications and includes built-in support for Laravel Vapor environments. By combining automatic event-based recording with manual instrumentation APIs, Pulse provides comprehensive observability while maintaining minimal performance overhead.