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

在FeathersJS中使用Winston实现带Correlation Id的日志记录

在FeathersJS中结合Winston实现自动携带Correlation ID的最佳方案

我来给你梳理一下实现思路和具体步骤,这个需求在分布式系统里非常实用,核心就是让请求级别的Correlation ID自动渗透到整个日志生命周期,不用每次手动传递。

核心思路

  1. 用Node.js的AsyncLocalStorage(或兼容库)存储请求上下文,保证异步调用链中能拿到当前请求的Correlation ID
  2. 编写全局中间件,在每个请求进来时生成/提取Correlation ID,并存入上下文
  3. 定制Winston日志格式,自动从上下文读取Correlation ID并格式化输出
  4. 在FeathersJS中集成日志器,直接在服务、hook里调用即可

具体实现步骤

1. 初始化请求上下文存储

我们用Node.js原生的AsyncLocalStorage来存储请求级别的Correlation ID,它能在异步操作(比如数据库查询、服务调用)中保持上下文,非常适合这个场景。

// src/utils/asyncStorage.js
const { AsyncLocalStorage } = require('async_hooks');

// 创建全局的AsyncLocalStorage实例
const asyncLocalStorage = new AsyncLocalStorage();

module.exports = asyncLocalStorage;

2. 编写Correlation ID中间件

这个中间件负责:

  • 从请求头X-Correlation-ID提取已有ID(方便上下游追踪)
  • 如果没有则生成新的UUID作为Correlation ID
  • 将ID存入AsyncLocalStorage,同时挂载到请求对象方便hook使用
  • 可选:将ID写入响应头,让调用方也能拿到
// src/middlewares/correlationId.js
const { v4: uuidv4 } = require('uuid');
const asyncLocalStorage = require('../utils/asyncStorage');

module.exports = () => (req, res, next) => {
  // 优先使用请求头传入的Correlation ID,没有则生成新的
  const correlationId = req.headers['x-correlation-id'] || uuidv4();
  
  // 把ID存入AsyncLocalStorage,开启上下文
  asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('correlationId', correlationId);
    // 挂载到req对象,方便Feathers hook直接使用
    req.correlationId = correlationId;
    // 写入响应头,方便调用方追踪
    res.setHeader('X-Correlation-ID', correlationId);
    next();
  });
};

然后在Feathers的主文件(比如app.js)中注册这个中间件,要放在其他中间件之前,确保所有请求都经过它处理:

// src/app.js
const correlationIdMiddleware = require('./middlewares/correlationId');

// 注册全局中间件
app.use(correlationIdMiddleware());

// 后续的Feathers配置...

3. 定制Winston日志器

配置Winston,让它在格式化日志时自动从AsyncLocalStorage读取Correlation ID,按照你需要的格式输出:

// src/utils/logger.js
const winston = require('winston');
const asyncLocalStorage = require('./asyncStorage');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    // 可选:添加时间戳,方便排查问题
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.printf(({ level, message, timestamp }) => {
      // 从上下文获取Correlation ID,没有则显示unknown
      const correlationId = asyncLocalStorage.getStore()?.get('correlationId') || 'unknown';
      // 按照要求的格式输出
      return `[${level.charAt(0).toUpperCase() + level.slice(1)}] [${correlationId}] : ${message}`;
    })
  ),
  transports: [
    // 输出到控制台
    new winston.transports.Console({
      format: winston.format.colorize({ all: true })
    }),
    // 可选:输出到文件,按日期分割
    new winston.transports.DailyRotateFile({
      filename: 'logs/%DATE%.log',
      datePattern: 'YYYY-MM-DD'
    })
  ]
});

module.exports = logger;

4. 在Feathers中使用日志器

现在你可以在任何服务、hook或者中间件里直接调用日志方法,不用手动传Correlation ID,它会自动从上下文读取:

// src/services/users/users.service.js
const logger = require('../../utils/logger');

class UsersService {
  async create(data) {
    logger.info('Creating new user with data: ' + JSON.stringify(data));
    // 业务逻辑...
    return data;
  }

  async get(id) {
    logger.debug(`Fetching user with ID: ${id}`);
    // 业务逻辑...
    return { id };
  }
}

注意事项

  • Node.js版本兼容:AsyncLocalStorage是Node.js 14.17.0+(或12.20.0+)的特性,如果你的版本更低,可以用cls-hooked库替代,用法类似。
  • 异步上下文保持:只要是在请求的异步调用链中(比如服务调用、数据库查询、Promise回调),AsyncLocalStorage都能正确获取到Correlation ID,不用担心异步丢失的问题。
  • 自定义格式:如果需要调整日志格式,比如加上时间戳、日志级别颜色,直接修改Winston的format配置即可。

内容的提问来源于stack exchange,提问作者Bervianto Leo Pratama

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:23:27