File size: 2,900 Bytes
6111b2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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),
  };
}