Adding Trace ID to Responses for Better Correlation

omrilotan
2 min readFeb 24, 2025

--

Track requests from client to server — improve correlation, debugging, and monitoring with trace IDs.

Image by freepik

In distributed systems, trace IDs are essential for tracking requests across different services. A widely adopted standard for passing trace IDs is the traceparent header, which follows the W3C Trace Context specification. This enables observability tools like OpenTelemetry to establish context and facilitate root cause analysis. The practice of tracking requests across multiple services using a unique trace identifier is known as Distributed Tracing.

The trace ID is usually propagated by request headers and starts in the backend. However, including the trace ID with the responses can greatly improve client-side correlation and debugging. This makes monitoring and troubleshooting easier, helping developers to immediately connect frontend failures to backend requests.

Injecting the Trace ID into Responses

A gateway (or middleware) ensures each request has a trace ID and returns it in the response headers for simpler debugging.

Gateway Function to Process Requests

/**
* Gateway function to process incoming requests to an origin server
* @warning simplified example
*/
async function facilitate(request: Request): Promise<Response> {
// Find trace ID from traceparent or create a new one
const traceId =
request.headers.get("traceparent")?.split("-").at(1) ??
crypto.randomUUID().replace(/-/g, "");
if (!request.headers.has("traceparent")) {
// Add new traceparent header upstream
request.headers.set("traceparent", `00-${traceId}-706f6f7062757474-01`);
}
// Forward the request upstream
const response = await fetch(request);
// Modify the response to include the trace ID
response.headers.set("Trace-Id", traceId);
response.headers.append("Server-Timing", `Trace-ID;desc="${traceId}"`);
return response;
}

Why Use the Server-Timing Header?

The Server-Timing header is the only response header accessible from JavaScript in the browser via the Performance API. This is how the frontend code will be able to obtain the trace ID of the page request.

Correlating Client Errors with Trace IDs

Once the trace ID is available in responses, we can enrich client-side error logging and include it in failure logs. This makes it possible for client-side and backend logs to be quickly correlated.

const response = await fetch(API_Endpoint);
// Log client errors with trace ID
if (response.status >= 400 && response.status < 500) {
logger.error({
message: "Request failed with client error",
url: API_Endpoint,
status: response.status,
traceId: response.headers.get("Trace-ID"),
response: (await response.text()).substring(0, 100)
});
}

Retrieving the Trace ID in the Browser

Using the Performance API, JavaScript provides access to the Server-Timing response header information. We can retrieve the trace ID from our Trace-ID entry.

const pageTraceId = performance
.getEntriesByType("navigation")
?.pop()
?.serverTiming?.find(({ name }) => name === "Trace-ID")
?.description;

Again, allowing frontend reports to include the trace ID for the page’s corresponding HTTP request.

Comprehensive Correlation

By adding trace IDs to responses, we unlock full end-to-end request tracking, making debugging faster and more precise. This small change leads to quicker issue resolution, better observability, and an improved developer experience.

--

--

omrilotan
omrilotan

No responses yet