File size: 2,802 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
 * Correlation ID Middleware — FASE-04 Observability
 *
 * Generates and propagates correlation IDs (X-Request-Id) across
 * requests and responses for distributed tracing. Uses AsyncLocalStorage
 * to make the correlation ID available in any downstream code.
 *
 * @module middleware/correlationId
 */

import { AsyncLocalStorage } from "node:async_hooks";
import crypto from "crypto";

const correlationStore = new AsyncLocalStorage();

/**
 * Generate a unique correlation ID.
 * @returns {string} UUID-like correlation ID
 */
function generateCorrelationId() {
  return crypto.randomUUID();
}

/**
 * Get the current correlation ID from async context.
 * @returns {string|undefined}
 */
export function getCorrelationId() {
  return correlationStore.getStore();
}

/**
 * Run a function within a correlation context.
 * If a correlationId is provided, it is used; otherwise a new one is generated.
 *
 * @param {string|null} correlationId - Optional existing correlation ID
 * @param {Function} fn - Function to run in context
 * @returns {*} Result of fn()
 */
export function runWithCorrelation(correlationId, fn) {
  const id = correlationId || generateCorrelationId();
  return correlationStore.run(id, fn);
}

/**
 * Express/Next.js middleware that injects correlation IDs.
 *
 * Usage:
 *   // In Next.js middleware or Express app
 *   import { correlationMiddleware } from './correlationId.js';
 *   app.use(correlationMiddleware);
 *
 * @param {Request} request
 * @param {Function} next
 * @returns {Promise<Response>}
 */
export function correlationMiddleware(request, next) {
  const requestId =
    request.headers.get("x-request-id") ||
    request.headers.get("x-correlation-id") ||
    generateCorrelationId();

  return runWithCorrelation(requestId, async () => {
    const response = await next();

    // Attach correlation ID to response
    if (response && response.headers) {
      response.headers.set("x-request-id", requestId);
    }

    return response;
  });
}

/**
 * Create a logger wrapper that automatically includes correlation IDs.
 *
 * @param {Object} baseLogger - Base logger with info/warn/error methods
 * @returns {Object} Wrapped logger
 */
export function createCorrelatedLogger(baseLogger) {
  const withCorrelation = (level, ...args) => {
    const correlationId = getCorrelationId();
    if (correlationId) {
      const meta = typeof args[args.length - 1] === "object" ? args.pop() : {};
      meta.correlationId = correlationId;
      args.push(meta);
    }
    baseLogger[level](...args);
  };

  return {
    info: (...args) => withCorrelation("info", ...args),
    warn: (...args) => withCorrelation("warn", ...args),
    error: (...args) => withCorrelation("error", ...args),
    debug: (...args) => withCorrelation("debug", ...args),
  };
}