# Correlation Id Tracking

> Manages correlation-id in Node.js/TypeScript applications using AsyncLocalStorage for async context isolation. Use when implementing correlation-id tracking, HTTP request/response correlation, logging integration, or when working with distributed tracing in Node.js applications.

- Skill: `whitebeardit/correlation-id-tracking-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add whitebeardit/correlation-id-tracking-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/whitebeardit/correlation-id-tracking-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: whitebeardit (https://skillmd.com/u/whitebeardit)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/whitebeardit/correlation-id-tracking-2

---


# Correlation-ID Tracking for Node.js

This skill helps you implement correlation-id tracking in Node.js/TypeScript applications using AsyncLocalStorage for async context preservation.

## Installation

```bash
npm install async-local-storage
# or
yarn add async-local-storage
```

## Quick Setup

### Express.js - Zero Configuration

**Minimal setup:**

```typescript
import express from 'express';
import { correlationIdMiddleware } from './middleware/correlation-id';

const app = express();

// Add correlation-ID middleware (must be first)
app.use(correlationIdMiddleware);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('/api/test', (req, res) => {
  const correlationId = getCorrelationId();
  res.json({ correlationId });
});

app.listen(3000);
```

### Correlation-ID Middleware

```typescript
import { Request, Response, NextFunction } from 'express';
import { AsyncLocalStorage } from 'async_hooks';

const correlationStorage = new AsyncLocalStorage<string>();

export function getCorrelationId(): string {
  return correlationStorage.getStore() || generateCorrelationId();
}

export function setCorrelationId(id: string): void {
  correlationStorage.enterWith(id);
}

function generateCorrelationId(): string {
  return crypto.randomUUID().replace(/-/g, '');
}

export function correlationIdMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
): void {
  const correlationId = 
    req.headers['x-correlation-id'] as string || generateCorrelationId();
  
  correlationStorage.run(correlationId, () => {
    res.setHeader('X-Correlation-Id', correlationId);
    next();
  });
}
```

## Using Correlation-ID in Code

### In Controllers

```typescript
import { Request, Response } from 'express';
import { getCorrelationId } from '../middleware/correlation-id';

export async function getOrder(req: Request, res: Response) {
  const correlationId = getCorrelationId();
  
  try {
    const order = await orderService.getById(req.params.id);
    res.json({ order, correlationId });
  } catch (error) {
    res.status(500).json({ error: 'Failed to get order', correlationId });
  }
}
```

### In Services

```typescript
import { getCorrelationId } from '../middleware/correlation-id';
import logger from '../logger';

export class OrderService {
  async getById(id: string) {
    const correlationId = getCorrelationId();
    
    logger.info('Getting order', { 
      orderId: id, 
      correlationId 
    });
    
    // Your business logic here
    return order;
  }
}
```

## HTTP Client Integration

### Axios Interceptor

```typescript
import axios from 'axios';
import { getCorrelationId } from './middleware/correlation-id';

// Add request interceptor
axios.interceptors.request.use((config) => {
  const correlationId = getCorrelationId();
  if (correlationId) {
    config.headers['X-Correlation-Id'] = correlationId;
  }
  return config;
});
```

### Fetch Wrapper

```typescript
import { getCorrelationId } from './middleware/correlation-id';

export async function fetchWithCorrelationId(
  url: string,
  options?: RequestInit
): Promise<Response> {
  const correlationId = getCorrelationId();
  
  const headers = {
    ...options?.headers,
    'X-Correlation-Id': correlationId || '',
  };
  
  return fetch(url, { ...options, headers });
}
```

## Logging Integration

### Winston Logger

```typescript
import winston from 'winston';
import { getCorrelationId } from './middleware/correlation-id';

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: {
    get correlationId() {
      return getCorrelationId();
    },
  },
  transports: [
    new winston.transports.Console({
      format: winston.format.simple(),
    }),
  ],
});

// Usage
logger.info('Processing order', { orderId: '123' });
// Automatically includes correlationId in metadata
```

### Pino Logger

```typescript
import pino from 'pino';
import { getCorrelationId } from './middleware/correlation-id';

const logger = pino({
  mixin() {
    return {
      correlationId: getCorrelationId(),
    };
  },
});

// Usage
logger.info({ orderId: '123' }, 'Processing order');
// Automatically includes correlationId
```

## Logging Best Practices

### Log Levels: When to Use Each

**DEBUG** - Development and troubleshooting only:
- Detailed execution flow
- Variable values and intermediate states
- Step-by-step process information
- **Not visible in production** (typically filtered out)

**INFO** - Production-ready, important events:
- Request start/completion
- Business operations (create, update, delete)
- External API calls (start/end)
- Important state changes
- **Visible in production**

**WARNING** - Potential issues that don't break functionality:
- Retry attempts
- Fallback to default values
- Deprecated API usage
- Performance degradation

**ERROR** - Failures that need attention:
- Exceptions and errors
- Failed operations
- External service failures

### What to Log (and What NOT to Log)

**✅ DO Log:**
- Request identifiers (correlation-ID, user ID, request ID)
- Business operations (what happened)
- External service calls (start, end, duration)
- Important state changes
- Errors with context (correlation-ID, user, operation)

**❌ DON'T Log:**
- Sensitive data (passwords, tokens, PII, credit cards)
- Large payloads (use summaries instead)
- Every iteration in loops (log once per operation)
- Redundant information (correlation-ID is already in context)
- Excessive detail in production (use DEBUG for that)

### Structured Logging with Correlation-ID

Always include correlation-ID in logs for traceability:

```typescript
// ✅ Good: Structured logging with correlation-ID
logger.info('Processing order', {
  orderId: orderId,
  userId: userId,
  correlationId: getCorrelationId(),
});

