ここでの根本的な問題はやや複雑なので、ここで私たちが求めているものを要約します-関数をフィールドとして提供することにより、動的に生成された値を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.2WinstonのStackdriverLogging 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を渡すユーザーに依存する必要があります。 これは、トランスポートがlog
コールバックの呼び出しを延期すると、Winston 3が( readable-stream
を介して)ログをバッチ処理するためです。
問題は、ユーザーがリクエストIDを渡してくれることに依存したくないということです。 自動的に発生させたいのです。 結局のところ、ユーザーはおそらくただ書きたいと思うでしょう
app.get('/', async (req, res) => {
logger.info('hello');
res.sendStatus(200);
});
そして私たちに休むために残します。
(リンクされた出力からの)期待される要求IDと実際の要求IDは一致します。
実際のリクエストIDが一致するようにコードを修正することは不可能であるため、この問題の解決策は、ユーザーがロガーが呼び出されたときに呼び出されるdefaultMetadata
のフィールドとして関数を指定できるようにすることです。 そうすれば、コードはこれに変更されます。これにより、ユーザーはリクエストIDを意識せずにコードを記述できますが、サンクをdefaultMetadata
フィールドとして提供するという小さな注意が必要です(モジュールが提供する可能性があります)。それらを使用する)。
したがって、この問題の解決策は、ユーザーがdefaultMetadataのフィールドとして関数を指定できるようにすることです。
これは、 reduxthunksと同じように実装できると思っていました。 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が言及したバグを修正しました