promphp/prometheus_client_php
PHP Prometheus client library that uses Redis, Predis, APCu, or in-memory adapters for client-side aggregation. Register counters, gauges, histograms, and summaries, then expose metrics in Prometheus text format.
Architecture fit. This is a framework-agnostic Prometheus client, not a Laravel package. It provides CollectorRegistry, four collector types (counter, gauge, histogram, summary), pluggable storage adapters (Redis, Predis, APC, APCng, InMemory, PDO), and a RenderTextFormat exposition renderer. There is no service provider, no config file, no middleware, no facades — all Laravel wiring is ours to write. That is roughly 150–250 lines of glue (provider + config + middleware + controller), which is acceptable but must be planned as first-party code we own, not "install and go."
The critical architectural fact is that PHP is share-nothing. Client-side aggregation means the registry state must live outside the request lifecycle. That forces a choice with real operational consequences:
Integration feasibility. Straightforward technically: composer require promphp/prometheus_client_php, register the registry as a singleton, expose a route that renders $registry->getMetricFamilySamples(). The friction points are specific and worth flagging up front:
Redis manager connection. With phpredis we duplicate host/port/auth config and lose Laravel's connection pooling and failover semantics; the only clean injection path is Predis::fromExistingConnection() if we standardize on predis (non-default in Laravel). Plan for config duplication or an explicit switch to predis for the metrics client only.Redis::setPrefix() and Predis::setPrefix() share one namespace; two adapters with different prefixes in one app is explicitly unsupported. Multi-app Redis sharing requires disciplined, per-app prefixes.promphp/prometheus_push_gateway_php). This matters: queue workers and cron jobs cannot be scraped, so job/batch metrics need either push-gateway support (second dependency) or a decision to not instrument workers at all.Technical risk. Moderate. Ranked:
/metrics hit reads the entire metric family set from Redis. Scrape interval × metric count × instance count is real read load on the same instance we use for cache/session unless isolated.allkeys-lru, metric keys get evicted silently — counters reset, histograms skew, no error surfaces.Dependents: 0 in the supplied metadata (likely a registry artifact, but not verified). Bus-factor is a genuine consideration for a library sitting on the hot path of every request.Key questions for the team.
maxmemory-policy explicitly.Stack fit. Composer-native, PSR-friendly, no framework coupling. PHP ^8.2 aligns with Laravel 11/12; if any service is still on Laravel 10 with PHP 8.1, this package pins the platform floor and forces an upgrade first — verify before committing.
Wiring. Four artifacts, all ours:
config/prometheus.php — namespace, adapter selection, Redis host/port/auth/timeout, storage prefix, enabled flag.PrometheusServiceProvider — binds CollectorRegistry as a singleton with the chosen adapter; reads from the config, not hardcoded defaults.MetricsController — single /metrics action, RenderTextFormat::MIME_TYPE, cached getMetricFamilySamples() per scrape. Must be excluded from web middleware (no session, no CSRF) and explicitly excluded from its own instrumentation to prevent recursion.InstrumentHttpRequests middleware — histogram on request duration and counter on status, labelled by route (route name, never raw URI) and method. Terminate-based recording to avoid capturing post-response work.Migration path. Deliverable in one sprint, non-breaking:
PROMETHEUS_ENABLED=false kill switch. Ship it inert.Compatibility. Framework-agnostic, so no Laravel major-version coupling. Watch: PHP 8.2 floor; phpredis vs predis choice determines connection injection; APC/APCng require PECL extensions in the build image (an image change, not a Composer change); PDO adapter supports MySQL, PostgreSQL, SQLite if that path is ever chosen.
Sequencing. Dependency + kill switch → metrics backend provisioned → scrape endpoint live and scraped → HTTP metrics → domain metrics → alerting. Do not interleave with an Octane migration or a PHP version upgrade; both change the state model underneath the adapter choice.
Maintenance. Low code churn required from us; the library evolves slowly (2.7 → 2.13 over multiple minor releases, mostly adapter internals and correctness fixes, no breaking changes since the 2.0 Push Gateway extraction). The ongoing cost is not the library — it's the metrics estate: dashboards, alerts, cardinality hygiene, and the Prometheus/Grafana infrastructure itself. Budget owner time for that, not for the dependency.
Support. Apache-2.0, public issue tracker, Slack channel, CI across the four adapters with black-box and performance test suites. Vendor support does not exist — this is community-maintained. For a hot-path dependency, plan an internal fork policy: we should be able to patch and pin if needed, and should pin to an exact minor version rather than a caret range on the request path.
Scaling. Cost model is per-request Redis commands: each observation is one or more round-trips (histogram observations more than counters). At high RPS this is a measurable p99 contribution — set the connection timeout low (default 0.1s, deliberately aggressive) and measure before enabling broadly. Scrape load scales with metric family count × instances × scrape frequency, all reads. Two scaling hedges: keep the metrics Redis separate so eviction and load isolation are decoupled from cache, and enforce a hard cap on distinct label combinations per metric. Note the 2.7 fixes for Redis write-locks on histogram/gauge updates and the APCng collection-lag removal — both were genuine concurrency defects, which is a reminder to stay current rather than pin old minors indefinitely.
Failure modes. Enumerate these in the runbook:
Ramp-up. One engineer, roughly 1–2 days for the provider/config/middleware/route with a working scrape, plus ~1 week to define a metric taxonomy, wire meaningful domain metrics, and validate dashboards. The PHP side is the easy half; the Prometheus/Grafana/alerting half is where timelines actually slip. If the observability stack doesn't already exist, treat this package as a 10% line item in a larger project and sequence it behind stack provisioning — otherwise the team builds a scrape endpoint nothing scrapes.
How can I help you explore Laravel packages today?