endclothing/prometheus_client_php
A PHP Prometheus client for instrumenting applications and exposing metrics for scraping. Provides counters, gauges, histograms, and summaries with in-memory, APC, and Redis storage adapters, plus framework-friendly integration for monitoring Laravel/PHP services.
Install the package:
composer require endclothing/prometheus_client_php
Everything lives under the Prometheus\ namespace. The three objects you touch first:
Prometheus\CollectorRegistry — register and hold metrics.Prometheus\Storage\Redis / APC / InMemory / PDO — the backing store.Prometheus\RenderTextFormat — renders the /metrics payload.Minimal endpoint:
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
use Prometheus\RenderTextFormat;
$adapter = new Redis(['host' => '127.0.0.1', 'port' => 6379]);
$registry = new CollectorRegistry($adapter);
$counter = $registry->getOrRegisterCounter(
'app',
'http_requests_total',
'Total HTTP requests',
['method', 'route', 'status']
);
$counter->inc(['GET', '/home', '200']);
$renderer = new RenderTextFormat();
header('Content-Type: ' . RenderTextFormat::MIME_TYPE);
echo $renderer->render($registry->getMetricFamilySamples());
First use case: instrument the request lifecycle — a counter for requests, a histogram for latency, a gauge for queue depth. Start by reading CollectorRegistry, the adapter class you choose, and the README examples for Counter, Gauge, Histogram, and Summary.
Centralize metric definitions. Put all getOrRegister* calls in one class or service provider. Registration is idempotent for identical config, but a changed help string or label list throws MetricsRegistrationException. One canonical definition avoids that.
Laravel integration skeleton:
// AppServiceProvider::register()
$this->app->singleton(CollectorRegistry::class, function () {
Redis::setPrefix(config('prometheus.prefix', 'app'));
$adapter = new Redis([
'host' => config('prometheus.redis.host'),
'port' => config('prometheus.redis.port'),
]);
return new CollectorRegistry($adapter);
});
// routes/api.php
Route::get('/metrics', function (CollectorRegistry $registry) {
$renderer = new RenderTextFormat();
return response($renderer->render($registry->getMetricFamilySamples()))
->header('Content-Type', RenderTextFormat::MIME_TYPE);
});
Middleware for RED metrics. Time the request, then observe a histogram labelled by route template (not raw URI):
$start = microtime(true);
$response = $next($request);
$route = $request->route()?->uri() ?? 'unmatched';
app(CollectorRegistry::class)
->getOrRegisterHistogram(
'app',
'http_request_duration_seconds',
'HTTP request duration',
['route', 'method'],
[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
)
->observe(microtime(true) - $start, [$route, $request->method()]);
Adapter selection. Redis is the default for multi-server deployments. APC/APCu is faster but shared only within one host's PHP-FPM pool. PDO works when you have a database but no Redis (expect more write load). InMemory is for tests and single-process scripts only.
CLI and cron jobs. A scrape cannot reach a short-lived CLI process, so push to a Pushgateway:
$gateway = new Prometheus\PushGateway('http://pushgateway:9091');
$gateway->pushAdd(
$registry->getMetricFamilySamples(),
'nightly-report',
['env' => 'prod']
);
Use pushAdd for accumulating counters across runs; use push when the job should replace its previous snapshot.
Testing. Swap in Prometheus\Storage\InMemory, call $registry->wipeStorage() in setUp(), then assert on getMetricFamilySamples() or rendered output. This keeps CI free of Redis/APCu.
Custom backends. Implement Prometheus\Storage\Adapter if you need DynamoDB, Memcached, etc.; the registry only depends on that interface.
getOrRegister* throws. Version the metric name or wipe storage when you intentionally change schema.inc()/incBy(). Call incBy(0, $labels) at startup to expose zero-valued series._total. Use the _total suffix yourself (http_requests_total); the library exposes the name you register. Histograms automatically expose _bucket, _sum, and _count.apc.enable_cli=1 for CLI tests. Redis is the safe shared store.Summary in this library is basic — prefer Histogram for latency quantiles.Redis::setPrefix() to namespace keys per app/environment. Call $registry->wipeStorage() only in tests or explicit admin commands — never on a live /metrics request./metrics can leak route names, versions, and traffic patterns. Restrict by network policy or auth; scrape from inside the cluster.RenderTextFormat::MIME_TYPE; ensure no BOM, debug output, or framework error page precedes the render.getMetricFamilySamples() to confirm registration; check adapter connectivity and prefix; confirm the scrape hits the same Redis DB/APC pool; confirm wipeStorage() isn't running in production.How can I help you explore Laravel packages today?