talentrydev/request-id-bundle
Adds a unique request ID to each incoming request so logs, responses, and error reports can be correlated. Exposes the ID through headers and middleware, making it simpler to trace requests across services. Lightweight drop-in integration for Laravel and PHP applications.
This is a Symfony bundle, not a Laravel package — there's no service provider or config/request-id.php. As a Laravel dev you have two paths: consume it inside a Symfony service, or read the ~100 lines of source and port the idea to a Laravel middleware (usually the smarter move given the project's state: 0 stars, 0 dependents, last release March 2021).
If you're in a Symfony app:
composer require talentrydev/request-id-bundle
Register it in config/bundles.php:
return [
// ...
Talentry\Dev\RequestIdBundle\RequestIdBundle::class => ['all' => true],
];
Where to look first, in this order:
src/ — the bundle is small; find the event subscriber/listener (likely on kernel.request / kernel.response).DependencyInjection/*Extension.php) — this tells you the real config keys and service IDs. Don't guess config from the README.First use case: put a request ID on every response and in every log line, so when a user reports "the checkout failed at 2:14pm" you can grep one ID and see the whole trace.
curl -i https://app.local/checkout | grep -i request-id
# X-Request-Id: 0f8c1e2a-...
grep 0f8c1e2a- storage/logs/*.log
If that round-trip works, the bundle is doing its job.
Inbound propagation (the important one). Most real deployments sit behind a gateway/load balancer that already assigns a correlation ID. The useful pattern is: reuse the incoming header if present, otherwise generate. Verify the bundle does this — if it unconditionally generates, the ID your edge logs and the ID your app logs won't match, which defeats the purpose.
// What you want the listener to look like
$id = $request->headers->get('X-Request-Id') ?: $this->generator->generate();
$request->attributes->set('_request_id', $id);
$response->headers->set('X-Request-Id', $id);
Monolog processor. The header alone is half the value. Wire a processor so every record carries the ID:
# config/services.yaml
services:
App\Logging\RequestIdProcessor:
arguments: ['@request_stack']
tags:
- { name: monolog.processor, method: __invoke }
final class RequestIdProcessor
{
public function __construct(private RequestStack $stack) {}
public function __invoke(array $record): array
{
$record['extra']['request_id'] =
$this->stack->getCurrentRequest()?->attributes->get('_request_id') ?? 'cli';
return $record;
}
}
Also add %extra.request_id% to your Monolog line formatter — otherwise it lands in the record but never prints.
Async work. Correlate jobs with the originating request: stash the ID into the message envelope and restore it in the worker. Without this, queue logs are orphaned from the HTTP request that caused them.
Laravel translation. If you'd rather not depend on an unmaintained bundle, the same behavior is a middleware in ~15 lines:
public function handle($request, Closure $next)
{
$id = Str::isUuid($request->header('X-Request-Id'))
? $request->header('X-Request-Id')
: (string) Str::uuid();
$request->attributes->set('request_id', $id);
app()->instance('request_id', $id); // injectable, Log::withContext()-friendly
$response = $next($request);
return $response->header('X-Request-Id', $id);
}
Pair it with Log::withContext(['request_id' => app('request_id')]) in a bootstrap step and you get the Symfony bundle's whole feature set with zero external dependency.
Testing. Assert the header exists and is stable across the request — a common bug is the response listener regenerating instead of reading the request attribute. Freeze the generator in tests for deterministic assertions.
private by default since Symfony 4), and possibly deprecations around ReturnTypeWillChange or event dispatcher signatures. Read composer.json for a hard symfony/* constraint before installing into a modern app — a ^4.0|^5.0 requirement will block install outright.X-Request-Id values invites log injection (newlines, ANSI escapes) and log-spoofing. Validate it as a UUID or at least cap length and charset before propagating.X-Request-Id; several don't by default. If it's stripped in transit, the "reuse upstream ID" logic silently never fires and every hop generates its own.kernel.request. If the subscriber runs late, early log records carry null. Check priority on the subscriber; kernel.request needs a high priority (e.g. 2048 or higher).getCurrentRequest() returns null in bin/console, so a naive processor throws or logs empty. Generate a synthetic per-command ID and make the processor null-safe.kernel.request, you may get ID churn on error pages. Guard with if (!$event->isMainRequest()) return;.final or not aliased, that's a sign the bundle wasn't designed for override — another argument for the 15-line middleware.kernel.response:dump($request->attributes->all(), $request->headers->get('X-Request-Id'));
If the attribute is set but the log formatter shows nothing, the problem is your Monolog config, not the bundle. Isolate those two layers first — it's the single most common source of "the request ID doesn't work" confusion.
How can I help you explore Laravel packages today?