Переменная JavaScript, ограниченная родительским запросом в сценарии Asynch

#javascript #node.js

Вопрос:

Мы переопределяем console.log в нашем производительном приложении ExpressJS добавление определенных полей (например, метка времени, идентификатор сеанса, идентификатор запроса), а также отправку журналов на сервер системного журнала.

Мы делаем это, добавляя console.requestId console.sessionId свойства и в глобальный console объект, а затем переопределяя console.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 запроса обрабатываются одновременно:

  • Запрос 1 устанавливает идентификатор запроса/идентификатор сеанса REQ1/SESSA
  • Запрос 1 ожидает someAsyncProcess()
  • Запрос 2 устанавливает идентификатор запроса/идентификатор сеанса REQ2/SESSB
  • Запрос 1 регистрируется REQ2/SESSB foo , когда он должен регистрироваться REQ1/SESSA foo

Так что глобалы (вроде 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 переменной, и все функции/модули/методы, вызываемые отсюда, будут console.log иметь этот идентификатор сеанса/запроса. Переменная «принадлежит» всему коду, вызванному из ответчика запроса.

Мы не хотим вводить этот logger экземпляр в каждый используемый нами подмодуль, но мы хотим console.log динамически обрабатывать запросы и вводить соответствующие локальные sessionId и requestId .

Комментарии:

1. есть ли какая-либо конкретная причина, по которой вы устанавливаете RequestID и SessionID, которые решает обещание берофе? потому что, если вы установите его после, он может работать так, как задумано. UPD: Да, не думал, что у тебя там может быть куча Обещаний)

2. Мы устанавливаем его, как только поступает запрос — если мы этого не сделаем, код someAsyncProcess() не будет регистрироваться с правильным идентификатором сеанса. Но я согласен с вами, установка его впоследствии (снова) поможет в некоторых случаях. Проблема someAsyncProcess() в том, что может снова вызвать множество async функций, и идентификатор сеанса будет постоянно перезаписываться другими «потоками»запросов.

Ответ №1:

Вам, вероятно, понадобится использовать 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);
}); 

Комментарии:

1. Выглядит впечатляюще, но теперь мой модуль маршрута и модуль журнала должны совместно использовать экземпляр магазина? Поэтому я возвращаюсь к передаче экземпляров в свой класс ведения журнала…

2. Я исправляюсь. Я могу создать глобальный экземпляр этого контекста global.context , и каждый модуль имеет доступ к запросу через global.context?.getRequestContext() . Это ПОТРЯСАЮЩЕ и именно то, что мне было нужно!

3. Хорошая статья здесь решает именно эту проблему: blog.besson.co/nodejs_async_hooks_to_get_per_request_context