From 817d36447d2d7688d1b5dde28dedc302b3caeacf Mon Sep 17 00:00:00 2001 From: Bokky73 Date: Tue, 30 Jun 2026 14:09:32 +0000 Subject: [PATCH] feat: add correlation ID utility for distributed tracing - Generate unique correlation IDs for request tracking - Extract or generate from X-Correlation-ID header - Validate ID format for consistency - Create context object for logging --- src/utils/__tests__/correlationId.test.ts | 66 +++++++++++++++++++++++ src/utils/correlationId.ts | 50 +++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/utils/__tests__/correlationId.test.ts create mode 100644 src/utils/correlationId.ts diff --git a/src/utils/__tests__/correlationId.test.ts b/src/utils/__tests__/correlationId.test.ts new file mode 100644 index 00000000..2ad6abde --- /dev/null +++ b/src/utils/__tests__/correlationId.test.ts @@ -0,0 +1,66 @@ +import { + generateCorrelationId, + getOrGenerateCorrelationId, + isValidCorrelationId, + createContext, +} from "../correlationId"; + +describe("correlationId utility", () => { + describe("generateCorrelationId", () => { + it("should generate a valid correlation ID", () => { + const id = generateCorrelationId(); + expect(id).toMatch(/^corr-/); + expect(id.length).toBeGreaterThan(15); + }); + + it("should generate unique IDs", () => { + const ids = new Set(); + for (let i = 0; i < 100; i++) { + ids.add(generateCorrelationId()); + } + expect(ids.size).toBe(100); + }); + }); + + describe("isValidCorrelationId", () => { + it("should validate correct format", () => { + expect(isValidCorrelationId("corr-abc123-def456-ghi789")).toBe(true); + expect(isValidCorrelationId("corr-1a2b3c4d-5e6f-7g8h")).toBe(true); + }); + + it("should reject invalid formats", () => { + expect(isValidCorrelationId("invalid")).toBe(false); + expect(isValidCorrelationId("")).toBe(false); + expect(isValidCorrelationId("corr-")).toBe(false); + }); + }); + + describe("getOrGenerateCorrelationId", () => { + it("should return existing valid correlation ID from headers", () => { + const headers = { "x-correlation-id": "corr-test123-abc456-def789" }; + const result = getOrGenerateCorrelationId(headers); + expect(result).toBe("corr-test123-abc456-def789"); + }); + + it("should generate new ID when header is missing", () => { + const headers = {}; + const result = getOrGenerateCorrelationId(headers); + expect(isValidCorrelationId(result)).toBe(true); + }); + + it("should generate new ID when header is invalid", () => { + const headers = { "x-correlation-id": "invalid-id" }; + const result = getOrGenerateCorrelationId(headers); + expect(isValidCorrelationId(result)).toBe(true); + }); + }); + + describe("createContext", () => { + it("should create context with correlation ID and timestamp", () => { + const headers = {}; + const context = createContext(headers); + expect(context.correlationId).toBeDefined(); + expect(context.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/); + }); + }); +}); \ No newline at end of file diff --git a/src/utils/correlationId.ts b/src/utils/correlationId.ts new file mode 100644 index 00000000..8df99ae6 --- /dev/null +++ b/src/utils/correlationId.ts @@ -0,0 +1,50 @@ +/** + * Correlation ID utility for distributed tracing and request tracking + * Generates unique identifiers to track requests across services + */ + +/** + * Generates a correlation ID for distributed request tracing + * Format: 8-4-4-4-12 (UUID-like but optimized for tracing) + */ +export function generateCorrelationId(): string { + const timestamp = Date.now().toString(36); + const randomPart = Math.random().toString(36).substring(2, 10); + const processId = process.pid.toString(36).padStart(4, "0"); + return `corr-${timestamp}-${randomPart}-${processId}`; +} + +/** + * Extracts correlation ID from request headers or generates a new one + */ +export function getOrGenerateCorrelationId( + headers: Record +): string { + const existing = headers["x-correlation-id"]; + if (existing && isValidCorrelationId(existing)) { + return existing; + } + return generateCorrelationId(); +} + +/** + * Validates a correlation ID format + */ +export function isValidCorrelationId(id: string): boolean { + return /^corr-[a-z0-9]{4,}-[a-z0-9]{4,}-[a-z0-9]{4,}$/.test(id); +} + +/** + * Extracts correlation ID for logging context + */ +export interface CorrelationContext { + correlationId: string; + timestamp: string; +} + +export function createContext(headers: Record): CorrelationContext { + return { + correlationId: getOrGenerateCorrelationId(headers), + timestamp: new Date().toISOString(), + }; +} \ No newline at end of file