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

Prometheus Metrics Bundle Laravel Package

l3tum/prometheus-metrics-bundle

A Symfony bundle that exposes Prometheus metrics from your PHP application. Collect counters, gauges and histograms, serve them on a scrape endpoint, and let Prometheus monitor your app's health and performance with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this is a Symfony bundle, not a Laravel package. You cannot composer require l3tum/prometheus-metrics-bundle into a Laravel app and expect it to boot. It ships a Symfony Bundle class, a DependencyInjection\Configuration tree, service YAML, and a controller/route for the scrape endpoint — none of which Laravel's container, router, or package discovery understands. There is no service provider, no facade, no publishable config. 1.9.0 does not change this — the release note mentions only request metric labels, not framework support.

What 1.9.0 does change is that the bundle's automatic request collection now emits status and uri labels. That is the first behavioural change in years and it is the one thing you must read before trusting a single dashboard.

Verify release provenance before acting on 1.9.0. The previous version of this assessment claimed no release since 2021-02-18 and zero dependents; that claim was made with no supporting data and is now contradicted. Check Packagist for the symfony/* version constraints on 1.9.0 — if they still cap at Symfony 4/5, everything below about not installing stands. If they were widened, the package is being revived and the "dead dependency" calculus changes, though Laravel support almost certainly still does not exist.

For a Laravel developer the day-one decision is unchanged: do not install this. The bundle remains a thin Symfony-flavored wrapper around promphp/prometheus_client_php. You get the value by pulling that library directly and writing ~40 lines of glue:

  1. composer require promphp/prometheus_client_php
  2. Bind a CollectorRegistry singleton in a service provider, backed by the Redis or APCu adapter.
  3. Add a GET /metrics route returning text/plain; version=0.0.4 with the registry's output.
  4. Point a Prometheus scrape job at it and confirm with curl -s localhost:8000/metrics | head.

If you are in a mixed stack with a legacy Symfony frontend (Laravel workers + Symfony web tier), open in this order: src/DependencyInjection/ (the config tree — every knob), src/Resources/config/ (which services are wired, which storage adapter is the default), the routing definition (endpoint path), and — new in 1.9.0 — the collector/subscriber that populates status and uri. Find out exactly what string it puts in the uri label before you enable it: raw path, query string included, or matched route. That single decision determines whether your Prometheus instance survives the week.

First real use case, unchanged in shape but now label-sensitive: an http_requests_total counter with method/route/status plus an http_request_duration_seconds histogram, then a Grafana rate panel. On the Symfony side 1.9.0 may hand you most of that for free — accept it only after you have normalized or dropped the uri label.

Implementation Patterns

Instrument at the boundary, one place. A kernel.request / kernel.response listener pair (start timer, observe on response), or the bundle's built-in collector if 1.9.0 wires one up. For the Laravel equivalent, a middleware plus a terminate() hook so timing excludes response streaming. Never sprinkle counter increments through controllers — you will miss paths and double-count.

Registry access is getOrRegister*:

$counter = $registry->getOrRegisterCounter('app', 'http_requests_total', 'HTTP requests', ['method', 'route', 'status']);
$counter->incBy(1, [$method, $route, (string) $status]);

$histogram = $registry->getOrRegisterHistogram('app', 'http_request_duration_seconds', 'Latency', ['route'], [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5]);
$histogram->observe($seconds, [$route]);

Label discipline is the whole game — and 1.9.0 pushes against it. Keep cardinality bounded: method, matched route name (not raw URI), status class, queue name, command name. Never user ID, order ID, exception message, full URL, or anything unbounded. If you adopt 1.9.0's auto-collected uri label, immediately normalize it: map to the route pattern (/orders/{id}, not /orders/88213/invoices), replace unmatched paths with unmatched/other, and strip the query string unconditionally.

Label normalization pattern for the new uri label:

$uri = $request->getPathInfo();                 // no query string
$route = $router->match($uri)['_route'] ?? null;
$label = $route ? "route:{$route}" : 'unmatched'; // collapses scanner noise

Route name is better than route pattern when names are stable; both beat raw path. Collapsing unmatched into one bucket is what stops bots hammering /wp-login.php, /.env, /phpmyadmin from creating thousands of series.

Storage adapter choice determines correctness, not performance.

Adapter Use when
InMemory Unit tests only. Resets every request.
APCu Single host, PHP-FPM, shared across requests on that host.
Redis Multiple pods/hosts, or long-running workers. The safe default.

If you bridge the two ecosystems, run separate scrape targets per app and keep a shared app_ namespace prefix so dashboards and alerts survive renames and rewrites.

Testing. Swap in the in-memory adapter in the test container, hit a route, assert the rendered /metrics body contains app_http_requests_total{...} 1. For 1.9.0 specifically, add a regression test that hits a parameterized route (e.g. /orders/123) and asserts the emitted uri label is the pattern, not 123. Assert on exposition text, not internal objects — that is the only place label regressions are visible.

Gotchas and Tips

  • 1.9.0 adds uri — treat this as a cardinality and privacy hazard, not a feature. Raw URIs contain path parameters, query strings, and therefore possibly emails, order IDs, and access tokens. Two concrete rules: strip the query string, and collapse to route name/pattern. If you can't guarantee normalization, disable the label entirely rather than shipping it.
  • The status label is safer but still worth shaping. Raw status codes are bounded (roughly 20 realistic values) so per-code is fine; if you also want 2xx/4xx classes for alerting, derive them in PromQL with label_replace, don't double the label set.
  • Abandonment status is now ambiguous — verify, don't assume. 1.9.0 is a real release, but a one-line note is not a changelog. Check whether 1.9.0 changes Symfony constraints, whether there's a new maintainer, and whether it ships a security-relevant fix. Until then, treat any dependency as fork-and-own rather than composer require.
  • In-memory storage is the classic footgun. If the bundle's default adapter is InMemory, every request resets the registry and /metrics always reports zeros or a single request. Read the config default before trusting a number — especially now that a "working" auto-collected metric set may look complete while reporting nothing.
  • Scrape endpoint exposure got worse with 1.9.0. With uri labels you are now publishing a sample of your URL space, including whatever clients put in the path. Bind /metrics to an internal port, an IP allowlist, or basic auth, and exclude /metrics from your own instrumentation to avoid self-referential noise.
  • Cardinality creep is invisible until it isn't. Weekly: check count(count by (__name__)({__name__=~"app_.+"})), plus count(count by (uri)(app_http_requests_total)) now that uri exists. Alert on series growth rate, not on absolute cardinality after the fact.
  • Upgrade check after bumping to 1.9.0: adding labels to an existing metric creates new series. Old series go stale but linger for the staleness window, and any recording rules or dashboards that hardcoded a label set will show gaps. Bump, wait one staleness window, then verify panel-by-panel.
  • Histogram buckets must match your SLO. Defaults cluster under 1s; if your p99 target is 800ms, you need buckets at 0.1/0.25/0.5/0.75/1.0 or the SLO is unmeasurable.
  • Long-running workers (queue, Octane, Swoole) hold state in process memory. Ensure the adapter flushes to shared storage per job, and check the registry isn't accumulating per-process series across restarts — a uri-labeled counter in a worker is a memory leak with extra steps.
  • Config caching quirks (Symfony side). Changing bundle YAML requires bin/console cache:clear; env-var interpolation in the config tree often resolves at compile time, so a changed env var may not take effect without a rebuild. This bites hardest when toggling the new status/uri labels on.
  • Debugging a bad scrape: curl the endpoint, diff against expected metric names, then verify adapter connectivity independently — a Redis adapter that can't reach its backend may throw during scrape rather than during instrumentation, which reads as "Prometheus is broken" instead of "Redis is down." For label problems specifically, grep the exposition text for uri=" and eyeball it for query strings and IDs.
  • Laravel recommendation instead of this bundle: spatie/laravel-prometheus (same underlying client, real Laravel integration) or hand-rolled promphp glue. Both are maintained and let you swap storage adapters without a framework migration. You now also get to make a deliberate choice about uri labeling instead of inheriting one.
  • If you must extend this bundle: decorate the registry service rather than editing vendor code, and add custom collectors as tagged services. Overriding the 1.9.0 label set is best done by decorating the collector that emits it, not by post-processing the scrape output. Weigh that against the cost of owning a lightly-maintained dependency — for a Laravel shop, the glue code is still cheaper.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource