Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Prometheus Bundle Laravel Package

ijanki/prometheus-bundle

ijanki/prometheus-bundle provides a Symfony integration for Prometheus monitoring. It exposes configurable application and infrastructure metrics via a scrape-ready endpoint, making it easy to collect, visualize, and alert on service health.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/prometheus.php, no facade, no auto-discovery. ijanki/prometheus-bundle exists to wire tweedegolf/prometheus-client into a Symfony DI container (prometheus.registry, storage adapters, an exporter, and a /metrics controller declared in Resources/config/). A Laravel developer does not get that wiring for free, and in practice you should not try to install it — its composer constraints target Symfony 2/3/4 era and it was last released in December 2018.

What to look at instead, in order:

  1. tweedegolf/prometheus-client — the actual library this bundle wraps. This is where the useful API lives: TweedeGolf\PrometheusClient\Registry, the Collector\Counter|Gauge|Histogram classes, and Storage\*Adapter implementations.
  2. promphp/prometheus_client_php — the maintained fork/successor that the PHP ecosystem (and every real Laravel Prometheus integration) has moved to. For a Laravel app today, this is the dependency you want.
  3. ijanki/prometheus-bundle's Resources/config/services.xml — read it as pseudo-documentation for how to wire a registry + adapter. That's the only thing you'd "leverage."

Minimal Laravel bootstrap (the pattern the bundle would have given you):

// composer require promphp/prometheus_client_php
// app/Providers/PrometheusServiceProvider.php
use Prometheus\Storage\Redis;
use Prometheus\CollectorRegistry;

public function register(): void
{
    $this->app->singleton(CollectorRegistry::class, function () {
        Redis::setDefaultOptions([
            'host' => config('prometheus.redis.host', '127.0.0.1'),
            'port' => config('prometheus.redis.port', 6379),
            'database' => config('prometheus.redis.db', 0),
            'timeout' => 0.2,
            'read_timeout' => 10,
        ]);
        return new CollectorRegistry(new Redis());
    });
}
// routes/web.php (or a dedicated routes/metrics.php)
Route::get('/metrics', function (CollectorRegistry $registry) {
    return response(
        (new RenderTextFormat())->render($registry->getMetricFamilySamples()),
        200,
        ['Content-Type' => RenderTextFormat::MIME_TYPE]
    );
})->withoutMiddleware(['web']);

First use case: request counter + latency histogram via middleware, exported at /metrics, scraped by Prometheus. That's 90% of what anyone does with this library family.

Implementation Patterns

Middleware instrumentation (the workhorse pattern).

class PrometheusMiddleware
{
    public function __construct(private CollectorRegistry $registry) {}

    public function handle($request, Closure $next)
    {
        $start = microtime(true);
        $response = $next($request);

        $this->registry->getOrRegisterCounter('app', 'requests_total', 'Total requests', ['route', 'status'])
            ->inc([$request->route()?->getName() ?? 'unmatched', (string) $response->getStatusCode()]);

        $this->registry->getOrRegisterHistogram('app', 'request_duration_seconds', 'Latency', ['route'])
            ->observe(microtime(true) - $start, [$request->route()?->getName() ?? 'unmatched']);

        return $response;
    }
}

Register it globally but exclude the metrics route itself (see Gotchas).

Domain-level counters in jobs/commands. The bundle's value in Symfony is that the registry is a shared DI service — replicate that with a singleton binding and inject CollectorRegistry into queued jobs. Count business events, not just HTTP:

$this->registry->getOrRegisterCounter('app', 'orders_placed_total', 'Orders placed', ['channel'])
    ->inc([$order->channel]);

Gauges for state you can recompute. Gauges are last-write-wins in shared storage; set them from a scheduled command (set($value, $labels)) rather than trying to inc/dec from concurrent workers, which is racy.

Adapter selection (mirroring the bundle's config keys).

Adapter Use when
InMemory CLI single-shot only — useless across FPM workers
APC / APCu Single host, multiple FPM workers; per-host, so multi-server scrapes show divergent values
Redis / Predis Default choice for any real Laravel deployment; shared across workers and hosts
PDO Only if you have no Redis and accept table growth

The Symfony bundle exposes this as prometheus.storage.type: redis plus connection params in YAML. In Laravel, translate that to a config/prometheus.php with the same shape — do not try to parse the bundle's YAML.

Porting the bundle's config surface. If you have an existing Symfony config like:

prometheus:
    storage:
        type: redis
        host: '%env(REDIS_HOST)%'

…the Laravel equivalent is a flat config file plus an adapter factory keyed on type. That's the entire "port," and it's ~30 lines.

Expose via a dedicated route file loaded without the web middleware group so you skip sessions, CSRF, and cookie encryption — scrapers send no cookies and you don't want a Set-Cookie on every scrape.

Gotchas and Tips

  • Dependency rot is the headline. tweedegolf/prometheus-client is unmaintained, the bundle is from 2018, and its Symfony constraints will fight Laravel 9+/PHP 8.1+. Expect composer require ijanki/prometheus-bundle to fail outright on a modern app. Treat this package as a reference implementation, not a dependency.
  • Never install a Symfony bundle into Laravel. There is no bundles.php, no kernel to register it, and no ContainerBuilder doing the wiring. Even if Composer resolves it, nothing happens at runtime. Port the service definitions into a provider.
  • Per-process storage silently lies. InMemoryAdapter in an FPM worker means every request starts from zero and every scrape is empty or wrong. Queue workers are separate processes too — jobs that increment counters against in-memory storage lose everything.
  • Self-instrumentation loop. If your middleware instruments all requests and the metrics route is in the same group, /metrics inflates requests_total and pollutes latency percentiles. Exclude by path or route name.
  • Don't put /metrics behind auth unless your Prometheus scraper sends credentials — and don't put it behind web at all. Scrape from an internal network or bind the route behind a token middleware you control.
  • Counter naming. The client appends _total to counters automatically in the text exposition. Naming yours requests_total yields requests_total or requests_total_total depending on the client version — check the rendered output with curl, don't guess.
  • Label cardinality is the classic outage. Adding a label with a raw user ID, URL path with IDs, or X-Request-Id will explode the Redis key space and Redis memory. Cap to route name, status class, method, tenant shard — never unbounded values.
  • Histogram buckets are declared once. getOrRegisterHistogram($ns, $name, $help, $labels, $buckets) — if you redeclare with different buckets the first registration wins and you'll be confused why your le labels don't match. Pin bucket arrays in config.
  • Debugging path: redis-cli --scan --pattern 'prometheus:*' to confirm writes are landing; curl -s localhost:8000/metrics | head -50 to confirm exposition; then check Prometheus targets page for scrape errors. If HELP/TYPE lines are missing, the exporter is being bypassed.
  • Symfony config syntax ≠ Laravel config. The bundle uses %env(VAR)% and Symfony's TreeBuilder validation. Copying YAML verbatim into config/ does nothing. Re-express as PHP arrays with env() calls.
  • Extension point worth stealing: the bundle's Exporters/ and any CollectorRegistry decoration show how to add a custom exporter or filter metric families. In Laravel, decorate the singleton rather than subclassing — same idea, one line in the provider.
  • If you're stuck on Symfony-isms: Superbalist/laravel-prometheus (historic) and spatie/laravel-prometheus-style packages exist for a reason — they do the provider + middleware + route wiring this bundle does for Symfony. Start there and skip ijanki entirely.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle