m6web/xrequestuid-bundle
A Symfony bundle that assigns a unique ID to each incoming request and exposes it via the X-Request-Uid header, making it easy to correlate logs and trace requests across services. Lightweight, simple to install, and works with Symfony's request/response cycle.
Read this first: the package is not usable as-is on Laravel. m6web/xrequestuid-bundle is a Symfony bundle (it ships a Bundle class, a DI extension, and a kernel event listener). It was archived in 2020, has 3 stars and 0 dependents, requires a Symfony framework version that no longer exists, and its composer.json will conflict with or sit dead-weight next to your Laravel install. Do not composer require it into an app. Instead, treat it as a ~100-line reference implementation and port it.
Where to look first (5 minutes):
src/ — expect four or five files: an event listener on kernel.request, a UID generator/service, a header-name config node, and possibly a Monolog processor and/or a Guzzle subscriber.DependencyInjection/Configuration.php — this tells you the real extension points: header name, whether an inbound UID is trusted, and the service ID of the generator.Resources/config/services.xml — shows listener priority, which matters more than anything else in the bundle.Tests/ — the functional test shows the exact round-trip: request without header → response with header; request with header → same value echoed back.The one idea worth stealing: every inbound request gets a correlation ID; it is echoed on the response, injected into every log line produced while handling that request, and forwarded on every outbound HTTP call. That is the whole product.
Minimal Laravel port — start here (app/Http/Middleware/AssignRequestUid.php):
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class AssignRequestUid
{
public const HEADER = 'X-Request-Uid';
public const ATTR = 'request_uid';
public function handle(Request $request, Closure $next): Response
{
$uid = $this->resolve($request);
$request->attributes->set(self::ATTR, $uid);
app()->instance('request.uid', $uid); // scoped in Octane
\Log::withContext(['request_uid' => $uid]);
$response = $next($request);
$response->headers->set(self::HEADER, $uid);
return $response;
}
private function resolve(Request $request): string
{
$incoming = $request->headers->get(self::HEADER);
if (is_string($incoming) && preg_match('/^[A-Za-z0-9\-_]{8,64}$/', $incoming)) {
return $incoming; // continue an existing trace
}
return (string) Str::ulid();
}
}
Register it globally (Laravel 11 bootstrap/app.php → ->withMiddleware(fn ($m) => $m->append(AssignRequestUid::class)), or $middleware array on older versions), plus a tiny helper:
function request_uid(): ?string { return app()->bound('request.uid') ? app('request.uid') : null; }
First real use case: a support ticket says "checkout failed at 14:32." You grep request_uid across app logs, nginx logs, and the two downstream services, and get the full call chain in one query.
Inherit, don't regenerate. The single most important behavior in the original bundle is: if X-Request-Uid arrives on the request, reuse it. Only mint a new one at the true edge (browser, mobile app, cron, load test). Everything downstream echoes. This is what turns isolated log lines into a trace.
Outbound HTTP — Laravel's Http client (10.x+):
// AppServiceProvider::boot()
Http::globalRequestMiddleware(fn ($request) => $request->withHeader(
'X-Request-Uid', request_uid() ?? (string) Str::ulid()
));
On older Laravel, a Guzzle Middleware::mapRequest pushed via Http::macro or a bound GuzzleHttp\Client factory does the same. Note the bundle ships (or documents) a Guzzle subscriber for exactly this — that's the piece you're replacing.
Outbound HTTP — anything else: curl wrappers, AWS SDK, Stripe SDK, and SOAP clients do not go through Http. Wrap them or set the header explicitly. A missing UID on 20% of your outbound calls is a common and confusing outcome.
Queues. A queued job runs in a fresh process with no request. Attach the UID to the payload, then re-establish it in the worker:
// AppServiceProvider::boot()
Queue::createPayloadUsing(fn () => ['request_uid' => request_uid()]);
// base Job class, at the top of handle()
if ($uid = $this->job?->payload()['request_uid'] ?? null) {
app()->instance('request.uid', $uid);
\Log::withContext(['request_uid' => $uid]);
}
Without this, the job's logs are orphaned from the request that dispatched it — the exact failure mode the bundle existed to prevent, in the exact place it can't help you.
Logging. In Symfony the bundle wires a Monolog processor. In Laravel, Log::withContext() in the middleware covers everything dispatched afterwards, and Context is reset per request by the framework. For structured/JSON logs, also put it in the Monolog extra via a processor if you want it on records emitted by third-party packages that bypass the facade.
Sub-requests and internal forwarding. Symfony's kernel.request fires for sub-requests (ESI, fragments); the bundle guards against re-generating. Laravel's analogue is $request->duplicate() and Route::dispatch() — since you store the UID on the request attributes and in the container, forward the attribute explicitly rather than relying on the middleware running twice.
Responding to the client. Echo the header on the response (shown above) and, if you have a JSON error envelope, include request_uid in it. Support teams copy that string out of the UI and hand it to you — this is the highest-ROI five lines in the whole setup.
Console commands. No HTTP request exists. Either mint a UID per command (Artisan::starting(...)) or let the command's own logs carry a command_run ID. Don't call request_uid() unguarded outside HTTP.
Deprecation is the headline. Archived, no releases since Jan 2020, no maintainer. Expect zero compatibility with PHP 8 attributes, Symfony 6/7, or any modern static analysis baseline. Treat any attempt to actually install it as a dead end — the Symfony framework-bundle dependency alone will fight Laravel's Symfony components. Port the concept; don't vendor the code wholesale either, since you'd inherit its assumptions.
Never trust an inbound UID blindly. It is user-controlled input written straight into your logs. Enforce a charset and length (the regex above), or an attacker can forge trace continuity between requests, inject newlines into log files, or blow up your log index cardinality with garbage IDs. The original bundle's "reuse the incoming header" behavior is right in a trusted service mesh and wrong at a public edge — make it configurable and default to generate at the edge.
Header stripping is real. Some proxies, WAFs, and CDNs drop or rewrite unknown X- headers; nginx by default discards headers with underscores (underscores_in_headers off), so a configurable header name is genuinely useful — X-Request-Id is a safer default than anything with an underscore. Verify end-to-end with curl -v through every hop, not just locally.
Collision and format. uniqid() (what bundles of this vintage typically use) is time-based and collides across hosts at the same microsecond. Use Str::ulid() or Str::uuid() — sortable, collision-safe, and short enough to paste into a ticket. Keep it short; it goes in every log line, so a 128-char ID is a measurable storage cost.
Octane / long-running workers. app()->instance() is not request-scoped in Octane unless you use app()->scoped() or explicitly forget the binding in terminate(). If you skip this, request N+1's logs get request N's UID — silently, and only in production. Same class of bug as a leaked container binding.
Listener/middleware ordering. In Symfony, the bundle's listener priority determines whether it runs before anything that logs. In Laravel, put the middleware near the top of the global stack — before TrimStrings/SubstituteBindings error paths — or validation-failure logs will have no UID. If you use Log::withContext, note that anything logged by an earlier middleware is already missed.
Extension points worth preserving in your port: (1) a configurable header name, (2) a swappable UID generator service so tests can freeze the value, (3) a config flag for trust-inbound, (4) an off-switch for environments where an upstream gateway already assigns IDs. The bundle's Configuration.php gives you the shape; your config/request_uid.php gives you the same.
Testing. Assert the header is present and matches /^[0-9A-Z]{26}$/; assert an inbound header with a valid ID is echoed verbatim; assert a malformed inbound header (spaces, \n, 4 KB) is replaced, not propagated. Bind a fake generator in the test container rather than regex-matching a random value.
Migration target. If you're doing this seriously in 2024+, the modern equivalent is OpenTelemetry trace propagation (traceparent header) via open-telemetry/opentelemetry-php, or symfony/uid in a Symfony app. A hand-rolled X-Request-Uid coexists fine with traceparent; just decide which one is authoritative for log correlation and don't maintain two half-populated IDs.
How can I help you explore Laravel packages today?