Le problème sous-jacent ici est quelque peu complexe, je vais donc résumer ce que nous demandons ici - nous serions intéressés de voir la possibilité pour defaultMetadata
d'inclure des valeurs générées dynamiquement en fournissant une fonction en tant que champ. Par example:
const logger = createLogger({
defaultMeta: {
// Function to be evaluated every time a log is written.
timeWritten: () => { return `Date.now()`; }
},
// ...
});
Pour une justification complète, voir ci-dessous.
winston
?_winston@2
winston@3
node -v
sorties :_ v10.15.2En tant qu'auteurs de Stackdriver Logging Transport pour Winston, nous aimerions prendre en charge la corrélation des requêtes de journal. Le module async_hooks
nous donne la possibilité de stocker des informations spécifiques à la demande, telles qu'un ID de demande. Cependant, nous n'avons aucun moyen de le récupérer de manière fiable du côté Transport
.
Le problème est mieux décrit par un exemple. Le code suivant produit cette sortie lorsque ~100 requêtes sont effectuées en succession rapide :
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);
Notez l'incompatibilité entre les ID de requête "attendus" et "réels". Si nous sommes l'auteur de MyTransport
, nous n'aurions aucun moyen d'obtenir avec précision l'ID de requête correct en consultant l'ID d'exécution actuel ("réel"). Au lieu de cela, nous devons compter sur l'utilisateur qui transmet l'ID de demande correct en tant que métadonnées ("attendu"). En effet, Winston 3 regroupe les journaux (via readable-stream
) lorsqu'un transport diffère son invocation de son rappel log
.
Le problème est que nous ne voulons pas compter sur les utilisateurs qui nous transmettent un ID de demande ; nous voulons que cela se produise automatiquement. Après tout, les utilisateurs voudraient probablement simplement écrire
app.get('/', async (req, res) => {
logger.info('hello');
res.sendStatus(200);
});
et laissez-nous reposer.
Les ID de demande attendus et réels (de la sortie liée) correspondent.
Il est impossible de corriger le code afin que les ID de demande réels correspondent, donc une solution à ce problème serait de permettre aux utilisateurs de spécifier des fonctions en tant que champs sur defaultMetadata
qui sont invoqués lorsque l'enregistreur est appelé. De cette façon, le code changerait en this , ce qui permet aux utilisateurs d'écrire leur code sans aucune connaissance de l'ID de la requête, avec la petite mise en garde qu'ils fournissent un thunk en tant que champ defaultMetadata
(que notre module fournirait peut-être pour à utiliser).
donc une solution à ce problème serait de permettre aux utilisateurs de spécifier des fonctions en tant que champs sur defaultMetadata
Je pensais que cela pourrait être implémenté de la même manière que les thunks redux . Au lieu d'avoir une propriété à valeur de fonction dans defaultMetadata
, je pensais que defaultMetadata
lui-même pourrait éventuellement être une fonction. Comme un thunk, winston
utiliserait la valeur renvoyée en appelant defaultMetadata
dans un tel cas. En d'autres termes:
const logger = createLogger({
defaultMeta: () => {
// Function to be evaluated every time a log is written.
return {
timeWritten: Date.now();
// ..
}
}
// ...
});
J'ai trouvé une solution de contournement qui peut être utilisée jusqu'à ce que la solution proposée soit implémentée - utilisez des getters :
const logger = createLogger({
defaultMeta: {
get timeWritten () { return `Date.now()`; }
},
// ...
});
UPD : correction du bug mentionné par @mooski
~ Excellente solution @Alexsey - pour quiconque utilise ce code, sachez simplement que les deux-points après timeWritten
ne devraient pas être là.~
Mise à jour : corrigée par @Alexsey
Commentaire le plus utile
J'ai trouvé une solution de contournement qui peut être utilisée jusqu'à ce que la solution proposée soit implémentée - utilisez des getters :
UPD : correction du bug mentionné par @mooski