如何在Express-TypeScript项目中为Winston集成Correlation ID
实现带Correlation ID的Express-TypeScript日志系统(Winston + Morgan)
核心需求落地
要实现控制台日志自动携带correlation-id、MongoDB日志新增correlationId字段,且控制器、服务层调用日志时无需手动传递该ID,可按以下步骤改造现有代码:
1. 扩展Express Request类型(TypeScript类型支持)
创建src/types/express.d.ts文件,让req.correlationId获得类型提示:
declare namespace Express { interface Request { correlationId: string; } }
2. 实现异步请求上下文存储
创建src/utils/requestContext.ts,用Node.js内置的AsyncLocalStorage在异步流程中共享correlationId:
import { AsyncLocalStorage } from 'node:async_hooks'; interface RequestContext { correlationId: string; } export const requestContext = new AsyncLocalStorage<RequestContext>();
3. 修改Correlation中间件,绑定上下文
更新src/middleware/correlationMiddleware.ts,将correlationId存入异步上下文:
import { Request, Response, NextFunction } from "express"; import { v4 as uuidv4 } from "uuid"; import { requestContext } from '../utils/requestContext'; 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); // 将correlationId绑定到当前异步上下文 requestContext.run({ correlationId: req.correlationId }, next); }; export default correlationIdMiddleware;
4. 改造Winston Logger,自动注入Correlation ID
更新src/utils/logger.ts,调整日志格式从上下文自动获取correlationId:
import winston from "winston"; import "winston-mongodb"; import sanitizedConfig from "../config"; import { requestContext } from './requestContext'; 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); // 自定义格式:注入correlationId到日志信息 const injectCorrelationId = winston.format((info) => { const context = requestContext.getStore(); info.correlationId = context?.correlationId || "unknown"; return info; }); // 基础日志格式 const baseFormat = winston.format.combine( winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }), injectCorrelationId() ); // 控制台日志格式 const consoleFormat = winston.format.combine( baseFormat, winston.format.colorize({ all: true }), winston.format.printf( (info) => `${info.timestamp} [${info.correlationId}] ${info.level}: ${info.message}` ) ); // MongoDB日志格式 const mongoFormat = winston.format.combine( baseFormat, winston.format.json() ); const mongoTransport = new winston.transports.MongoDB({ db: sanitizedConfig.DB_URL, collection: "logs", level: "debug", format: mongoFormat, }); const consoleTransport = new winston.transports.Console({ format: consoleFormat, }); const Logger = winston.createLogger({ level: level(), levels, transports: [consoleTransport, mongoTransport], }); export default Logger;
5. 控制器与服务层的日志使用示例
控制器代码
import { Request, Response } from 'express'; import Logger from '../utils/logger'; import { sendMail } from '../services/mailService'; export const updateUser = async (req: Request, res: Response) => { const { userId } = req.params; // 自动携带correlationId Logger.info(`User ${userId} updated successfully`); // 调用服务层,日志同样自动携带ID await sendMail(userId, 'Update Confirmation'); res.status(200).json({ message: 'User updated', correlationId: req.correlationId }); };
服务层代码(mailService.ts)
import Logger from '../utils/logger'; export const sendMail = async (userId: string, subject: string) => { // 无需手动传递correlationId,自动从上下文获取 Logger.debug(`Preparing mail for user ${userId} with subject: ${subject}`); // 模拟邮件发送逻辑 setTimeout(() => { Logger.info(`Mail sent to user ${userId}`); }, 100); };
6. 可选:Morgan日志集成Correlation ID
如果需要Morgan的HTTP日志也带correlationId,可自定义格式:
import morgan from 'morgan'; import Logger from '../utils/logger'; // 自定义Morgan token获取correlationId morgan.token('correlation-id', (req) => req.correlationId); const morganMiddleware = morgan( ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" [correlation-id: :correlation-id]', { stream: { write: (message) => Logger.http(message.trim()), }, } ); export default morganMiddleware;
关键细节说明
- AsyncLocalStorage:确保在整个请求的异步流程(包括控制器、服务层、异步任务)中都能自动获取correlationId,无需手动传递参数。
- 统一格式注入:通过
injectCorrelationId自定义格式,同时给控制台和MongoDB日志添加correlationId,避免重复逻辑。 - 降级处理:非请求触发的后台任务无法获取上下文时,日志会显示
unknown作为correlationId,保证系统稳定。
内容的提问来源于stack exchange,提问作者VIPIN
相关产品推荐
相关产品推荐

