hostme/request-id-bundle
Symfony bundle that assigns a unique request ID to every incoming HTTP request, propagates it through response headers and application logs, and makes it simple to correlate traces across distributed services for faster debugging and better observability.
First, the reality check: hostme/request-id-bundle is a Symfony bundle, not a Laravel package. If you're a Laravel developer looking at this repo, you will not be able to composer require it into a Laravel app and get anything useful — there's no service provider, no Illuminate integration, and Laravel has no bundle registration mechanism. Its value to you is as a reference implementation for the correlation-ID pattern, plus direct use if your stack also contains Symfony services (e.g. a Symfony gateway or sidecar talking to your Laravel APIs).
With that in mind, the minimal path for a Symfony service:
composer require hostme/request-id-bundle
No Flex recipe exists (3 stars, 0 dependents), so registration is manual. Add the bundle class to config/bundles.php:
return [
// ...
HostMe\RequestIdBundle\RequestIdBundle::class => ['all' => true],
];
Where to look first — the package is tiny, so read it before configuring it:
vendor/hostme/request-id-bundle/src/DependencyInjection/Configuration.php — the authoritative config tree. Do not trust blog posts or memory; keys like header name, trust-incoming-header, and generator service are defined here.php bin/console config:dump-reference <bundle-alias> — prints the full tree with defaults.src/EventListener/ (or EventSubscriber/) — the kernel request/response hooks that create the ID and set the response header.src/Log/ or src/Monolog/ — the processor that injects the ID into log records.First use case: one HTTP endpoint, prove the loop closes.
# Send a known correlation ID in
curl -i -H 'X-Request-Id: demo-1234' https://app.test/api/ping
# Expect: response carries X-Request-Id: demo-1234
# Expect: the log line for that request contains demo-1234
If both hold, the bundle is wired. If the response header is missing, the response listener isn't registered. If the log line is missing the ID, the Monolog processor isn't attached to the handler you actually use (see Gotchas).
Adopt-or-generate at the edge. The pattern that makes correlation IDs useful across microservices is: trust an inbound X-Request-Id from your gateway/load balancer if present, otherwise generate one. Configure the bundle to accept incoming values, and make the gateway the only component that mints IDs from outside traffic. Everything downstream — Symfony, Laravel, Node, whatever — just passes it along.
Log enrichment, not log formatting. Configure the processor to add the ID to Monolog's extra array (or context), not to hand-roll a formatter string. That way every handler keeps working — file, stderr, Sentry, ELK — and you can still index on extra.request_id without regex.
# config/packages/monolog.yaml (illustrative)
monolog:
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
channels: ['!event']
# ensure the request_id processor applies to this handler
Outbound propagation via HTTP client middleware. The bundle's selling point is that outgoing calls to other services carry the same ID. In Symfony that's usually a ScopingHttpClient decorator or a Guzzle middleware that reads the current ID from the request context and sets the header. Scope it to internal clients only — leaking an internal correlation ID to third-party APIs (Stripe, SendGrid) is noise, and some vendors reject unknown headers.
Queue/async handoff. A correlation ID dies the moment work leaves the HTTP process. Carry it explicitly:
$bus->dispatch(new SendInvoice($invoiceId, $requestId));
Then in the handler, wrap execution in the context-restore call so logs from the worker still group under the originating request. This is the single highest-value thing you can do with a request-ID package, and it's usually not included — budget a small amount of glue code.
Cross-stack contract with Laravel. If you're the Laravel developer on a mixed team, define the contract once and implement it twice. Laravel side:
// app/Http/Middleware/RequestId.php
public function handle($request, Closure $next)
{
$id = $request->header('X-Request-Id') ?: (string) Str::uuid();
app()->instance('request_id', $id);
Log::withContext(['request_id' => $id]);
$response = $next($request);
return $response->header('X-Request-Id', $id);
}
And outbound: Http::withHeaders(['X-Request-Id' => app('request_id')])->get(...). Same header name, same accept-or-generate rule, same log key. Now a single ID traces a request from Laravel → Symfony → back.
Support-ticket pattern. Surface the ID to users. Return it in the response header and in error payloads, so a customer pasting an error into a ticket gives you the exact log slice. This is the day-to-day payoff, and it's worth wiring before you need it.
illuminate/support constraint. Do not attempt composer require in a Laravel app expecting it to boot. Reimplement the ~60 lines above instead.composer.json constraints before adopting; if it blocks your upgrade, the code is small enough to vendor into src/ and own outright — which is probably the right call for something this thin.X-Request-Id: ../../etc/passwd or a megabyte of junk. Validate against a strict pattern (UUID/ULID or [A-Za-z0-9._-]{1,128}) and regenerate on mismatch. Log injection via header value is a real, boring vulnerability.channels exclusions can silently skip the one you care about. Verify by checking the actual log file, not by reading config.kernel.request fires for sub-requests too. You want the ID generated once on the master request and reused; a bundle that regenerates on every sub-request will fragment your logs. Check the listener's isMainRequest()/isMasterRequest() guard.config/bundles.php is manual both ways; removing the package leaves a dangling class reference that hard-fails the kernel. Remove the entry first.web-/job- so log filters can separate traffic classes), and override the header name if an upstream proxy already claims X-Request-Id.extra.request_id. Three tests, and the pattern can't silently regress.How can I help you explore Laravel packages today?