shureban/laravel-prometheus
Laravel package to expose Prometheus metrics. Install via Composer, publish config, switch Redis client to Predis, and create Counter/Gauge metrics via artisan commands or custom classes. Use DI to increment metrics with label values in your controllers.
Installation:
composer require shureban/laravel-prometheus
Register the service provider in config/app.php:
Shureban\LaravelPrometheus\PrometheusServiceProvider::class,
Publish Config:
php artisan vendor:publish --provider="Shureban\LaravelPrometheus\PrometheusServiceProvider"
Update .env to use REDIS_CLIENT=predis.
First Use Case: Generate a counter metric via CLI:
php artisan make:counter AuthEvents --name=auth_events --labels=event,user_id --description="Auth-related events"
Use it in a controller:
use App\Prometheus\AuthEvents;
public function login(AuthEvents $authEvents) {
$authEvents->withLabelsValues(['event' => 'login', 'user_id' => auth()->id()])->inc();
}
Verify Metrics Endpoint:
Access /prometheus/metrics (default route) to see collected metrics in Prometheus format.
php artisan make:counter ApiRequests --name=api_requests --labels=endpoint,status --dynamic
--dynamic enables runtime label adjustments (e.g., per-request labels).app/Prometheus/:
namespace App\Prometheus;
use Shureban\LaravelPrometheus\{Counter, Name, Labels};
class ApiRequests extends Counter {
public function __construct() {
parent::__construct(
new Name('api_requests'),
new Labels(['endpoint', 'status']),
'Total API requests by endpoint and status'
);
}
public function success(string $endpoint): void {
$this->withLabelsValues(['endpoint' => $endpoint, 'status' => '200'])->inc();
}
}
public function store(ApiRequests $apiRequests) {
$apiRequests->success('users.create');
}
withLabels() for dynamic labels (e.g., user-specific metrics):
$counter->withLabels(Labels::newFromArray([
'event' => 'purchase',
'user_id' => $user->id,
'amount' => $amount
]))->inc();
$counter->withLabels(Labels::newFromCollection($user->only(['country', 'browser'])))->inc();
class UserActivity extends Counter {
public function login(string $ip): void {
$this->withLabelsValues(['event' => 'login', 'ip' => $ip])->inc();
}
}
use Shureban\LaravelPrometheus\Http\Middleware\PrometheusMiddleware;
Route::middleware([PrometheusMiddleware::class])->group(function () {
// All routes here will auto-increment `http_requests_total`.
});
use App\Prometheus\AuthEvents;
public function handle(Registered $event, AuthEvents $authEvents) {
$authEvents->withLabelsValues(['event' => 'registration'])->inc();
}
use App\Prometheus\QueueMetrics;
public function handle(QueueMetrics $queueMetrics) {
$queueMetrics->withLabelsValues(['job' => self::class, 'status' => 'started'])->inc();
// ... job logic ...
$queueMetrics->withLabelsValues(['job' => self::class, 'status' => 'completed'])->inc();
}
/prometheus/metrics route in config/prometheus.php:
'web_route' => 'metrics',
use Shureban\LaravelPrometheus\RenderTextFormat;
$renderer = new RenderTextFormat();
file_put_contents('metrics.txt', $renderer->render());
config/prometheus.php:
'redis' => [
'connection' => 'cache', // or 'redis'
'prefix' => 'prometheus:',
],
Redis Dependency:
try {
Redis::connection()->ping();
} catch (\Predis\Connection\ConnectionException $e) {
Log::error('Redis unavailable; metrics collection paused.', ['error' => $e]);
}
Label Cardinality Explosion:
user_id) can bloat Redis memory.event_type) and dynamic labels for high-cardinality fields (e.g., user_id).Middleware Overhead:
Route::middleware([PrometheusMiddleware::class, 'sample_rate=0.1'])->group(...);
Dynamic Metrics Naming:
api_requests_{endpoint}) may conflict with static metrics.$counter = new Counter(new Name('custom_api_requests_{$endpoint}'), ...);
Prometheus Scrape Failures:
/metrics due to auth or network issues.Route::get('/health', function () {
return response()->json(['status' => 'ok']);
});
Configure Prometheus to scrape this instead if metrics are secondary.Verify Metrics Collection:
redis-cli KEYS "prometheus:*"
redis-cli HGETALL prometheus:api_requests
Log Metric Operations:
class DebugCounter extends Counter {
public function inc(): void {
Log::debug("Incrementing {$this->name} with labels: " . json_encode($this->labels));
parent::inc();
}
}
Test Locally with Prometheus:
# prometheus.yml
scrape_configs:
- job_name: 'laravel'
static_configs:
- targets: ['host.docker.internal:8000'] # or your Laravel URL
http://localhost:9090).Profile Redis Usage:
redis-cli INFO memory
config/database.php:
'options' => [
'maxmemory-policy' => 'allkeys-lru',
],
Custom Metric Types:
namespace Shureban\LaravelPrometheus;
class Histogram extends Metric {
// Custom implementation
}
Alternative Backends:
Shureban\LaravelPrometheus\Contracts\MetricStorage:
class DatabaseStorage implements MetricStorage {
public function store(Metric $metric) {
DB::table('prometheus_metrics')->insert([...]);
}
}
Metric Aggregation:
public function handle() {
$metrics = collect(Redis::hgetall('prometheus:api_requests'));
$aggregated = $metrics->groupBy('endpoint')->map(fn ($group) => $group->sum('value'));
How can I help you explore Laravel packages today?