首页 > 解决方案 > JavaScript 变量范围为异步场景中的父请求

问题描述

我们在我们的生产性 ExpressJS 应用程序中重写console.log以添加某些字段(例如时间戳、会话 ID、请求 ID)以及将日志发送到系统日志服务器。

我们通过向全局对象添加console.requestId和属性然后覆盖以输出这些值以及将日志发送到 syslog 来做到这一点。这工作正常:console.sessionIdconsoleconsole.log()

let originalConsoleLog = console.log;
console.log = function() {
   let args = Array.from(arguments);
   args.unshift(this.sessionId);
   args.unshift(this.requestId);
   originalConsoleLog.log.apply(console, args);
   syslog.log(arguments);
}
app.get('/some-endpoint', async (req, res) => {
   console.requestId = req.header('X-Request-Id');
   console.sessionId = req.session = req.cookies['SESSIONID'];
   let response = await someAsyncProcess();
   console.log('foo');
   req.json(response);
});

当同时处理 2 个请求时,问题就来了:

所以全局变量(如console)出来了。

由于我们不想将某些logger实例传递给每个模块,因此本地人也被淘汰了。

我们需要的是一个“伪全局”变量,它的作用域是 ExpressJS 中的单个请求。类似于闭包的东西,但也适用于外部模块:

let logger = new Logger(sessionId, requestId);
function() {
  // I can see logger
  logger.log('hello');
  // This module can't
  let foo = require('foo');
}

这个想法是我们可以在这个logger变量上设置一个请求和会话 id,并且从这里调用的所有函数/模块/方法都将console.log使用这个会话/请求 id。变量“属于”从请求响应者调用的所有代码。

我们不想将此logger实例注入我们使用的每个子模块,但我们想console.log动态捕获请求并注入相关的本地sessionIdrequestId.

标签: javascriptnode.js

解决方案


您可能需要为此使用async hooks,例如

const asyncHooks = require('async_hooks');
const store = new Map();

const asyncHook = asyncHooks.createHook({
    init: (asyncId, _, triggerAsyncId) => {
        if (store.has(triggerAsyncId)) {
            store.set(asyncId, store.get(triggerAsyncId))
        }
    },
    destroy: (asyncId) => {
        if (store.has(asyncId)) {
            store.delete(asyncId);
        }
    }
});

asyncHook.enable();

const createRequestContext = (sessionId, requestId) => {
    const context = { sessionId, requestId };
    store.set(asyncHooks.executionAsyncId(), context);
    return context;
};

const getRequestContext = () => {
    return store.get(asyncHooks.executionAsyncId());
};


let originalConsoleLog = console.log;

console.log = function() {
   let args = Array.from(arguments);
   const { sessionId, requestId } = getRequestContext();
   args.unshift(sessionId);
   args.unshift(requestId);
   originalConsoleLog.log.apply(console, args);
   syslog.log(arguments);
}

app.get('/some-endpoint', async (req, res) => {
   const requestId = req.header('X-Request-Id');
   const sessionId = req.session = req.cookies['SESSIONID'];
   createRequestContext(requestId, sesscionId);
   let response = await someAsyncProcess();
   console.log('foo');
   req.json(response);
});


推荐阅读