saimaz/laravel-prometheus

Professional Prometheus metrics integration for Laravel applications

Maintainers

Package info

github.com/saimaz/laravel-prometheus

pkg:composer/saimaz/laravel-prometheus

Statistics

Installs: 85

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.0.0 2026-04-06 07:37 UTC

This package is auto-updated.

Last update: 2026-04-06 07:38:00 UTC


README

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.

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

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_SUBSCRIPTIONS = 'active_subscriptions';

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

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

    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 StripeService
{
    public function __construct(
        private readonly MetricsRegistryInterface $metrics,
    ) {}

    public function charge(float $amount): void
    {
        $start = hrtime(true);

        // ... call Stripe API ...

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

Or with the facade:

use Ninebit\LaravelPrometheus\Facades\Prometheus;

Prometheus::gauge(Metric::ACTIVE_SUBSCRIPTIONS)->set(42, ['pro']);

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;

class SubscriptionCollector implements CollectorInterface
{
    public function collect(MetricsRegistryInterface $registry): void
    {
        $counts = Subscription::query()
            ->selectRaw('plan, count(*) as total')
            ->where('active', true)
            ->groupBy('plan')
            ->pluck('total', 'plan');

        foreach ($counts as $plan => $count) {
            $registry->gauge(Metric::ACTIVE_SUBSCRIPTIONS)->set($count, [$plan]);
        }
    }
}

Register in config/prometheus.php:

'collectors' => [
    \App\Prometheus\Collectors\SubscriptionCollector::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.