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
Choose a storage adapter (Redis recommended for production):
// Redis (default)
$registry = \Prometheus\CollectorRegistry::getDefault();
// In-Memory (for testing)
$registry = new \Prometheus\CollectorRegistry(new \Prometheus\Storage\InMemory());
Instrument a simple metric (e.g., HTTP request counter):
$counter = $registry->getOrRegisterCounter(
'http', 'requests_total', 'Total HTTP requests', ['method', 'path']
);
$counter->inc(['method' => 'GET', 'path' => '/api/users']);
Expose metrics endpoint (e.g., /metrics):
$renderer = new \Prometheus\RenderTextFormat();
echo $renderer->render($registry->getMetricFamilySamples());
// In your Laravel middleware or controller
$counter = \Prometheus\CollectorRegistry::getDefault()
->getOrRegisterCounter(
'api', 'requests_total', 'Total API requests', ['method', 'endpoint']
);
// Increment on request
$counter->inc([
'method' => request()->method(),
'endpoint' => request()->path()
]);
// Register once (e.g., in Service Provider)
$registry = \Prometheus\CollectorRegistry::getDefault();
// Counter for failed jobs
$failedJobs = $registry->getOrRegisterCounter(
'jobs', 'failed_total', 'Total failed jobs', ['queue', 'job']
);
// Gauge for active workers
$activeWorkers = $registry->getOrRegisterGauge(
'workers', 'active', 'Number of active workers', ['type']
);
Service Provider Bootstrapping:
public function boot()
{
$this->app->singleton('prometheus.registry', function () {
return new \Prometheus\CollectorRegistry(
new \Prometheus\Storage\Redis()
);
});
}
Middleware for Automatic Metrics:
public function handle($request, Closure $next)
{
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
$registry = app('prometheus.registry');
$registry->getOrRegisterHistogram(
'http', 'request_duration_seconds', 'HTTP request latency', ['method', 'path']
)->observe($duration, [
'method' => $request->method(),
'path' => $request->path()
]);
return $response;
}
Dynamic Labels:
$counter = $registry->getOrRegisterCounter(
'db', 'queries_total', 'Total DB queries', ['connection', 'query_type']
);
// In query builder
$counter->inc([
'connection' => DB::connection()->getName(),
'query_type' => 'select'
]);
Constant Labels:
$counter = $registry->getOrRegisterCounter(
'app', 'deployments_total', 'App deployments', ['environment']
);
$counter->inc(['environment' => config('app.env')]);
// For cron jobs or batch operations
$registry = new \Prometheus\CollectorRegistry(new \Prometheus\Storage\Redis());
$counter = $registry->getOrRegisterCounter(
'batch', 'processed_items', 'Items processed in batch'
);
// Process items...
foreach ($items as $item) {
$counter->inc();
// Process $item
}
// Extend Collector for custom logic
class DatabaseCollector extends \Prometheus\Collector
{
public function collect()
{
$queries = DB::select('SELECT COUNT(*) FROM queries');
$this->metrics['queries_total'] = (int) $queries[0]->{'COUNT(*)'};
}
}
// Register in registry
$registry->registerCollector(new DatabaseCollector());
// Exponential buckets for latency metrics
$latency = $registry->getOrRegisterHistogram(
'http', 'response_time_seconds',
'HTTP response time in seconds',
['endpoint'],
\Prometheus\Histogram::exponentialBuckets(0.05, 2, 10) // 10 buckets
);
// Observe in middleware
$latency->observe($duration, ['endpoint' => $request->path()]);
// Track percentiles for API response times
$summary = $registry->getOrRegisterSummary(
'api', 'response_time_seconds',
'API response time summary',
['endpoint'],
60, // max age in seconds
[0.5, 0.9, 0.95, 0.99] // quantiles
);
// Observe in middleware
$summary->observe($duration, ['endpoint' => $request->path()]);
// Track only failed requests
if ($response->isClientError() || $response->isServerError()) {
$errors->inc([
'method' => $request->method(),
'status' => $response->status()
]);
}
// Track per-user metrics
$userId = auth()->id();
$counter = $registry->getOrRegisterCounter(
'user', 'actions_total', 'User actions', ['user_id', 'action']
);
// Increment with user context
$counter->inc(['user_id' => $userId, 'action' => 'login']);
// Track service health
$health = $registry->getOrRegisterGauge(
'service', 'health', 'Service health status', ['component']
);
// Update in health check endpoint
$health->set(1, ['component' => 'database']); // 1 = healthy
Redis Connection Drops:
\Prometheus\Storage\Redis::setDefaultOptions([
'timeout' => 0.5, // Lower for faster failures
'read_timeout' => 2.0
]);
Predis for better connection handling:
$client = new \Predis\Client(['scheme' => 'tcp', 'host' => 'redis', 'port' => 6379]);
$registry = new \Prometheus\CollectorRegistry(
\Prometheus\Storage\Predis::fromExistingConnection($client)
);
APCu Memory Limits:
apcu_cache_info()
apc.shm_size in php.ini if needed.$counter->inc(['status' => (string) $response->status()]);
__ (reserved by Prometheus).round($value, 3) if needed.MetricNotFoundException
getOrRegister* to auto-create metrics:
$counter = $registry->getOrRegisterCounter('namespace', 'name', 'desc');
RedisException with JSON decode errors.
redis-cli FLUSHDB
How can I help you explore Laravel packages today?