saimaz/laravel-prometheus

Professional Prometheus metrics integration for Laravel applications
2,028
Install
composer require saimaz/laravel-prometheus
Latest Version:v2.3.0
PHP:^8.2
License:MIT
Last Updated:Jul 27, 2026
Links: GitHub  ·  Packagist
Maintainer: saimaz

Laravel Prometheus

Latest Version on Packagist GitHub Tests Action Status Total Downloads License

Zero-config Prometheus metrics for Laravel. Install the package, set one env var, and your app starts exporting HTTP metrics, Horizon queue stats, and custom application metrics — ready for Grafana.

Built on promphp/prometheus_client_php.

What you get out of the box

  • HTTP metrics — request count and duration histogram, auto-registered as global middleware
  • Horizon metrics — supervisor status, jobs/min, queue workload, wait times (auto-detected)
  • /metrics endpoint — Prometheus text format, protected by IP whitelist
  • Custom metrics — define your own via PHP backed enums
  • Extensible collectors — gather point-in-time metrics on each scrape

Requirements

  • PHP 8.2+
  • Laravel 11, 12, or 13
  • Redis (recommended) or APCu for metric storage

Installation

composer require saimaz/laravel-prometheus

Add to your .env:

PROMETHEUS_ENABLED=true

That's it. Visit /metrics to see your metrics.

Optional configuration

Publish the config file only if you need to customize defaults:

php artisan vendor:publish --tag=prometheus-config

Configuration

All configuration is done via environment variables — no config file needed for most setups.

Variable Default Description
PROMETHEUS_ENABLED false Enable/disable metrics collection
PROMETHEUS_NAMESPACE APP_NAME Metric name prefix (e.g. myapp_http_requests_total)
PROMETHEUS_STORAGE redis Storage driver: redis, apc, in_memory
PROMETHEUS_REDIS_CONNECTION default Laravel Redis connection name
PROMETHEUS_PREFIX PROMETHEUS_ Redis key prefix
PROMETHEUS_ALLOWED_IPS (empty) Comma-separated IPs allowed to scrape /metrics
PROMETHEUS_ENDPOINT metrics Path for the metrics endpoint

Storage

Redis is recommended for production (metrics persist across PHP processes). The package automatically falls back to in-memory storage when:

  • PROMETHEUS_ENABLED is false
  • Redis connection fails (with error logged)
  • Running in testing environment

Built-in metrics

HTTP metrics (automatic)

Registered as global middleware — no setup needed.

Metric Type Labels
{ns}_http_requests_total Counter route, method, status
{ns}_http_request_duration_seconds Histogram route, method, status

Routes are identified by name (e.g. api.users.index) or URI pattern (e.g. api/users/{user}) to prevent label cardinality explosion. Laravel's own generated::{random} names — assigned to unnamed routes by route:cache and re-rolled on every build — are treated as unnamed so they never reach a label.

Ignoring routes — by default, metrics and horizon.* are ignored. Customize in config:

'http' => [
    'ignored_routes' => ['metrics', 'horizon.*', 'health', 'telescope.*'],
],

Horizon metrics (automatic)

Auto-detected when laravel/horizon is installed. No configuration needed.

Metric Type Labels
{ns}_horizon_status Gauge
{ns}_horizon_master_supervisors Gauge
{ns}_horizon_jobs_per_minute Gauge
{ns}_horizon_recent_jobs Gauge
{ns}_horizon_failed_jobs_per_hour Gauge
{ns}_horizon_current_workload Gauge queue
{ns}_horizon_current_processes Gauge queue
{ns}_horizon_queue_wait_time_seconds Gauge queue

Horizon metrics are scraped from the web /metrics endpoint (Redis-backed). The Horizon worker process does not need its own HTTP scrape.

Scheduler metrics (automatic)

Listens to ScheduledTaskStarting / Finished / Failed when PROMETHEUS_ENABLED=true.

Metric Type Labels
{ns}_scheduler_heartbeat_timestamp Gauge — (unix time of last finished task)
{ns}_scheduler_runs_total Counter command, status (success / failure)
{ns}_scheduler_duration_seconds Histogram command

The schedule:work container and the web app must share Redis (default) so the web scrape sees scheduler series. Alert when time() - scheduler_heartbeat_timestamp is large (scheduler silent).

Pruning stale series

Redis keeps every series it has ever stored, so it is re-exported on each scrape forever. Fixing a bad label stops new junk but never clears what is already there — use prometheus:prune-labels for that.

# Always look first: nothing is written on a dry run.
php artisan prometheus:prune-labels --match='generated::*' --dry-run

php artisan prometheus:prune-labels --match='generated::*'

The glob is tested against every label value of each stored series, so it matches on any label position. --match is repeatable. Only series that match are removed: unrelated counters, gauges, and histograms keep their values, which matters for gauges that are only refreshed by an infrequent job.

