promphp/prometheus_client_php
Prometheus client library for PHP with counters, gauges, histograms, and summaries. Supports Redis, Predis, APCu/APCng, or in-memory storage for metric aggregation across workers, with simple APIs to register, update, and expose metrics for scraping.
Install the package:
composer require promphp/prometheus_client_php
Basic metric collection (e.g., in routes/web.php or a controller):
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
$registry = CollectorRegistry::getDefault();
$counter = $registry->getOrRegisterCounter('app', 'requests_total', 'Total HTTP requests', ['method']);
$counter->inc(['GET']); // Increment counter with label 'method=GET'
Expose metrics endpoint (e.g., /metrics):
Route::get('/metrics', function () {
$registry = CollectorRegistry::getDefault();
$renderer = new RenderTextFormat();
header('Content-type: ' . RenderTextFormat::MIME_TYPE);
return $renderer->render($registry->getMetricFamilySamples());
});
Track HTTP requests with labels for method, status, and route:
$requestCounter = $registry->getOrRegisterCounter(
'http',
'requests_total',
'Total HTTP requests',
['method', 'status', 'route']
);
$requestCounter->inc([
'method' => request()->method(),
'status' => response()->getStatusCode(),
'route' => request()->path()
]);
| Metric Type | Use Case | Laravel Integration Example |
|---|---|---|
| Counter | Monotonically increasing values | Track API calls, errors, or events. |
| Gauge | Current value (can increase/decrease) | Track queue size, memory usage, or active users. |
| Histogram | Distribution of values | Measure request latency or response sizes. |
| Summary | Quantiles + distribution | Track request durations with quantiles. |
Example: Request Latency Histogram
$latencyHistogram = $registry->getOrRegisterHistogram(
'http',
'request_latency_seconds',
'HTTP request latency in seconds',
['method', 'route'],
Histogram::exponentialBuckets(0.001, 2, 10) // Buckets: 0.001, 0.002, 0.004, etc.
);
// In middleware or controller:
$start = microtime(true);
$response = $kernel->handle($request);
$latency = microtime(true) - $start;
$latencyHistogram->observe($latency, [
'method' => $request->method(),
'route' => $request->path()
]);
Choose an adapter based on your environment:
$registry = new CollectorRegistry(new \Prometheus\Storage\Redis());
$registry = new CollectorRegistry(new \Prometheus\Storage\APCng());
$registry = new CollectorRegistry(new \Prometheus\Storage\InMemory());
$registry = new CollectorRegistry(new \PDO('mysql:host=localhost;dbname=prometheus'));
Redis Configuration (Laravel .env)
PROMETHEUS_REDIS_HOST=127.0.0.1
PROMETHEUS_REDIS_PORT=6379
PROMETHEUS_REDIS_PASSWORD=
\Prometheus\Storage\Redis::setDefaultOptions([
'host' => env('PROMETHEUS_REDIS_HOST'),
'port' => env('PROMETHEUS_REDIS_PORT'),
'password' => env('PROMETHEUS_REDIS_PASSWORD'),
]);
Register the registry in AppServiceProvider:
public function register()
{
$this->app->singleton('prometheus.registry', function () {
return new CollectorRegistry(
new \Prometheus\Storage\Redis()
);
});
}
public function handle($request, Closure $next)
{
$registry = app('prometheus.registry');
$start = microtime(true);
$response = $next($request);
$latency = microtime(true) - $start;
$registry->getOrRegisterCounter('http', 'requests_total')->inc();
$registry->getOrRegisterHistogram(
'http',
'request_latency_seconds'
)->observe($latency);
return $response;
}
public function handle()
{
$registry = app('prometheus.registry');
$jobGauge = $registry->getOrRegisterGauge(
'queue',
'jobs_active',
'Number of active jobs'
);
$jobGauge->inc();
try {
// Job logic
} finally {
$jobGauge->dec();
}
}
Dynamic Labels (e.g., User ID)
$userId = auth()->id();
$registry->getOrRegisterCounter(
'auth',
'login_attempts_total',
'Total login attempts',
['user_id', 'result']
)->inc(['user_id' => $userId, 'result' => 'success']);
Conditional Metrics (e.g., Errors)
try {
// Risky operation
} catch (\Exception $e) {
$registry->getOrRegisterCounter(
'errors',
'exceptions_total',
'Total exceptions',
['type', 'message']
)->inc([
'type' => get_class($e),
'message' => $e->getMessage()
]);
}
Custom Renderer (e.g., JSON for Grafana)
use Prometheus\Render\JsonFormat;
Route::get('/metrics/json', function () {
$renderer = new JsonFormat();
return response()->json($renderer->render(
app('prometheus.registry')->getMetricFamilySamples()
));
});
Prometheus Scrape Configuration
# prometheus.yml
scrape_configs:
- job_name: 'laravel_app'
static_configs:
- targets: ['laravel-app:8000'] # Docker service name or IP
| Adapter | Gotcha | Solution |
|---|---|---|
| Redis | Connection timeouts | Use persistent_connections: true in config. |
| Redis evictions | Monitor memory usage; adjust maxmemory-policy. |
|
| APCu | Shared-memory limits | Avoid large metrics; use Redis for distributed setups. |
| PDO | Slow queries | Index tables properly (e.g., metric_name). |
| InMemory | Data loss on restart | Only for testing/CLI scripts. |
Redis Connection Tips
persistent_connections to avoid overhead:
\Prometheus\Storage\Redis::setDefaultOptions([
'persistent_connections' => true,
]);
Redis and Predis with different prefixes in the same app.$counter->inc(['user_id' => (string) $userId]);
_) instead of spaces/camelCase for Prometheus compatibility:
// Good:
$counter->inc(['http_method' => 'GET']);
// Bad (Prometheus will escape):
$counter->inc(['http-method' => 'GET']);
exponentialBuckets() for skewed distributions:
Histogram::exponentialBuckets(0.001, 2, 10); // 0.001, 0.002, 0.004, ..., 1.024
How can I help you explore Laravel packages today?