---
title: Set a Correlation ID at the Request Boundary
impact: MEDIUM-HIGH
impactDescription: enables end-to-end request tracing through services
tags: proxy, observability, correlation-id, logging
---

## Set a Correlation ID at the Request Boundary

`setRequestId(request)` attaches `crypto.randomUUID()` as the `x-correlation-id` header on every incoming request. Server actions, route handlers, and services read it via `getLogger()` so every log entry generated by the same request shares the same ID. Without this, debugging a user-reported error means scrolling through a fanned-out request graph in chronological order and guessing which lines belong together.

**Incorrect (no correlation, scattered logs across services):**

```ts
// proxy.ts — no correlation ID set.
export default async function proxy(request: NextRequest) {
  return await handlePipeline(request);
}

// Some service deep in a workflow:
console.log('Charging customer'); // Which request? Which user? Good luck.
```

**Correct (correlation ID at boundary, used by all downstream loggers):**

```ts
// apps/web/proxy.ts
function setRequestId(request: Request) {
  request.headers.set('x-correlation-id', crypto.randomUUID());
}

export default async function proxy(request: NextRequest) {
  const response = handleI18nRouting(request);
  const secureHeadersResponse = await createResponseWithSecureHeaders(response);

  setRequestId(request);  // Every downstream log inherits this.

  // ... rest of pipeline
}

// In a service:
const logger = await getLogger();  // Reads x-correlation-id from request.
logger.info({ name: 'billing.charge', customerId }, 'Charging customer');
// Output: { level: 'info', correlationId: 'a3f2...', name: 'billing.charge', ... }
```

**What the structured log looks like:**

```json
{
  "level": "info",
  "time": 1736974823000,
  "correlationId": "a3f2c9d1-7b3e-4f5a-9c12-abcdef012345",
  "name": "billing.charge",
  "customerId": "cus_abc",
  "userId": "user_xyz",
  "msg": "Charging customer"
}
```

Filter logs by `correlationId` and you get every line of work this request did — the action, the service, the Supabase queries, the external Stripe call, all stitched together.

**What this enables when something breaks:**

| Without correlation ID | With correlation ID |
|------------------------|---------------------|
| User reports "checkout failed at 14:02" | User reports correlation ID from error toast |
| Search logs by timestamp ± 1 minute | Search logs by correlationId |
| Guess which entries are this request | All entries are this request |
| Re-create from screenshots | Have the full server-side trace |

**Surface it to the client.** When a server error happens, include the correlation ID in the error response so the user (or support) can quote it back. The kit's monitoring service captures exceptions with the ID attached — the response just needs to expose it.

**Don't overwrite an inbound `x-correlation-id`.** If you ever put a load balancer or upstream service in front, it may already be setting one. Generate a new ID only if the header is missing.

Reference: [Pino structured logging](https://getpino.io/#/) (the logger backing `@kit/shared/logger`)
