这里的潜在问题有些复杂,所以我将在这里总结一下我们的要求——我们希望看到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作为 Winston 的 Stackdriver Logging Transport 的作者,我们希望支持日志请求关联。 async_hooks
模块使我们能够存储特定于请求的信息,例如请求 ID。 但是,我们没有办法在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);
请注意“预期”和“实际”请求 ID 中的不匹配。 如果我们是MyTransport
的作者,我们将无法通过查询当前执行 ID(“实际”)来准确获取正确的请求 ID。 相反,我们必须依靠用户传递正确的请求 ID 作为元数据(“预期”)。 这是因为 Winston 3 在 Transport 延迟调用其log
回调时(通过readable-stream
)批处理日志。
问题是我们不想依赖用户为我们传递请求 ID; 我们希望它自动发生。 毕竟,用户可能只想写
app.get('/', async (req, res) => {
logger.info('hello');
res.sendStatus(200);
});
让我们休息。
预期的和实际的请求 ID(来自链接的输出)匹配。
修复代码以使实际请求 ID 匹配是不可行的,因此解决此问题的方法是允许用户将函数指定为defaultMetadata
上的字段,这些字段在调用记录器时被调用。 这样,代码将更改为this ,这允许用户在不知道请求 ID 的情况下编写他们的代码,但需要注意的是,他们提供了一个 thunk 作为defaultMetadata
字段(也许我们的模块会提供他们使用)。
所以解决这个问题的方法是允许用户将函数指定为 defaultMetadata 上的字段
我在想这可以实现类似于 redux thunks 。 而不是在defaultMetadata
中具有函数值属性,而是认为defaultMetadata
本身可以是一个可选的函数。 像 thunk 一样,在这种情况下, winston
将使用通过调用defaultMetadata
返回的值。 换句话说:
const logger = createLogger({
defaultMeta: () => {
// Function to be evaluated every time a log is written.
return {
timeWritten: Date.now();
// ..
}
}
// ...
});
我找到了一种解决方法,可以在实施建议的解决方案之前使用 - 使用 getter:
const logger = createLogger({
defaultMeta: {
get timeWritten () { return `Date.now()`; }
},
// ...
});
UPD:修复了@mooski 提到的错误
〜很棒的解决方案@Alexsey - 对于使用此代码的任何人,请注意timeWritten
之后的冒号不应该在那里。〜
更新:由@Alexsey 修复
最有用的评论
我找到了一种解决方法,可以在实施建议的解决方案之前使用 - 使用 getter:
UPD:修复了@mooski 提到的错误