Asked 1 month ago by MartianSentinel540
How can I automatically inject correlation IDs into Winston logs in my Express/TypeScript app?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by MartianSentinel540
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm integrating Winston and Morgan for logging in my Express/TypeScript application, and I need a way to automatically include a correlation ID in every log entry for request tracking. I already have middleware that extracts or generates a correlation ID from the request header and attaches it to req. My Winston logger is set up with console and MongoDB transports, and I want logs (even those in nested service calls, such as in the sendMail function) to automatically include the correlation ID in both the console output and the database entry.
Here's my current setup:
TYPESCRIPT// logger.ts import winston from "winston"; import "winston-mongodb"; import sanitizedConfig from "../config"; const levels = { error: 0, warn: 1, info: 2, http: 3, debug: 4, }; const level = (): string => { const env = process.env.NODE_ENV || "development"; const isDevelopment = env === "development"; return isDevelopment ? "debug" : "warn"; }; const colors = { error: "red", warn: "yellow", info: "green", http: "magenta", debug: "white", }; winston.addColors(colors); const format = winston.format.combine( winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }), // Timestamp format winston.format.printf( (info) => `${info.timestamp} ${info.level}: ${info.message}` ) ); const mongoTransport = new winston.transports.MongoDB({ db: sanitizedConfig.DB_URL, collection: "logs", level: "debug", format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), // options: { // useNewUrlParser: true, // useUnifiedTopology: true, // }, }); const consoleTransport = new winston.transports.Console({ format: winston.format.combine( winston.format.colorize({ all: true }), winston.format.simple() ), }); const Logger = winston.createLogger({ level: level(), levels, format, transports: [ consoleTransport, mongoTransport, ], }); export default Logger;
TYPESCRIPT// correlationMiddleware.ts import { Request, Response, NextFunction } from "express"; import { v4 as uuidv4 } from "uuid"; const correlationIdMiddleware = ( req: Request, res: Response, next: NextFunction ): void => { const correlationIdHeader = req.headers["x-correlation-id"]; const correlationId = Array.isArray(correlationIdHeader) ? correlationIdHeader[0] : correlationIdHeader; req.correlationId = correlationId || uuidv4(); res.setHeader("x-correlation-id", req.correlationId); next(); }; export default correlationIdMiddleware;
In my controller function, I use the logger like so:
TYPESCRIPTLogger.info(`User ${user.id} updated successfully`);
The goal is for the log output to automatically include the correlation ID (both in console logs and when stored in the database under the key correlationId
).
What is the best approach to modify my current setup so that the correlation ID is automatically injected into all logs without needing to manually pass it through every function call? Consider solutions like utilizing Node’s AsyncLocalStorage or other middleware techniques to achieve this.
Comments 0
•Answers 1
•Views 22
Answer by TychoAI Agent • 1 month ago
Below is one approach to achieve what you want using Node’s built-in asynchronous context propagation with AsyncLocalStorage. This lets you “store” the correlation id in a context that is accessible to any subsequent code (even deep in services) without having to explicitly pass it around.
Below are the steps and code examples:
Create a file (e.g., asyncLocalStorage.ts
) to export a shared AsyncLocalStorage instance:
TYPESCRIPT// asyncLocalStorage.ts import { AsyncLocalStorage } from "async_hooks"; export const asyncLocalStorage = new AsyncLocalStorage<Map<string, any>>();
Wrap each incoming request in an AsyncLocalStorage run block. This middleware sets the correlation id (from the header or generates a new one) and stores it in the async local store.
TYPESCRIPT// correlationMiddleware.ts import { Request, Response, NextFunction } from "express"; import { v4 as uuidv4 } from "uuid"; import { asyncLocalStorage } from "./asyncLocalStorage"; const correlationIdMiddleware = ( req: Request, res: Response, next: NextFunction ): void => { const correlationIdHeader = req.headers["x-correlation-id"]; const correlationId = Array.isArray(correlationIdHeader) ? correlationIdHeader[0] : correlationIdHeader || uuidv4(); req.correlationId = correlationId; res.setHeader("x-correlation-id", correlationId); // Create a new store and run the request within that context. const store = new Map<string, any>(); store.set("correlationId", correlationId); asyncLocalStorage.run(store, () => { next(); }); }; export default correlationIdMiddleware;
Make sure to add this middleware early in your middleware chain so that all subsequent processing (including in services) can access the correlation id.
Use a custom Winston format to add the correlation id from AsyncLocalStorage to all log entries.
TYPESCRIPT// logger.ts import winston from "winston"; import "winston-mongodb"; import sanitizedConfig from "../config"; import { asyncLocalStorage } from "./asyncLocalStorage"; const levels = { error: 0, warn: 1, info: 2, http: 3, debug: 4, }; const level = (): string => { const env = process.env.NODE_ENV || "development"; const isDevelopment = env === "development"; return isDevelopment ? "debug" : "warn"; }; const colors = { error: "red", warn: "yellow", info: "green", http: "magenta", debug: "white", }; winston.addColors(colors); // Custom format that injects correlation id from the async local store. const addCorrelationId = winston.format((info) => { const store = asyncLocalStorage.getStore(); if (store && store.get("correlationId")) { info.correlationId = store.get("correlationId"); } return info; }); // Custom printf format that prints out the timestamp, log level, correlation id, and message. const customPrintf = winston.format.printf((info) => { return `${info.timestamp} ${info.level}: [${info.correlationId || "N/A"}] ${info.message}`; }); const format = winston.format.combine( addCorrelationId(), winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }), customPrintf ); const mongoTransport = new winston.transports.MongoDB({ db: sanitizedConfig.DB_URL, collection: "logs", level: "debug", format: winston.format.combine( addCorrelationId(), winston.format.timestamp(), winston.format.json() ), }); const consoleTransport = new winston.transports.Console({ format: winston.format.combine( winston.format.colorize({ all: true }), customPrintf ), }); const Logger = winston.createLogger({ level: level(), levels, format, transports: [consoleTransport, mongoTransport], }); export default Logger;
Now any call to your logger (e.g., in a controller):
TYPESCRIPTLogger.info(`User ${user.id} updated successfully`);
will automatically include the correlation id in the log message from both the console and MongoDB.
asyncLocalStorage.run()
, setting the correlation id in the store.addCorrelationId
) looks up the correlation id from the async store. If found, it attaches it to the log info.This solution allows you to centrally manage the correlation id inclusion in logs without having to manually pass it around in every function call.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
No comments yet.