#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