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"
Register Middleware (if needed):
Add Spatie\Prometheus\Middleware\PrometheusMiddleware to your app/Http/Kernel.php under $middleware or $middlewareGroups['web'].
Basic Metric Exposure:
Add a route in routes/web.php:
use Spatie\Prometheus\Facades\Prometheus;
Prometheus::addGauge('user.count')
->value(fn() => \App\Models\User::count());
Route::get('/prometheus', [\Spatie\Prometheus\Http\Controllers\PrometheusController::class, 'metrics']);
Prometheus Configuration:
Configure your Prometheus server (prometheus.yml) to scrape your Laravel app:
scrape_configs:
- job_name: 'laravel_app'
scrape_interval: 15s
static_configs:
- targets: ['your-app-url:8000']
Track HTTP request counts and durations:
Prometheus::addCounter('http.requests.total')
->labels(['method', 'endpoint'])
->increment();
Prometheus::addHistogram('http.request.duration.seconds')
->labels(['endpoint'])
->observe($durationInSeconds);
Prometheus::addGauge('cache.size')
->value(fn() => Cache::getStore()->getSize());
Prometheus::addCounter('api.errors')
->increment();
Prometheus::addHistogram('request.latency')
->observe($executionTime);
Prometheus::addSummary('response.time')
->observe($responseTime);
Add dimensions to metrics for granularity:
Prometheus::addCounter('database.queries')
->labels(['connection' => 'mysql', 'type' => 'select'])
->increment();
Use the Conditionable trait for dynamic metrics:
Prometheus::addGauge('active.users')
->when(fn() => auth()->check(), fn() => auth()->user()->active ? 1 : 0);
Leverage built-in collectors for Laravel Queues/Horizon:
// Auto-registered via config (enabled by default)
Prometheus::collectors()->addQueueCollectors();
Track requests globally:
// app/Http/Middleware/TrackRequests.php
public function handle($request, Closure $next) {
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
Prometheus::addHistogram('http.request.duration')
->labels(['method' => $request->method(), 'path' => $request->path()])
->observe($duration);
return $response;
}
Extend functionality by creating custom collectors:
use Spatie\Prometheus\Collectors\Collector;
class DatabaseCollector extends Collector {
public function collect(): array {
$queries = DB::getQueryLog();
return [
'db_queries_total' => count($queries),
'db_queries_duration_seconds_sum' => array_sum(array_column($queries, 'time')),
];
}
}
// Register in a service provider:
Prometheus::collectors()->add(new DatabaseCollector());
Register metrics dynamically (e.g., per-model):
// app/Providers/AppServiceProvider.php
public function boot() {
foreach (Model::allModels() as $model) {
Prometheus::addGauge("models.{$model}.count")
->value(fn() => $model::count());
}
}
Track rate limits:
Prometheus::addCounter('rate.limit.hits')
->labels(['endpoint' => 'api/auth'])
->increment();
Prometheus::addCounter('rate.limit.rejected')
->labels(['endpoint' => 'api/auth'])
->when(fn() => $this->isRateLimited(), fn() => 1);
Expose health metrics:
Prometheus::addGauge('system.health')
->value(fn() => app()->isDownForMaintenance() ? 0 : 1);
React to events and update metrics:
// app/Providers/EventServiceProvider.php
protected $listen = [
'eloquent.created' => [function ($model) {
Prometheus::addCounter('models.created')
->labels(['model' => class_basename($model)])
->increment();
}],
];
Metric Naming Collisions:
namespace_metric_type_metric_name)._) or dots (.).Performance Overhead:
/prometheus endpoint.Prometheus::addGauge('user.count')
->value(fn() => Cache::remember('user_count', 60, fn() => User::count()));
Label Cardinality Explosion:
Middleware Misconfiguration:
PrometheusMiddleware can expose metrics without security.app/Http/Kernel.php:
protected $middleware = [
// ...
\Spatie\Prometheus\Middleware\PrometheusMiddleware::class,
];
Queue Collector Conflicts:
'collectors' => [
'queue' => [
'enabled' => env('QUEUE_COLLECTOR_ENABLED', false),
],
],
Prometheus Scrape Timeouts:
scrape_configs:
- scrape_timeout: 30s
Inspect Metrics Locally:
Visit /prometheus in your browser to see raw metrics before configuring Prometheus.
Check Collector Registration: Debug collectors with:
dd(\Spatie\Prometheus\Facades\Prometheus::collectors()->all());
Log Metric Updates:
Enable debug logging in config/prometheus.php:
'debug' => env('PROMETHEUS_DEBUG', false),
Validate Prometheus Configuration:
Use promtool check config prometheus.yml to validate your Prometheus config.
Monitor Scraping:
Check Prometheus targets page (http://prometheus-server:9090/targets) for failed scrapes.
Security:
/prometheus endpoint is public. Always secure it:
// config/prometheus.php
'middleware' => ['throttle:60,1'],
'middleware' => ['Spatie\Prometheus\Middleware\TrustProxies'],
Collector Registry Wiping:
'wipe_collector_registry' => env('PROMETHEUS_WIPE_REGISTRY', true),
false if you want metrics to persist across requests (e.g., for counters).Custom Endpoint Path:
Change the default /prometheus path:
'route' => [
'path' => 'metrics',
'middleware' => [],
],
Prometheus Client Configuration: Customize the Prometheus client (e.g., namespace):
'client
How can I help you explore Laravel packages today?