A correlation ID is the cheapest piece of observability you can add, and one of the highest-leverage. It's a string you generate once per request and attach to everything that request touches. That's the whole idea. The payoff shows up the first time you have to answer "what happened to this one user at 14:23" and you can pull the entire story with a single filter instead of stitching it together from timestamps.
The 3am incident use case
A customer reports their checkout failed at 14:23. Without a correlation ID, you grep logs by timestamp + user ID, miss half the story because cross-service logs don't share keys, and reconstruct the flow by guessing. With a correlation ID: one grep 'req_5c1d...' across every log source and you have the full timeline.
Implementing in Laravel
app/Http/Middleware/AssignRequestId.php
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class AssignRequestId
{
public function handle(Request $request, Closure $next)
{
$id = $request->header('X-Request-Id', Str::uuid()->toString());
Log::withContext([
'request_id' => $id,
'user_id' => auth()->id(),
'route' => $request->route()?->uri(),
]);
$response = $next($request);
$response->headers->set('X-Request-Id', $id);
return $response;
}
}
Register globally in bootstrap/app.php. Every subsequent log line in that request carries request_id. The response header lets clients report the ID when filing bug reports.
Propagating to outbound HTTP
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'X-Request-Id' => request()->header('X-Request-Id'),
'traceparent' => request()->header('traceparent'), // if using OTel
])->get($url);
Or set it once via a Laravel Http::macro() so it's automatic on every outbound call.
Correlation ID vs trace ID
Pre-OpenTelemetry era: X-Request-Id was the common header, implementations varied. Post-OTel: use W3C traceparent — it encodes trace_id + span_id + sampling flag in one header, and every OTel-instrumented service understands it natively.
For interoperability, send both. X-Request-Id for simple log correlation, traceparent for tracing tooling. They can share the same underlying UUID if you want.
What to include in log context
Beyond the correlation ID itself:
user_id(hashed or numeric, not PII)routeorcontrollerdeploy_sha(git SHA of the running build)env(production, staging)
Don't lose it at the queue boundary
The place correlation IDs quietly break is the queue. A request dispatches a
job and returns; the job runs seconds later in a different process, and unless
you carried the ID across, its logs have no link to the request that caused
them. Pass the ID into the job's constructor and re-apply it with
Log::withContext at the top of
handle(). Same idea for scheduled commands
and any other async work — the ID is only as useful as the boundaries it
survives.