Основная проблема здесь несколько сложна, поэтому я резюмирую то, о чем мы здесь просим: нам было бы интересно увидеть возможность для defaultMetadata
включать динамически генерируемые значения, предоставляя функцию как поле. Например:
const logger = createLogger({
defaultMeta: {
// Function to be evaluated every time a log is written.
timeWritten: () => { return `Date.now()`; }
},
// ...
});
Полное обоснование см. ниже.
winston
?_winston@2
winston@3
node -v
выходы:_ v10.15.2Как авторы Stackdriver Logging Transport для Winston, мы хотели бы поддерживать корреляцию журналов и запросов. Модуль async_hooks
дает нам возможность хранить специфичную для запроса информацию, такую как идентификатор запроса. Однако у нас нет надежного способа получить его на стороне Transport
.
Проблему лучше всего описать на примере. Следующий код выдает этот вывод , когда выполняется около 100 запросов в быстрой последовательности:
const ah = require('async_hooks');
const express = require('express');
const { createLogger } = require('winston');
const TransportStream = require('winston-transport');
// Activate async hooks.
let requestIdHighWater = 0;
const requestIdTable = {};
ah.createHook({
init: (child, resource, parent) => {
requestIdTable[child] = requestIdTable[parent];
}
}).enable();
// OUR CODE
class MyTransport extends TransportStream {
log(info, next) {
// From the transport, the request ID is not reliable.
const actualRequestId = requestIdTable[ah.executionAsyncId()];
console.log(`Expected: ${info.expectedRequestId}, Actual: ${actualRequestId}`);
setImmediate(next);
}
}
// OUR USER'S CODE
const logger = createLogger({
transports: [new MyTransport()]
});
const app = express();
app.get('/', async (req, res) => {
// Store a request ID.
const requestId = requestIdHighWater++;
requestIdTable[ah.executionAsyncId()] = requestId;
logger.info('hello', { expectedRequestId: requestId });
res.sendStatus(200);
});
app.listen(3000);
Обратите внимание на несоответствие «ожидаемых» и «фактических» идентификаторов запросов. Если бы мы были автором MyTransport
, у нас не было бы возможности точно получить правильный идентификатор запроса, обратившись к текущему идентификатору выполнения («фактический»). Вместо этого мы должны полагаться на то, что пользователь передает правильный идентификатор запроса в качестве метаданных («ожидаемый»). Это связано с тем, что Winston 3 регистрирует пакеты (через readable-stream
), когда Transport откладывает вызов своего обратного вызова log
.
Проблема в том, что мы не хотим полагаться на то, что пользователи передают нам идентификатор запроса; мы хотим, чтобы это происходило автоматически. В конце концов, пользователи, вероятно, захотят просто написать
app.get('/', async (req, res) => {
logger.info('hello');
res.sendStatus(200);
});
и оставьте отдыхать до нас.
Ожидаемый и фактический идентификаторы запроса (из связанного вывода) совпадают.
Невозможно исправить код так, чтобы фактические идентификаторы запросов совпадали, поэтому решением этой проблемы будет предоставление пользователям возможности указывать функции в виде полей на defaultMetadata
, которые вызываются при вызове регистратора. Таким образом, код изменится на this , что позволит пользователям писать свой код, не зная об идентификаторе запроса, с небольшой оговоркой, что они предоставляют преобразователь в виде поля defaultMetadata
(которое, возможно, наш модуль предоставил бы для их использовать).
поэтому решением этой проблемы было бы позволить пользователям указывать функции как поля в метаданных по умолчанию.
Я думал, что это можно реализовать аналогично редукционным преобразователям . Вместо того, чтобы иметь свойство с функциональным значением в defaultMetadata
, я думал, что defaultMetadata
сама по себе может быть опциональной функцией. Как преобразователь, winston
будет использовать значение, возвращенное вызовом defaultMetadata
в таком случае. Другими словами:
const logger = createLogger({
defaultMeta: () => {
// Function to be evaluated every time a log is written.
return {
timeWritten: Date.now();
// ..
}
}
// ...
});
Я нашел обходной путь, который можно использовать до тех пор, пока предложенное решение не будет реализовано - используйте геттеры:
const logger = createLogger({
defaultMeta: {
get timeWritten () { return `Date.now()`; }
},
// ...
});
UPD: исправлена ошибка, упомянутая @mooski
~ Отличное решение @Alexsey - для тех, кто использует этот код, просто имейте в виду, что двоеточия после timeWritten
там быть не должно.~
Обновление: исправлено @Alexsey
Самый полезный комментарий
Я нашел обходной путь, который можно использовать до тех пор, пока предложенное решение не будет реализовано - используйте геттеры:
UPD: исправлена ошибка, упомянутая @mooski