You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:04:53