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

Statsd Bundle Laravel Package

m6web/statsd-bundle

Symfony bundle for sending StatsD metrics over UDP. It provides service configuration and helpers for counters, gauges, timers, and sets, making it easy to instrument applications with lightweight performance and business metrics.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is a Symfony bundle, not a Laravel package. m6web/statsd-bundle (M6Web\Bundle\StatsdBundle) registers a statsd service, a config tree under m6web_statsd, and an annotation/event listener system tied to Symfony's kernel. Composer will install it into a Laravel app, but nothing will be wired up — there is no service provider, no facade, no config publish. The only thing directly reusable in Laravel is the underlying framework-agnostic client library (m6web/statsd, the M6Web\Statsd\Client class), which the bundle wraps.

So the practical first step is to decide which layer you need:

  • You want StatsD metrics in a Laravel app → install m6web/statsd (the client), not the bundle. Skip to the service-provider wiring below.
  • You are porting a Symfony app to Laravel → install the bundle only to read its config/listener source as a reference, then re-implement the two things it actually does: a singleton-wrapped client and event/annotation-driven metric emission.

Minimal Laravel wiring for the underlying client:

composer require m6web/statsd
// config/statsd.php
return [
    'host' => env('STATSD_HOST', '127.0.0.1'),
    'port' => (int) env('STATSD_PORT', 8125),
    'namespace' => env('STATSD_NAMESPACE', 'myapp'),
    'protocol' => env('STATSD_PROTOCOL', 'udp'),
];
// app/Providers/StatsdServiceProvider.php
public function register(): void
{
    $this->app->singleton(\M6Web\Statsd\Client::class, function ($app) {
        $config = $app['config']['statsd'];
        $client = new \M6Web\Statsd\Client($config['namespace']);
        // The bundle maps this to `m6web_statsd.clients.default.servers`;
        // in plain Laravel you attach a server directly.
        $client->addServer($config['host'], $config['port'], $config['protocol']);

        return $client;
    });
}

First use case: time and count HTTP requests in a global middleware, because that is the highest-value metric and exercises the whole path (client → UDP → collector) end to end. Run socat -u UDP-RECV:8125 - in a terminal, hit a route, and confirm a line arrives before you write any more instrumentation. Once that works, add a facade (Statsd::increment(...)) or inject the client directly.

Look first at the bundle's README sections on the m6web_statsd.servers / clients config and its EventListener classes — those are the parts worth copying conceptually. Ignore the Annotations\Statsd machinery unless you are staying on Symfony.

Implementation Patterns

Singleton client + thin facade. Bind the client once (as above), then expose a facade so callers don't have to inject. Keep the facade thin — it should map 1:1 onto the client's increment, decrement, timing, gauge, update, and pass tags/sample rate through unchanged. This mirrors what the Symfony bundle does with the statsd service, and it makes the client swappable in tests.

HTTP middleware for request timing. This replaces the bundle's kernel.request/kernel.response event config and its annotation-based controller timing:

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

    $this->statsd->timing('http.request', (microtime(true) - $start) * 1000, 1.0, [
        'route' => $request->route()?->getName() ?? 'unmatched',
        'method' => $request->method(),
        'status' => $response->getStatusCode(),
    ]);
    $this->statsd->increment('http.request.count', 1.0, [
        'status' => $response->getStatusCode(),
    ]);

    return $response;
}

The bundle's annotation listener effectively does the same thing around a controller action; in Laravel, a #[StatsdMetric] PHP attribute plus a route middleware or a decorator is the equivalent. PHP attributes are the correct translation of the bundle's Doctrine annotations — do not carry over @Statsd(...) docblocks, they will be silently ignored.

Event listeners for domain metrics. The bundle's events: config is just a declarative mapping of “on event X, emit metric Y.” In Laravel you get this for free with Event::listen, which is more flexible:

Event::listen(OrderPlaced::class, function (OrderPlaced $event) {
    Statsd::increment('order.placed', 1.0, ['channel' => $event->order->channel]);
    Statsd::gauge('order.total', $event->order->total_cents);
});

