重写console.log
nodejs + express 要求重写console.log,需要输出每次请求的header里的X-Request-Id值,如果没有则使用一个uuid代替
为了在每次调用 console.log
时输出当前请求的 req.id
,我们需要确保我们可以访问到与当前执行上下文相关联的请求对象。这可以通过使用 Node.js 的 async_hooks
模块中的 AsyncLocalStorage
类来实现。
以下是一个修正后的代码片段,展示了如何安全地重写 console.log
函数以包含请求 ID:
const express = require('express'); const { v4: uuidv4 } = require('uuid'); const { AsyncLocalStorage } = require('async_hooks'); const app = express(); const asyncLocalStorage = new AsyncLocalStorage(); // 重写 console.log 函数 const originalConsoleLog = console.log; console.log = (...args) => { const store = asyncLocalStorage.getStore(); const requestId = store ? store.get('requestId') : 'no-request-id'; originalConsoleLog.apply(console, [`[RequestId: ${requestId}]`, ...args]); }; // 创建一个中间件来获取或生成 X-Request-Id 并存储在 AsyncLocalStorage app.use((req, res, next) => { const requestId = req.headers['x-request-id'] || uuidv4(); asyncLocalStorage.run(new Map(), () => { asyncLocalStorage.getStore().set('requestId', requestId); next(); }); }); // 示例路由 app.get('/', (req, res) => { console.log('这是一个普通的日志信息。'); // 使用被替换的 console.log 函数 res.send('Hello World!'); }); // 启动服务器 const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
在此代码中,我们首先创建了一个 AsyncLocalStorage
实例。然后我们重写了 console.log
函数,使其在打印日志消息之前,先从 AsyncLocalStorage
获取当前的存储区域(如果存在),并尝试获取 requestId
。如果找不到 requestId
(例如,在请求外部的代码中),则使用 'no-request-id'
作为默认值。
然后,我们定义了一个中间件,它会在每个请求开始时运行,并将 X-Request-Id
存储在 AsyncLocalStorage
中。这样,无论在请求处理期间还是在异步操作中,重写的 console.log
函数都能够输出正确的 X-Request-Id
。
请注意,这种修改全局 console.log
函数的做法可能会影响到整个应用程序的日志记录行为,因此在生产环境中使用时需要特别小心。此外,如果你的应用程序已经在使用其他日志库或者有特定的日志记录要求,那么可能需要考虑更适合你应用场景的解决方案。
如果出现并发是否还会正常输出,另外,在第二个路由文件中应该如何使用
使用 AsyncLocalStorage
可以确保即使在并发请求的情况下,每个请求的上下文都能够正确地与其对应的 requestId
关联。AsyncLocalStorage
在 Node.js 中被设计为跟踪异步操作的上下文,因此它适用于并发请求场景。
如果你有多个路由文件,并希望在所有路由中使用重写后的 console.log
函数,你需要确保:
console.log
的重写发生在所有路由加载之前。每个请求都经过一个中间件来初始化
AsyncLocalStorage
。
以下是一个示例,说明了如何在不同的路由文件中使用这种方法:
首先,在你的主应用文件中(比如 app.js
),设置全局的 console.log
重写和 AsyncLocalStorage
:
// app.js const express = require('express'); const { v4: uuidv4 } = require('uuid'); const { AsyncLocalStorage } = require('async_hooks'); const otherRoutes = require('./otherRoutes'); // 引入其他路由模块 const app = express(); const asyncLocalStorage = new AsyncLocalStorage(); // 重写 console.log 函数 const originalConsoleLog = console.log; console.log = (...args) => { const store = asyncLocalStorage.getStore(); const requestId = store ? store.get('requestId') : 'no-request-id'; originalConsoleLog.apply(console, [`[RequestId: ${requestId}]`, ...args]); }; // 创建一个中间件来获取或生成 X-Request-Id 并存储在 AsyncLocalStorage app.use((req, res, next) => { const requestId = req.headers['x-request-id'] || uuidv4(); asyncLocalStorage.run(new Map(), () => { asyncLocalStorage.getStore().set('requestId', requestId); next(); }); }); // 使用其他路由 app.use(otherRoutes); // 启动服务器const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); 然后,在你的其他路由文件中(比如 otherRoutes.js),你可以直接使用 console.log,它已经被重写以包含当前请求的 requestId: // otherRoutes.js const express = require('express'); const router = express.Router(); router.get('/other-route', (req, res) => { console.log('这是另一个路由的日志信息。'); res.send('This is another route.'); }); module.exports = router;
在这种配置中,无论请求是在主文件中的路由处理还是在其他路由文件中处理,console.log 都将输出包含 requestId 的日志信息。这是因为 console.log 已经在应用程序的最初阶段被重写,并且 AsyncLocalStorage 中间件被应用于所有传入的请求,确保每个请求都有一个唯一的 requestId 存储在 asyncLocalStorage 中。
由于 AsyncLocalStorage 跟踪的是异步上下文,所以每个请求都会有自己的独立上下文,即使它们是并发到达的。这意味着每个请求的日志都会正确地打印出与之关联的 requestId,而不会相互干扰。