softwax/correlation-ids-bundle
A PHP/Symfony bundle that generates and propagates correlation IDs across requests, responses, and logs, making it easier to trace distributed transactions and debug issues end-to-end.
src/ before installing. This is published as a Symfony bundle (*Bundle suffix, SoftWax vendor), not a Laravel package. The two things you actually care about live there: the DI extension/config class, and the request/response event subscriber that generates the ID, reads the inbound header, and writes it back. Those ~50 lines are the whole product; everything else is framework glue you will likely replace.composer.json for symfony/framework-bundle / symfony/http-kernel requirements. If it is Symfony-only, do not fight it into Laravel's container — port the behaviour into a middleware and a Monolog processor. For a concern this small, reimplementation is usually cheaper and removes a pre-1.0 dependency from every service's logging path."softwax/correlation-ids-bundle": "0.1.*" or an exact tag. Release 0.1 has no changelog body, only a commit-range link, so there is no documented BC promise. Treat any bump as potentially breaking.X-Correlation-ID / X-Request-ID).curl -i your health route and a route that logs, and confirm (a) the response header is present and stable, (b) the log line contains the same value as structured context. If either half fails, nothing downstream matters.Minimal Laravel shape to aim for (the "port, don't bundle" path):
// app/Http/Middleware/CorrelationId.php
public function handle($request, Closure $next)
{
$incoming = $request->header('X-Correlation-ID');
$id = $incoming && $this->isTrusted($request) && $this->validFormat($incoming)
? $incoming
: (string) Str::uuid();
CorrelationContext::set($id);
Log::withContext(['correlation_id' => $id]);
try {
$response = $next($request);
} finally {
// runs for 4xx/5xx too
$response ??= response();
$response->headers->set('X-Correlation-ID', $id);
}
return $response;
}
Context, and set the response header in finally so error responses carry it.correlation_id and use the same key in web, queue, console, and scheduler. Log queries that join across services depend on the key name being boring and constant. Keep it in the record's context (structured field), not interpolated into the message string.Http::globalMiddleware (or Guzzle middleware) that copies the ID onto outgoing requests. This is what makes the ID actually worth having — without it you only have per-app IDs that don't join at service boundaries.handle() runs. Reset after the job. The worker is long-lived; a static holder that is not reset will attribute the next job's logs to the previous job.config/correlation.php rather than hard-coding — you will want to exclude /up and /metrics.$app->scoped(...)) rather than a singleton, and flush it in a terminating callback. Scoped bindings are the mechanism that makes per-request state safe in long-lived workers.Log::spy() or a test log handler to assert the field. Set the trust flag true for the test so the fixed header is honoured.symfony/http-kernel into a Laravel app for a header is a poor trade.X-Correlation-ID is attacker-controlled input that flows into logs and onward downstream. Validate format and length (e.g. strict UUID/ULID regex), or simply overwrite inbound values from untrusted sources. Unvalidated IDs enable log injection and unbounded log-field cardinality.Http::pool(), or deferred callbacks automatically. Every exit from the request lifecycle needs explicit propagation. Instrument for the missing ID case — a worker logging without a correlation ID is the failure mode you'll hit first.finally or via a response event.php artisan config:clear. Stale cached config is the usual reason a header name "silently doesn't change".0.1.*, read the commit range on every bump, and keep the integration surface thin so replacement stays cheap.How can I help you explore Laravel packages today?