重写console.log

yifeng1个月前大集合15

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 函数,你需要确保:

  1. console.log 的重写发生在所有路由加载之前。

  2. 每个请求都经过一个中间件来初始化 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,而不会相互干扰。


相关文章

优化一下这段代码,java8环境下

请优化一下这段代码,java8环境下: for (RegularEvaluateTemplate template : templates) ...

常用sql语句及案例(oracle)

常用sql语句及案例(oracle)

基本--新建表:create table table1( id varchar(300) primary key, name&nb...

Confluence 6.9 安装步骤

https://www.cnblogs.com/byronliu029/p/9272322.html...

NetCore全局调整路由拦截处理(系统下线配置)

Startup.csConfigure节代码如下---// 在MVC之前添加自定义重定向中间件            app.Use(asy...

未命名

整个夏天,直至秋天,都是生命独享风流的季节。长风沛雨,艳阳明月,那时田野被喜悦铺满,天地见充斥着天地的豪情;那时候视角是一条视线,无暇旁顾;樸田,叶外花厅,你是我印象中的一部分,而你的全部印象才是我。...