// ✅ Good: Using structured properties
logger.info('External API call completed', {
  endpoint: endpoint,
  duration: duration,
  statusCode: statusCode,
  correlationId: getCorrelationId(),
});

// ❌ Bad: Logging without correlation-ID context
logger.info(`Processing order ${orderId}`); // Missing correlation-ID!

// ❌ Bad: Logging sensitive data
logger.info(`User password: ${password}`); // NEVER log passwords!
```

### Logging Patterns for Debugging

**Pattern 1: Request Lifecycle**
```typescript
export async function getOrder(req: Request, res: Response) {
  const correlationId = getCorrelationId();
  
  // ✅ INFO: Request start (production-visible)
  logger.info('Getting order', { 
    orderId: req.params.id, 
    correlationId 
  });
  
  try {
    // ✅ DEBUG: Detailed steps (development only)
    logger.debug('Querying database for order', { 
      orderId: req.params.id, 
      correlationId 
    });
    
    const order = await orderRepository.findById(req.params.id);
    
    if (!order) {
      // ✅ WARNING: Expected but notable condition
      logger.warn('Order not found', { 
        orderId: req.params.id, 
        correlationId 
      });
      return res.status(404).json({ error: 'Order not found' });
    }
    
    // ✅ INFO: Successful completion (production-visible)
    logger.info('Order retrieved successfully', { 
      orderId: req.params.id, 
      correlationId 
    });
    
    res.json(order);
  } catch (error) {
    // ✅ ERROR: Always log exceptions with correlation-ID
    logger.error(error, 'Error retrieving order', { 
      orderId: req.params.id, 
      correlationId 
    });
    res.status(500).json({ error: 'Internal server error' });
  }
}
```

**Pattern 2: External Service Calls**
```typescript
export async function processPayment(request: PaymentRequest) {
  const correlationId = getCorrelationId();
  
  // ✅ INFO: External call start (production-visible)
  logger.info('Calling payment service', {
    amount: request.amount,
    currency: request.currency,
    correlationId,
  });
  
  const startTime = Date.now();
  
  try {
    const response = await paymentClient.process(request);
    const duration = Date.now() - startTime;
    
    // ✅ INFO: Success with duration (production-visible)
    logger.info('Payment service call completed', {
      status: response.status,
      duration,
      correlationId,
    });
    
    return response;
  } catch (error) {
    const duration = Date.now() - startTime;
    
    // ✅ ERROR: Failure with context (production-visible)
    logger.error(error, 'Payment service call failed', {
      duration,
      correlationId,
    });
    throw error;
  }
}
```

**Pattern 3: Conditional Debug Logging**
```typescript
export async function processItems(items: Item[]) {
  const correlationId = getCorrelationId();
  
  // ✅ INFO: Operation start (production-visible)
  logger.info('Processing items', {
    count: items.length,
    correlationId,
  });
  
  for (let i = 0; i < items.length; i++) {
    // ✅ DEBUG: Detailed per-item info (development only)
    logger.debug('Processing item', {
      index: i + 1,
      total: items.length,
      itemId: items[i].id,
      correlationId,
    });
    
    await processItem(items[i]);
  }
  
  // ✅ INFO: Operation completion (production-visible)
  logger.info('Processed items successfully', {
    count: items.length,
    correlationId,
  });
}
```

## Key Features

1. **AsyncLocalStorage**: Uses Node.js `async_hooks` AsyncLocalStorage for context preservation
2. **Automatic Propagation**: HTTP clients automatically include correlation-ID in headers
3. **Logging Integration**: Works with Winston, Pino, and other loggers
4. **Framework Support**: Works with Express, Fastify, NestJS, and other frameworks
5. **Header-Based**: Uses `X-Correlation-Id` header for HTTP propagation

## Important Notes

- **UUID Format**: Correlation-IDs are typically 32-character UUIDs without hyphens
- **Never Overwrites**: If correlation-ID exists (from headers), it's preserved
- **Independent from OpenTelemetry**: Correlation-ID is separate from trace context - both can coexist
- **Thread-Safe**: AsyncLocalStorage is safe for concurrent async operations

## Common Patterns

### Pattern 1: Reading Correlation-ID from Request

The middleware automatically:
1. Reads `X-Correlation-Id` from incoming request headers
2. If present, uses that value
3. If missing, generates new UUID (32 chars, no hyphens)
4. Sets AsyncLocalStorage context
5. Adds to response headers

### Pattern 2: Preserving Across Async Operations

```typescript
const correlationId = getCorrelationId(); // "abc123"

await someAsyncMethod();

const sameId = getCorrelationId(); // Still "abc123" - preserved!
```

### Pattern 3: Manual Propagation

```typescript
// Set correlation-ID from external source
setCorrelationId(externalCorrelationId);

// Now all operations use this correlation-ID
await processRequest();
```

## Key Principles

1. **Correlation-ID Always**: Every log should include correlation-ID
2. **Structured Properties**: Use structured logging with named properties, not string interpolation
3. **Context Matters**: Include relevant context (user ID, operation, IDs) but not sensitive data
4. **Level Appropriately**: Use DEBUG for detailed troubleshooting, INFO for production visibility
5. **Performance Aware**: Don't log in tight loops; summarize instead
6. **Error Context**: Always include correlation-ID and relevant context in error logs

