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

Metrics Bundle Laravel Package

flagbit/metrics-bundle

Flagbit MetricsBundle is a Symfony bundle for gathering and exposing application metrics. It offers a simple way to instrument services and publish measurements to your monitoring stack, helping teams track performance and business KPIs. Designed for easy setup in existing Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

flagbit/metrics-bundle is a Symfony bundle, not a Laravel package. It is archived (last meaningful activity years ago), has a single star and zero dependents, and its service wiring is Symfony DI (YAML/XML + compiler passes). You should not composer require it into a Laravel app and expect anything to boot — there is no config/bundles.php, no auto-discovery, and no Illuminate\Contracts bridge.

Treat it as a reference implementation for a pattern: tag collector services, resolve them from the container, and expose an aggregated metrics output. Port the idea, not the package.

Minimal steps for a Laravel developer:

  1. Clone the repo and read, in order:
    • the collector interface (typically a small contract with a name/key and a collect()/getValue() method),
    • the compiler pass that registers services carrying its DI tag,
    • the controller or console command that iterates collectors and renders output (the bundle's only "runtime" surface).
  2. Decide on your output contract. Most teams end up with Prometheus text exposition (name{labels} value) because it scrapes cleanly; StatsD or JSON are alternatives.
  3. Re-implement the three moving parts in Laravel:
    • an interface (MetricCollector),
    • a MetricsServiceProvider that binds and tags collectors,
    • a /metrics route (or php artisan metrics:collect command) that resolves tagged collectors and renders.

First use case: expose queue depth, failed-job count, signup count for the last 24h, and cache hit ratio as a single protected /metrics endpoint scraped by Prometheus.

Implementation Patterns

Define a narrow interface. Keep it value-only; labels belong on the registration, not inside the collector.

interface MetricCollector
{
    public function name(): string;
    /** @return array<string,string|int|float> */
    public function labels(): array;
    public function value(): float|int;
}

Register and tag in a service provider. This is the Laravel analogue of the bundle's compiler pass.

final class MetricsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(MetricRegistry::class, fn ($app) =>
            new MetricRegistry($app->tagged('metrics.collectors'))
        );
    }

    public function boot(): void
    {
        $collectors = [
            QueueDepthCollector::class,
            FailedJobsCollector::class,
            SignupCountCollector::class,
            CacheHitRatioCollector::class,
        ];

        foreach ($collectors as $class) {
            $this->app->bind($class);
        }

        $this->app->tag($collectors, 'metrics.collectors');
    }
}

Aggregate lazily, render once. The registry should resolve tagged() on demand, not cache values across requests (important under Octane).

final class MetricRegistry
{
    public function __construct(private iterable $collectors) {}

    public function all(): iterable
    {
        foreach ($this->collectors as $collector) {
            try {
                yield $collector;
            } catch (Throwable $e) {
                report($e); // one broken collector must not blank the endpoint
            }
        }
    }
}

Expose a protected endpoint. Never leave /metrics public; use an internal host, a token, or throttle.

Route::get('/metrics', MetricsController::class)
    ->middleware(['throttle:60,1', 'metrics.token']);
final class MetricsController
{
    public function __invoke(MetricRegistry $registry): Response
    {
        $lines = [];
        foreach ($registry->all() as $c) {
            $labels = $c->labels();
            $suffix = $labels ? '{' . collect($labels)
                ->map(fn ($v, $k) => sprintf('%s="%s"', $k, addslashes((string) $v)))
                ->implode(',') . '}' : '';
            $lines[] = sprintf('%s%s %s', $c->name(), $suffix, $c->value());
        }
        return response(implode("\n", $lines)."\n", 200)
            ->header('Content-Type', 'text/plain; version=0.0.4');
    }
}

Push instead of pull. If you scrape nothing, schedule a command that iterates the same registry and ships to StatsD/Pushgateway. Reusing the registry keeps pull and push in sync.

Update counters from domain events. Listeners (UserRegistered, JobFailed) writing into a cache/Redis key are cheaper and more accurate than recomputing from the DB on scrape.

Test with fakes. Bind a FakeCollector into the container and assert exact exposition output; snapshot the /metrics response with assertSee on each metric name.

Gotchas and Tips

  • Archived and unmaintained. No security fixes, no PHP 8 guarantees, and its Symfony DI era is likely 2.x/3.x (old compiler-pass and service-definition APIs). Do not add it to composer.json; port the interfaces and wiring instead.
  • No Laravel bridge exists. You cannot register a Symfony bundle in a Laravel app without a full Symfony kernel, and pulling one in is not worth a five-class pattern. Reimplement.
  • Tag before tagged() resolves. $app->tag() only records tags for abstracts the container can make; bind concrete classes first or use $this->app->singleton() to be explicit.
  • Collector state leaks under Octane. Any static or singleton cache of metric values survives across requests. Resolve values per-request or key caches by request/interval.
  • Cardinality kills Prometheus. Never use user IDs, request IDs, or arbitrary URLs as label values. Labels should be bounded enumerations (status, queue, driver).
  • One failing collector should not 500 the scrape. Wrap each collector in try/catch, log, and emit a *_collector_errors_total gauge so you notice silently broken metrics.
  • Slow collectors are a self-inflicted outage. A COUNT(*) over a big table on every scrape will stall your metrics endpoint and, if it shares a pool, your app. Cache values for 15–60s in Redis or the cache store.
  • Prometheus format details bite. Escape backslashes and quotes in label values, use _total suffix for counters, do not emit +Inf/NaN unquoted incorrectly, and always terminate lines with \n.
  • Auth is not optional. Scrapers often sit on a network you trust less than you think. Use a bearer token middleware or bind the route to an internal interface.
  • Debugging: php artisan tinker → app()->tagged('metrics.collectors') to confirm registration; app(MetricRegistry::class) to inspect the resolved iterable; php artisan route:list --path=metrics to verify middleware. There is no bundle debug command to lean on.
  • Extension points worth copying: the collector interface (your seam for new metrics), the aggregator (swap the renderer for JSON/StatsD), and the tag name (metrics.collectors) as the public contract other packages can honor.
  • License note: BSD-3-Clause permits reuse of the pattern and code; if you copy classes verbatim, keep the copyright header — but writing a ~100-line replacement is usually cleaner than vendoring old code.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource