m6web/statsd-request-headers-bundle
A Symfony bundle that emits StatsD metrics for incoming HTTP request headers, so you can track header presence, sizes, and distributions across your application. It hooks into the m6web StatsD ecosystem to instrument requests with minimal configuration.
Read this first: v1.1.0 adds route information to emitted events and reworks the metric names. The package is still the archived 2016 codebase targeting the Symfony 2/3 era — treat it as a reference implementation of "put request headers into statsd" rather than something you composer require into a modern app. What changed in v1.1.0 is worth knowing even if you rewrite: it confirms the two things you actually want are (a) a header presence/value signal and (b) the route it happened on.
If you still want to run it:
composer require m6web/statsd-request-headers-bundle:^1.1
# if constraints block you, fork it and use a path/vcs repository
AppKernel::registerBundles() on Symfony 3, or append to config/bundles.php on Symfony 4+ (the bundle class namespace is M6Web\Bundle\StatsdRequestHeadersBundle\M6WebStatsdRequestHeadersBundle; verify the exact class name in the repo root).m6web/statsd or an equivalent). Confirm the expected service id by reading Resources/config/services.yml before wiring anything.# app/config/config.yml — verify the root key against DependencyInjection/Configuration.php
m6_web_statsd_request_headers:
headers:
- X-Request-Id
- X-Forwarded-Proto
Where to look first, in this order:
DependencyInjection/Configuration.php — the real config schema. v1.1.0 may have added a route/name option here; assume nothing.EventListener/ (or EventSubscriber/) — which kernel event fires, at what priority, and where it now reads the route from ($request->attributes->get('_route'), _route_params, or the raw path). This is the substance of the release.Resources/config/services.yml — the service id you must satisfy, and whether it's a required constructor argument.First use case worth shipping: measuring adoption of a header your edge sets, now broken down by route. "What fraction of /checkout requests arrive with X-Request-Id?" is the number that tells you whether log/trace correlation is actually possible per surface — and it's the one thing v1.1.0 makes easier than v1.0.
Presence counters, not value gauges. The natural shape is still "header present / absent" per request, now attributed to a route:
app.request.header.x_request_id.present.<route_name>:1|c
app.request.header.x_request_id.absent.<route_name>:1|c
Verify the exact separator/segment order the release uses — "optim names" almost certainly means they flattened or reordered something here.
Use the route name, never the path. _route gives you checkout_submit — a bounded, code-controlled set. The raw path gives you /orders/12345 — one metric series per order id. If the listener in v1.1.0 appends $request->getPathInfo() rather than _route, that's a cardinality landmine: strip numerics/UUIDs or turn the feature off before it reaches production.
Pick your kernel event deliberately — and this release raises the stakes. The router listener runs at priority 32 on kernel.request; anything listening above that sees _route === null. If v1.1.0 reads the route at a high priority, you'll emit a large pile of …present.n/a series that look like a config bug. Read getSubscribedEvents() and confirm the priority is below the router. On kernel.response, _route is reliably set (including _route for 404s sometimes absent) — that's the safe point if the release didn't already handle it.
Gate by environment. UDP statsd from a test suite is pure noise and can mask real metrics:
# config/packages/test/…
m6_web_statsd_request_headers:
enabled: false # or point at a noop client
Better: swap the statsd client service for a no-op implementation in test and dev rather than toggling the bundle. Route-tagged metrics are especially noisy in functional tests, where every kernel boot may be a different route.
Run a local collector and diff v1.0 → v1.1. docker run -p 8125:8125/udp statsd/statsd (or a statsd_exporter with --statsd.listen-udp) plus nc -ul 8125 is the fastest way to see actual metric strings. Do this twice — once on the old tag, once on the new — and keep the transcript. The client gives you no feedback and the release notes are three words long; the wire is the only spec.
Per-route ratios, not per-route counts. Once route is a dimension you'll be tempted to alert on raw counts. Ratios (present / (present+absent) per route) survive traffic swings and are the metric that actually answers "did the CDN stop injecting this header on checkout?".
Decorating rather than forking. The cleanest extension point is to override the listener service definition in your app and add a metric prefix or an environment tag, keeping the bundle's parsing logic untouched. With v1.1.0, this is also how you'd remove the route segment if the naming scheme doesn't suit your backend.
Pair it with logs, not instead of them. Headers-in-statsd is an aggregate signal ("is the CDN injecting X-Cache-Status, and on which routes?"); headers in a Monolog processor are the per-request detail. Route-tagged statsd plus route-tagged logs is a genuinely useful pair; route-tagged statsd alone rarely answers an incident question.
ContainerAware, non-autowired service definitions, and possibly a compiler pass that no longer resolves). A new tag on an archived repo is a courtesy release, not a maintenance commitment — it doesn't reset the migration clock. Budget for a fork, or better, a rewrite.null. Unmatched requests, console/queue contexts, and listeners above priority 32 all yield an empty route. Decide explicitly whether that becomes unknown (bounded, safe) or an empty metric segment (app.request.header.x.present.:1|c — which some backends reject outright).Authorization, Cookie, User-Agent, free-form X-Tenant) — raw values as metric names will blow up your statsd/Graphite/Prometheus backend. And now multiply by route: 40 routes × 10 header values is 400 series from a package that used to emit 10. Allowlist both axes explicitly.Authorization, Cookie, X-Api-Key, and internal trace headers can end up in a metrics pipeline with weaker access controls than your logs. Route names also leak internal structure (feature flags, A/B variants, admin paths) into a shared metrics backend. Audit both before merging.HeaderBag::get() returns the first value; multi-value headers (Forwarded, Via, Accept) will be silently truncated. If you need all values, check whether the listener uses all().composer.json likely constrains to ~5.5|~7.0. On PHP 8 you'll hit composer platform errors and runtime deprecations — and v1.1.0, being a small optimisation release, almost certainly didn't lift them. Forking to bump constraints is usually cheaper than fighting it.absent series. Same for a renamed route: refactor checkout_submit → checkout_store and you get two half-populated series with no warning. Alert on the presence ratio per route dropping, not on the metric existing.kernel.request subscriber (priority below 32) or kernel.response listener that reads _route, iterates an allowlist, calls $request->headers->has() / ->get(), and increments a counter on a modern client (league/statsd, or an OTel meter with route as an attribute) is roughly 30–40 lines and gives you real dimensions instead of string concatenation. v1.1.0 essentially documents the design you should copy. For a package with zero dependents, use the repo as a spec, not a dependency.How can I help you explore Laravel packages today?