Requires the redis storage driver; the command refuses to run on any other.

Custom metrics

1. Define a metric enum

<?php

namespace App\Prometheus;

use Ninebit\LaravelPrometheus\Contracts\MetricDefinition;

enum Metric: string implements MetricDefinition
{
    case API_CALLS = 'external_api_calls_total';
    case API_DURATION = 'external_api_duration_seconds';
    case ACTIVE_SESSIONS = 'active_sessions_total';

    public function helpText(): string
    {
        return match ($this) {
            self::API_CALLS => 'Total external API calls',
            self::API_DURATION => 'External API call duration',
            self::ACTIVE_SESSIONS => 'Number of active user sessions',
        };
    }

    public function labelNames(): array
    {
        return match ($this) {
            self::API_CALLS => ['service', 'status'],
            self::API_DURATION => ['service', 'endpoint'],
            self::ACTIVE_SESSIONS => ['guard'],
        };
    }

    public function buckets(): ?array
    {
        return match ($this) {
            self::API_DURATION => [0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0],
            default => null,
        };
    }
}

2. Record metrics

Inject MetricsRegistryInterface or use the Prometheus facade:

use App\Prometheus\Metric;
use Ninebit\LaravelPrometheus\Contracts\MetricsRegistryInterface;

class EmailService
{
    public function __construct(
        private readonly MetricsRegistryInterface $metrics,
    ) {}

    public function send(string $template): void
    {
        $start = hrtime(true);

        // ... call external mail provider API ...

        $this->metrics->observeDuration(Metric::API_DURATION, $start, ['mailgun', '/v3/messages']);
        $this->metrics->counter(Metric::API_CALLS)->incBy(1, ['mailgun', 'success']);
    }
}

Or with the facade:

use Ninebit\LaravelPrometheus\Facades\Prometheus;

Prometheus::gauge(Metric::ACTIVE_SESSIONS)->set(42, ['web']);

Custom collectors

Collectors run on each /metrics scrape — useful for point-in-time metrics.

<?php

namespace App\Prometheus\Collectors;

use App\Prometheus\Metric;
use Ninebit\LaravelPrometheus\Contracts\CollectorInterface;
use Ninebit\LaravelPrometheus\Contracts\MetricsRegistryInterface;
use Illuminate\Support\Facades\DB;

class ActiveSessionsCollector implements CollectorInterface
{
    public function collect(MetricsRegistryInterface $registry): void
    {
        // Example: count active sessions from a database table
        $count = DB::table('sessions')
            ->where('last_activity', '>=', now()->subMinutes(15)->getTimestamp())
            ->count();

        $registry->gauge(Metric::ACTIVE_SESSIONS)->set($count, ['web']);
    }
}

Register in config/prometheus.php:

'collectors' => [
    \App\Prometheus\Collectors\ActiveSessionsCollector::class,
],

Custom HTTP labels

Add tenant, brand, or other labels to HTTP metrics by implementing HttpLabelProvider:

<?php

namespace App\Prometheus;

use Illuminate\Http\Request;
use Ninebit\LaravelPrometheus\Contracts\HttpLabelProvider;
use Symfony\Component\HttpFoundation\Response;

class TenantLabelProvider implements HttpLabelProvider
{
    public function labelNames(): array
    {
        return ['tenant', 'route', 'method', 'status'];
    }

    public function labelValues(Request $request, Response $response): array
    {
        return [
            $request->header('X-Tenant-ID', 'default'),
            $request->route()?->getName() ?? $request->route()?->uri() ?? 'unnamed',
            $request->getMethod(),
            (string) $response->getStatusCode(),
        ];
    }
}

Set in config:

'http' => [
    'label_provider' => \App\Prometheus\TenantLabelProvider::class,
],

Prometheus scrape config

Add your Laravel app as a target in prometheus.yml:

scrape_configs:
  - job_name: 'laravel'
    scrape_interval: 15s
    static_configs:
      - targets: ['your-app:8080']
    metrics_path: /metrics

Testing

composer test        # Run tests
composer analyse     # PHPStan static analysis
composer format      # Fix code style with Pint

Changelog

Please see CHANGELOG for more information on what has changed recently.

License

The MIT License (MIT). Please see License File for more information.

Related Packages

fogeto/laravel-server-orchestrator

Laravel Prometheus monitoring package for multi-project server orchestration. Co...

2,731 2
outboundiq/laravel-outboundiq

OutboundIQ integration for Laravel - Third-party API monitoring and analytics

1,802 0
cboxdk/laravel-telemetry

Collector-free telemetry for Laravel: Prometheus metrics, OTLP traces and events...

5,138 3
divan4ik/laravel-prometheus-exporter

A laravel and lumen service provider to export metrics for prometheus.

7 0