dbstudios/prometheus-client
Lightweight Prometheus client for PHP. Create a CollectorRegistry, choose a storage adapter (Redis, Filesystem, APCu), then register and use counters, gauges, and histograms. Retrieve collectors by name with typed helpers for safer access.
## Getting Started
### Minimal Setup for Laravel
1. **Install the package**:
```bash
composer require dbstudios/prometheus-client
Register the service provider (Laravel 5.5+):
Add to config/app.php under providers:
DaybreakStudios\PrometheusClient\PrometheusServiceProvider::class,
Publish the config (optional):
php artisan vendor:publish --provider="DaybreakStudios\PrometheusClient\PrometheusServiceProvider"
This creates a prometheus.php config file in config/.
Basic usage in a controller or service:
use DaybreakStudios\PrometheusClient\Facades\Prometheus;
// Increment a counter
Prometheus::counter('api_requests_total', 'Total API requests')->increment();
// Observe a histogram
Prometheus::histogram('request_duration_seconds', 'Request duration in seconds', [0.1, 0.5, 1.0])
->observe(0.45);
Expose metrics endpoint (add to routes/web.php):
use DaybreakStudios\PrometheusClient\Facades\Prometheus;
Route::get('/metrics', function () {
return Prometheus::export();
});
Workflows:
Middleware for request tracking:
use DaybreakStudios\PrometheusClient\Facades\Prometheus;
public function handle($request, Closure $next)
{
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
Prometheus::histogram('http_request_duration_seconds')
->labels(['method' => $request->method(), 'path' => $request->path()])
->observe($duration);
return $response;
}
Job observability (Laravel Queues):
use DaybreakStudios\PrometheusClient\Facades\Prometheus;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class ProcessPayment implements ShouldQueue
{
use Dispatchable, Queueable;
public function handle()
{
Prometheus::counter('jobs_processed_total')->increment();
Prometheus::histogram('job_execution_time_seconds')->observe($this->execute());
}
}
Database query logging:
use DaybreakStudios\PrometheusClient\Facades\Prometheus;
use Illuminate\Support\Facades\DB;
DB::listen(function ($query) {
Prometheus::histogram('db_query_duration_seconds')
->labels(['type' => $query->bindings ? 'bound' : 'plain'])
->observe($query->time);
});
| Adapter | Use Case | Laravel-Specific Notes |
|---|---|---|
| Redis | Production (persistent, scalable) | Use Laravel's Redis cache driver config. |
| Filesystem | Debugging, local dev | Store in storage/app/prometheus (auto-created). |
| APCu | Testing (in-memory, fast) | Avoid in production; data lost on restart. |
| InMemory | Unit tests | Requires symfony/stopwatch for timers. |
Example Redis config (config/prometheus.php):
'adapter' => [
'driver' => 'redis',
'config' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', 6379),
'password' => env('REDIS_PASSWORD', null),
'prefix' => 'laravel_prom_',
],
],
| Collector Type | Laravel Use Case | Example |
|---|---|---|
| Counter | Track cumulative events (e.g., emails sent, API calls). | Prometheus::counter('emails_sent_total')->increment() |
| Gauge | Track real-time metrics (e.g., queue size, memory usage). | Prometheus::gauge('queue_size')->set(app('queue')->size()) |
| Histogram | Measure distributions (e.g., request latency, DB query times). | Prometheus::histogram('cache_hit_latency_ms')->observe($latency) |
Dynamic labels with Laravel helpers:
use Illuminate\Support\Str;
Prometheus::counter('api_errors_total')
->labels([
'endpoint' => Str::after($request->path(), '/api/'),
'status' => $response->status(),
])
->increment();
Basic endpoint (Laravel route):
Route::get('/metrics', function () {
return Prometheus::export();
})->name('metrics');
Authenticated endpoint (use Laravel middleware):
Route::get('/metrics', function () {
return Prometheus::export();
})->middleware('auth:prometheus-token'); // Custom middleware
Custom renderer (e.g., JSON for Grafana):
use DaybreakStudios\PrometheusClient\Export\Render\JsonRenderer;
Route::get('/metrics/json', function () {
$renderer = new JsonRenderer();
return response()->json($renderer->render(Prometheus::collect()));
});
| Issue | Solution |
|---|---|
| Labels missing in exports | Ensure all labels are defined during registration (not dynamically). |
| APCu not working in CLI | Add apc.enable_cli=1 to php.ini or use FilesystemAdapter for CLI. |
| Redis connection errors | Verify Laravel's Redis config matches the adapter config. |
Histograms with symfony/stopwatch |
Install via composer require symfony/stopwatch. |
Type mismatches in get*() methods |
Use getCounter(), getGauge(), etc., for IDE autocompletion. |
Check registered collectors:
dd(Prometheus::registry()->collectors());
Inspect adapter storage:
// For Redis:
dd(Prometheus::adapter()->search('*')->toArray());
// For Filesystem:
dd(scandir(storage_path('app/prometheus')));
Validate metric names:
Prometheus requires metric names to match /^[a-zA-Z_:][a-zA-Z0-9_:]*$/. Use Str::snake()` for Laravel models:
Prometheus::counter('user.' . Str::snake(class_basename($user)) . '_created_total');
Batch increments (e.g., for bulk operations):
Prometheus::counter('users_created_total')->increment(10); // +10 in one call
Avoid frequent observe() calls in loops:
// Bad: Observes 1000 times
foreach ($items as $item) {
Prometheus::histogram('item_processing_time_ms')->observe($time);
}
// Good: Batch observe
$times = array_map(fn($item) => $item->processing_time, $items);
Prometheus::histogram('item_processing_time_ms')->observe($times);
Use time() for block timing (avoids manual microtime() calls):
Prometheus::histogram('cache_operation_time_ms')->time(function () {
Cache::get('key');
});
Custom collectors (extend base classes):
use DaybreakStudios\PrometheusClient\Collector\CollectorInterface;
use DaybreakStudios\PrometheusClient\Collector\Collector;
class LaravelQueueGauge extends Collector implements CollectorInterface
{
public function set($value, array $labels = [])
{
$this->value = app('queue')->size();
$this->labels = $labels;
}
}
Dynamic metric registration (e.g., per-tenant):
Prometheus::registry()->register(
new Counter(Prometheus::adapter(), 'tenant_requests_total', 'Requests per tenant', ['tenant_id']),
['tenant_id' => auth()->user()->tenant_id]
);
Prometheus annotations (for alerts):
Prometheus::gauge('app_uptime_seconds')->set(time() - $startTime);
// Annotate in Prometheus config:
How can I help you explore Laravel packages today?