A request that touches the web process, a queue job and an outbound API call produces three sets of logs with nothing connecting them.
// generated at the edge, or accepted from the caller
$id = $request->headers->get('X-Request-Id') ?? (string) Str::uuid();
Log::withContext(['request_id' => $id]);
// carried onto the job
SendConfirmation::dispatch($order->id)->withContext(['request_id' => $id]);
// and onto the outbound call
$request = $request->withHeader('X-Request-Id', $id);
The propagation across the queue boundary is the part that takes work — the job runs in a different process with a fresh context, so the id has to be part of the payload and restored on the handling side. Accepting an inbound header rather than always generating is what makes a trace span two of our own services, and it needs a validation rule so a caller cannot inject anything.