Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Prometheus Laravel Package

vmorozov/laravel-prometheus

A Laravel package for integrating Prometheus metrics into your application. Expose metrics endpoints, track counters and gauges, and add observability to Laravel services with minimal setup. Ideal for exposing application and request metrics to Prometheus scrapers.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish config

    composer require vmorozov/laravel-prometheus
    php artisan vendor:publish --provider="Vm\Prometheus\PrometheusServiceProvider"
    

    In Laravel ≥5.5 the service provider is auto-discovered; on older versions add it to config/app.php under providers.

  2. Where to look first — after publishing you'll get a config/prometheus.php (namespace, storage adapter, route enabled flag). Read src/ in this order:

    • PrometheusServiceProvider.php — what's bound in the container, what tags it registers.
    • Collectors/ — the built-in collector classes you can subclass.
    • Storage/ — the adapter (APCu / Redis / InMemory) that persists metric samples between requests.
    • routes/ (or the Route:: call in the provider) — the /metrics endpoint you expose to Prometheus.
  3. First use case — expose an HTTP-request counter so Prometheus has something to scrape:

    // app/Http/Middleware/TrackRequests.php
    public function handle($request, Closure $next)
    {
        $counter = Prometheus::counter('http_requests_total', 'Total HTTP requests')
            ->labels($request->method(), $request->path());
        $counter->inc();
        return $next($request);
    }
    

    Register it in Kernel.php and point your local Prometheus scrape_configs at http://app.local/metrics. Verify with curl -s localhost/metrics | head, you should see # HELP, # TYPE and sample lines in the text/plain; version=0.0.4 format.

Implementation Patterns

Exposing the endpoint. Either let the package register /metrics automatically (config flag route.enabled) or mount it yourself so you can wrap it in auth/throttle middleware:

Route::get('/metrics', [\Vm\Prometheus\Http\Controllers\MetricsController::class, 'show'])
    ->middleware(['auth:api', 'throttle:60,1']);

Custom collectors for domain metrics. Subclass the base collector, implement describe() (returns metric families) and collect() (populates them). Register the class in config/prometheus.php under collectors so it runs on every scrape:

class OrderCollector extends Collector
{
    public function describe(): array { /* HELP/TYPE definitions */ }
    public function collect(): array { /* query DB, build MetricFamilySamples */ }
}

Counters / gauges / histograms via the facade. Day-to-day you'll mostly use the fluent API rather than raw collectors:

Prometheus::gauge('queue_depth', 'Jobs waiting')->set(Queue::size('default'));
Prometheus::histogram('order_value_usd', 'Order totals', [10, 50, 100, 500])->observe($order->total);

Instrumenting Laravel subsystems. The common pattern is a small set of middleware + event listeners:

  • Middleware wrapping the request lifecycle for http_requests_total and http_request_duration_seconds (histogram keyed by route, method, status).
  • A listener on Illuminate\Queue\Events\JobProcessed to bump a job-success counter; one on JobFailed for failures.
  • A scheduled command that refreshes gauges every minute (active users, cache hit ratio) — gauges must be re-set, they don't decay.

Multi-process storage. For classic PHP-FPM you cannot use the in-memory adapter — each worker has its own memory. Use the Redis adapter (shared across workers and pods) or APCu if you accept per-worker visibility. Document the choice in config/prometheus.php:

'storage' => [
    'driver' => 'redis',
    'redis'  => ['connection' => 'metrics', 'prefix' => 'prom:'],
],

Grafana alignment. Keep metric names stable and snake_case; use the same label set across dashboards so PromQL joins don't break. Precompute expensive metrics in scheduled commands (or collectors with an internal Cache::remember) rather than doing heavy SQL on every scrape.

Testing. Boot the app, run the middleware, then hit /metrics in a feature test and assert the sample line is present:

$this->get('/metrics')->assertSee('http_requests_total{method="GET",path="orders"}');

Reset storage between tests (Prometheus::clear() or the adapter's flush()) so counters don't leak across cases.

Gotchas and Tips

  • Don't make /metrics public. It leaks route names, queue names, and sometimes business metrics. Put it behind network policy or a shared secret header, and remember throttle middleware can drop scrapes — Prometheus retries, but gaps appear in dashboards.
  • Label cardinality is the #1 way to kill Prometheus. Never use raw $request->path(), user IDs, order IDs, or query strings as labels. Normalize to the route name ($request->route()->getName()) and cap the number of distinct values for any label with an unbounded set (e.g. status codes → bucket into 2xx, 4xx, 5xx).
  • Counters reset on deploy if you use a non-persistent adapter: the process starts at zero and Prometheus sees a counter going backwards, which rate() interprets as a spike. Use a persistent backend or increase()/rate() tolerant dashboards with a unless guard.
  • APCu vs. FPM workers. APCu is per-process. With pm.max_children = 20 you'll get 20 divergent counters and the scraper only sees whichever worker answers. Redis (or a dedicated exporter sidecar) is the safe default in production.
  • Naming collisions with other packages. If spatie/laravel-prometheus or promphp/prometheus_client_php are also installed, they may register their own /metrics. Pick one, alias the service bindings, and check php artisan route:list | grep metrics before debugging "why is my metric missing".
  • The facade is a thin wrapper — if Prometheus::counter() returns an existing metric, ->labels(...) mutates the shared instance. Always pass labels on the same call as counter(); caching the instance and re-calling ->labels() across requests is a classic source of cross-request label bleed in Octane.
  • Octane. Long-lived workers mean InMemory storage survives between requests — metrics accumulate forever (memory leak) and reset only on Octane reload. Either flush per request via a terminating middleware or (better) use Redis and treat the adapter as authoritative.
  • Histogram bucket selection matters. Default buckets are tuned for seconds; if you're observing milliseconds or dollar amounts, pass your own buckets or every observation lands in +Inf and your p95 is meaningless.
  • Debugging a missing metric:
    1. curl -v http://localhost/metrics — check content type and that the metric family is present at all.
    2. Check describe() output — a missing # TYPE line means the collector is registered but its family isn't described; Prometheus will reject the sample.
    3. Verify the collector is in config/prometheus.php (config is cached — run php artisan config:clear after edits).
    4. If the metric appears intermittently, it's almost always the per-worker storage issue above.
  • Extension points worth knowing: the storage adapter is swappable (bind your own implementation of the package's StorageAdapter contract); collectors are additive — register one class per domain rather than one god-collector; and the exporter route/controller is not final, so you can subclass MetricsController to add auth or content negotiation for OpenMetrics (application/openmetrics-text) instead of the legacy text format.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums