在FeathersJS中使用Winston实现带Correlation Id的日志记录
在FeathersJS中结合Winston实现自动携带Correlation ID的最佳方案
我来给你梳理一下实现思路和具体步骤,这个需求在分布式系统里非常实用,核心就是让请求级别的Correlation ID自动渗透到整个日志生命周期,不用每次手动传递。
核心思路
- 用Node.js的
AsyncLocalStorage(或兼容库)存储请求上下文,保证异步调用链中能拿到当前请求的Correlation ID - 编写全局中间件,在每个请求进来时生成/提取Correlation ID,并存入上下文
- 定制Winston日志格式,自动从上下文读取Correlation ID并格式化输出
- 在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
相关产品推荐
相关产品推荐

