如何在处理并发请求的Express服务器中自动记录userId?
解决Express日志自动携带请求上下文(username)的方案
直接用Node.js内置的AsyncLocalStorage就能搞定,完美适配并发场景,不用在每个函数里传userId,也不用让日志工具直接拿req对象。
具体实现步骤
1. 初始化AsyncLocalStorage
先创建一个上下文管理模块,用来存储和获取请求级别的上下文:
// src/context.js const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); // 设置请求上下文 const setRequestContext = (context) => { asyncLocalStorage.enterWith(context); }; // 获取请求上下文 const getRequestContext = () => { return asyncLocalStorage.getStore(); }; module.exports = { setRequestContext, getRequestContext };
2. 编写Express中间件注入上下文
在请求进来的时候,从headers里取出username,把它存入AsyncLocalStorage的上下文:
// src/middlewares/contextMiddleware.js const { setRequestContext } = require('../context'); const contextMiddleware = (req, res, next) => { // 从请求头获取username,可根据实际字段名调整 const username = req.headers['username'] || 'anonymous'; // 还可以存入其他请求信息,比如请求ID、方法等 setRequestContext({ username }); next(); }; module.exports = contextMiddleware;
然后在Express应用里把这个中间件放在所有业务路由之前注册:
// src/app.js const express = require('express'); const contextMiddleware = require('./middlewares/contextMiddleware'); const logger = require('./logger'); const app = express(); // 先注册上下文中间件 app.use(contextMiddleware); // 示例路由 app.get('/api/data', async (req, res) => { logger.log('开始处理数据请求'); // 调用业务函数,无需传递username await handleDataProcessing(); res.send('success'); }); app.listen(3000);
3. 修改日志工具自动注入上下文
改造你的logger类,让它打日志时自动从上下文取username并加到payload里:
// src/logger.js const { getRequestContext } = require('./context'); class Logger { log(message, payload = {}) { // 获取当前请求上下文 const context = getRequestContext(); // 合并上下文信息到日志内容 const logData = { ...payload, username: context?.username || 'unknown' }; // 替换成你实际的日志输出逻辑,比如写入文件、发送到日志服务等 console.log(`[${new Date().toISOString()}] ${message}`, logData); } } // 导出单例实例 module.exports = new Logger();
4. 业务函数直接调用日志即可
不管是路由处理函数,还是深层的业务逻辑函数,直接调用logger.log就自动带上username了:
// src/services/dataService.js const logger = require('../logger'); async function handleDataProcessing() { const start = Date.now(); logger.log('数据处理函数开始执行'); // 模拟数据处理耗时 await new Promise(resolve => setTimeout(resolve, 100)); const duration = Date.now() - start; logger.log('数据处理函数执行完成', { duration }); } module.exports = handleDataProcessing;
方案优势
- AsyncLocalStorage是Node.js官方提供的异步上下文管理工具,专门解决并发场景下的请求上下文串流问题,稳定性拉满
- 完全解耦业务逻辑和日志上下文,不用修改任何业务函数的参数
- 上下文可扩展,后续需要加请求ID、请求路径等信息,直接在中间件里追加就行
不推荐的替代方案
之前有人用cls-hooked第三方库,但Node.js 14.17+内置AsyncLocalStorage后,官方就不再推荐第三方库了,内置工具更稳定省心。
内容的提问来源于stack exchange,提问作者Nero Bello
相关产品推荐
相关产品推荐