Prefer explicit listeners over a generic config-driven dispatcher. The bundle's YAML event mapping is convenient in Symfony because listeners are already registered; in Laravel, a config-driven dispatcher adds indirection without buying much.

Queue and job instrumentation. Wrap jobs with a job middleware (or a base job class) that times execution and increments success/failure counters. This is where the bundle has no equivalent — its event config targets Symfony kernel events, not async workers — and where StatsD is most valuable in a Laravel app.

Testing. Bind a recording double in the container during tests and assert on emitted metrics:

$this->app->instance(Client::class, $fake = new FakeStatsdClient());
$this->get('/orders');
$fake->assertIncremented('http.request.count', ['status' => 200]);

Because the real client is fire-and-forget over UDP, tests will otherwise pass while silently emitting nothing. Always assert against a fake, never against a real socket.

Tags over metric names. The bundle predates widespread tag support; its config encourages metric names like request.GET.users.show. Use the |#tag:value format instead (Datadog/Telegraf/statsd_exporter all support it). It keeps cardinality manageable and dashboards composable.

Gotchas and Tips

  • The bundle is not maintained for Laravel, and barely for recent Symfony. Last release was August 2023, it has a low star count and zero dependents listed. Treat it as a reference implementation, not a dependency. The underlying m6web/statsd client is small and stable enough to vendor or copy into a Support namespace if you want zero external risk.
  • Namespacing is applied once, by the client. If you set namespace: myapp in config and also emit myapp.http.request, you get myapp.myapp.http.request. The bundle's clients.default.namespace key behaves the same way. Pick one place to prefix and never do it in call sites.
  • UDP is fire-and-forget by design. There is no error, no exception, no return value if the collector is down or the packet is dropped. Every "why are my metrics missing" investigation starts with socat -u UDP-RECV:8125 - or tcpdump -i lo -n udp port 8125, not with your application code. Add a dev-only fallback logger so local development surfaces intended emissions.
  • Keep packets under the MTU. A single metric with a large tag set can exceed ~1432 bytes and get fragmented or dropped. Sanitize user-derived tag values (truncate, hash high-cardinality ones) before they reach the client.
  • Timing units are not standardized. The StatsD protocol has no unit; Datadog and most modern backends assume milliseconds. Be consistent across the codebase — mixing microtime(true) - $start (seconds) with (microtime(true) - $start) * 1000 will produce dashboards that are off by a factor of 1000 and are painful to diagnose later.
  • Sample rates must be declared, not applied silently. increment($metric, 0.1) sends 1 in 10 events but the backend scales it back up. If you pass a sample rate and your collector/aggregator does not honor it, counters will read 10× low. Leave it at 1.0 unless you have a measured volume problem.
  • Config caching and env reads. If you instantiate the client with env() outside a config file, php artisan config:cache can produce a client with stale or null connection values. Always read config('statsd.*'), never env() in the service provider body.
  • Long-running workers and Octane. The singleton client holds an open socket. That is fine for UDP, but under Octane or queue workers, verify the socket survives worker recycling and that you aren't accumulating state. Prefer creating the client in register() (per-worker) rather than holding references across requests.
  • Do not block the request path. If you need guaranteed delivery, write metrics to a queue or a local buffer and flush out-of-band. The bundle offers no such mechanism; adding one is a common reason teams outgrow it.
  • High-cardinality tags are the usual performance killer. Route names, status codes, methods: good. User IDs, order IDs, raw URLs, exception messages: never. The bundle won't stop you — nothing will — so add a review checklist or a sanitizer in your facade.
  • Extension points are thin. The bundle exposes the client via DI and lets you register custom listeners; there is no metric-serializer or transport abstraction worth hooking. If you need custom transports, batching, or non-StatsD backends, use OpenTelemetry or a dedicated client instead of extending this one.
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.
abbasudo/artisan-route
mirvan/ippanel
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
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package