Skip to content

Correlate the browser and the service

HTTP Guardian and MIC can carry the same identifier from a user action to the service logs. This does not recreate distributed tracing: the identifier makes operational searches easier, while traceparent remains the standard for linking browser, HTTP, and SQL spans.

  • X-Request-Id covers one logical request, including retries. Use it for log searches and support responses.
  • X-Correlation-Id covers a business journey that may span several requests. Use it for explicit functional grouping.
  • traceparent covers one distributed trace. Use it to connect OpenTelemetry spans through instrumented dependencies.

Use X-Request-Id when the identifier must immediately appear in every MIC structured log. correlationIdGuard() keeps the value stable across retries, and LogContextMiddleware validates it, binds it to the log context, and returns it in the response.

Browser side

Install the current public HTTP Guardian prerelease:

npm install @cedvict/http-guardian@next

Configure the guard with the header name expected by the MIC log context:

import {
  correlationIdGuard,
  createApiParserStructured,
  createHttpClient,
  noAuth,
} from "@cedvict/http-guardian";

export const api = createHttpClient({
  baseUrl: "https://api.example.com",
  auth: noAuth(),
  parser: createApiParserStructured(),
  guards: [
    correlationIdGuard({ headerName: "X-Request-Id" }),
  ],
});

An identifier is generated once for each logical request. If a retry plugin replays the call, every attempt therefore keeps the same identifier. The header returned by the service is also copied to ctx.meta.correlationId for HTTP Guardian logging, Sentry, or OpenTelemetry plugins.

MIC with FastAPI

from fastapi import FastAPI
from mic.observability import LogContextMiddleware

app = FastAPI()
app.add_middleware(LogContextMiddleware)

Every structured log emitted during the request receives request_id. The response carries the same X-Request-Id. An incoming value that does not match the [A-Za-z0-9_-]{1,128} allow-list is replaced instead of being reflected in logs and headers.

MIC with Litestar

from litestar import Litestar
from litestar.middleware import DefineMiddleware
from mic.observability import LogContextMiddleware

app = Litestar(
    route_handlers=[...],
    middleware=[DefineMiddleware(LogContextMiddleware)],
)

The middleware is ASGI-based, so FastAPI and Litestar share the same contract and log fields.

Business journeys with X-Correlation-Id

Keep the default browser-side header name when the same identifier must span several requests:

guards: [correlationIdGuard()]

Then enable the MIC correlation middleware:

from fastapi import FastAPI
from mic.fastapi import CorrelationMiddleware

app = FastAPI()
app.add_middleware(CorrelationMiddleware)
from litestar import Litestar
from litestar.middleware import DefineMiddleware
from mic.litestar import CorrelationMiddleware

app = Litestar(
    route_handlers=[...],
    middleware=[DefineMiddleware(CorrelationMiddleware)],
)

MIC then exposes the identifier as request.state.correlation_id with FastAPI, or as scope["state"]["correlation_id"] with Litestar. It also returns X-Correlation-Id, X-Request-Id, and X-Response-Time-ms.

Prefer the X-Request-Id contract from the previous section when logs must be correlated automatically. A business-journey identifier may be logged as an additional field, but it does not replace the request identifier.

Frontend and API on different origins

The API CORS policy must:

  • allow X-Request-Id or X-Correlation-Id in request headers;
  • expose the same header through Access-Control-Expose-Headers so HTTP Guardian can read the value returned by the service.

Without response-header exposure, propagation to the service still works, but the browser cannot read the value normalized by the API.

Operational check

curl -i https://api.example.com/health \
  -H 'X-Request-Id: checkout-smoke-42'

The check is complete when:

  1. the response contains X-Request-Id: checkout-smoke-42;
  2. the MIC logs for this request contain "request_id":"checkout-smoke-42";
  3. any retries carry the same value;
  4. distributed spans continue to use traceparent independently.