spatie/laravel-prometheus
Export Laravel app metrics to Prometheus via a /prometheus endpoint. Register custom gauges and counters in code, with built-in metrics for queues and Horizon. Includes optional security so your metrics aren’t publicly exposed; pair with Grafana for dashboards.
Installation:
composer require spatie/laravel-prometheus
Publish the config file:
php artisan vendor:publish --provider="Spatie\Prometheus\PrometheusServiceProvider"
Basic Metric Exposure:
Add a gauge metric in a service provider's boot() method:
use Spatie\Prometheus\Facades\Prometheus;
Prometheus::addGauge('app.active_users')
->value(fn() => User::where('last_seen_at', '>', now()->subHours(1))->count());
Access Metrics:
Visit /prometheus endpoint to see metrics in Prometheus format. Configure Prometheus server to scrape this endpoint.
Track HTTP request counts by status code:
use Spatie\Prometheus\Facades\Prometheus;
Prometheus::addCounter('http_requests_total')
->labels(['method', 'status_code'])
->increment(fn() => request()->method(), fn() => response()->getStatusCode());
Metric Types:
active_users).
Prometheus::addGauge('memory_usage')
->value(fn() => memory_get_usage(true) / 1024 / 1024);
api_calls).
Prometheus::addCounter('api_errors')
->increment(fn() => request()->wantsJson() && response()->isClientError());
Prometheus::addHistogram('request_duration_seconds')
->observe(fn() => request()->getDuration());
Conditional Metrics:
Use the Conditionable trait for dynamic metrics:
Prometheus::addGauge('high_traffic_users')
->when(fn() => request()->ip() === '192.168.1.1')
->value(fn() => User::count());
Labels for Context: Add dimensions to metrics:
Prometheus::addCounter('database_queries')
->labels(['connection', 'query_type'])
->increment(fn() => DB::connection()->getName(), fn() => 'select');
Queue Monitoring: Built-in collectors for Laravel queues (including Horizon):
// Auto-registered via config/prometheus.php
'collectors' => [
\Spatie\Prometheus\Collectors\QueueCollector::class,
\Spatie\Prometheus\Collectors\HorizonCollector::class,
],
Middleware Integration: Track request metrics in middleware:
public function handle(Request $request, Closure $next) {
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
Prometheus::addHistogram('request_latency_seconds')
->observe($duration);
return $response;
}
Event-Based Metrics: Use events to trigger metric updates:
event(new UserRegistered($user));
// In an event listener:
Prometheus::addCounter('users_registered')->increment();
Custom Collectors:
Extend \Spatie\Prometheus\Collectors\Collector for reusable logic:
class CacheCollector extends Collector {
public function collect() {
return collect([
'cache_hits' => Cache::stats()['hits'],
'cache_misses' => Cache::stats()['misses'],
]);
}
}
Metric Naming:
snake_case for metric names (Prometheus convention)./ or - may cause issues in some Prometheus setups.Performance Overhead:
User::count()) if scraped frequently.
Prometheus::addGauge('user_count')
->value(fn() => Cache::remember('user_count', 60, fn() => User::count()));
Security:
/prometheus endpoint with middleware:
'middleware' => ['auth:sanctum'], // config/prometheus.php
Label Cardinality:
user_id.Collector Registration:
boot() of a service provider to ensure they run after dependencies.Queue) must be registered after those services are bootstrapped.Verify Metrics:
/prometheus endpoint directly in a browser or with curl:
curl http://localhost:8000/prometheus
promtool to validate metrics:
promtool check rules your_rules.yml
Logging:
config/prometheus.php:
'debug' => env('APP_DEBUG', false),
Prometheus Configuration:
scrape_configs:
- job_name: 'laravel_app'
static_configs:
- targets: ['localhost:8000']
Custom Metric Types:
Extend the package by adding new metric types (e.g., Summary):
// In a custom collector:
Prometheus::addSummary('request_size_bytes')
->observe(fn() => strlen(request()->getContent()));
Dynamic Metrics: Use Laravel's service container to bind dynamic metrics:
$this->app->bind('prometheus.dynamic_metric', function () {
return Prometheus::addGauge('dynamic_metric')
->value(fn() => rand(0, 100));
});
Grafana Dashboards:
Testing:
Prometheus::shouldReceive('addGauge')->andReturnSelf();
Middleware Priority:
app/Http/Kernel.php to ensure metrics are collected before responses:
protected $middleware = [
// ...
\Spatie\Prometheus\Middleware\PrometheusMiddleware::class,
];
Environment-Specific Metrics:
if (env('ENABLE_ANALYTICS', false)) {
Prometheus::addCounter('user_actions')->increment();
}
Prometheus Client Library:
promphp/prometheus_client_php under the hood. Version conflicts may arise; pin the dependency in composer.json:
"promphp/prometheus_client_php": "^0.15.0"
How can I help you explore Laravel packages today?