A questão subjacente aqui é um pouco complexa, então vou resumir o que estamos pedindo aqui -- estaríamos interessados em ver a capacidade de defaultMetadata
incluir valores gerados dinamicamente fornecendo uma função como um campo. Por exemplo:
const logger = createLogger({
defaultMeta: {
// Function to be evaluated every time a log is written.
timeWritten: () => { return `Date.now()`; }
},
// ...
});
Para uma justificativa completa veja abaixo.
winston
versão?_winston@2
winston@3
node -v
saídas:_ v10.15.2Como autores do Stackdriver Logging Transport for Winston, gostaríamos de oferecer suporte à correlação de solicitação de log. O módulo async_hooks
nos dá a capacidade de armazenar informações específicas de solicitação, como um ID de solicitação. No entanto, não temos meios de recuperá-lo de forma confiável no lado Transport
.
O problema é melhor descrito por exemplo. O código a seguir produz essa saída quando ~100 solicitações são feitas em rápida sucessão:
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);
Observe a incompatibilidade nos IDs de solicitação "esperado" e "real". Se formos o autor de MyTransport
, não teríamos como obter com precisão o ID de solicitação correto consultando o ID de execução atual ("real"). Em vez disso, devemos confiar no usuário passando o ID de solicitação correto como metadados ("esperado"). Isso ocorre porque o Winston 3 agrupa logs (via readable-stream
) quando um Transporte adia sua invocação de seu retorno de chamada log
.
O problema é que não queremos depender de usuários passando um ID de solicitação para nós; queremos que isso aconteça automaticamente. Afinal, os usuários provavelmente iriam querer apenas escrever
app.get('/', async (req, res) => {
logger.info('hello');
res.sendStatus(200);
});
e deixe descansar por nossa conta.
Os IDs de solicitação esperados e reais (da saída vinculada) correspondem.
É inviável corrigir o código para que os IDs de solicitação reais correspondam, portanto, uma solução para esse problema seria permitir que os usuários especificassem funções como campos em defaultMetadata
que são invocados quando o registrador é chamado. Dessa forma, o código mudaria para this , o que permite que os usuários escrevam seu código sem qualquer conhecimento do ID da solicitação, com a pequena ressalva de que eles fornecem um thunk como um campo defaultMetadata
(que talvez nosso módulo forneça para que eles usem).
então uma solução para este problema seria permitir que os usuários especificassem funções como campos em defaultMetadata
Eu estava pensando que isso poderia ser implementado semelhante ao redux thunks . Em vez de ter uma propriedade com valor de função em defaultMetadata
, estava pensando que defaultMetadata
si poderia ser opcionalmente uma função. Como um thunk, winston
usaria o valor retornado invocando defaultMetadata
nesse caso. Em outras palavras:
const logger = createLogger({
defaultMeta: () => {
// Function to be evaluated every time a log is written.
return {
timeWritten: Date.now();
// ..
}
}
// ...
});
Encontrei uma solução alternativa que pode ser usada até que a solução proposta seja implementada - use getters:
const logger = createLogger({
defaultMeta: {
get timeWritten () { return `Date.now()`; }
},
// ...
});
UPD: corrigido o bug mencionado por @mooski
~ Ótima solução @Alexsey - para quem usa este código, esteja ciente de que os dois pontos depois de timeWritten
não devem estar lá.~
Atualização: corrigida por @Alexsey
Comentários muito úteis
Encontrei uma solução alternativa que pode ser usada até que a solução proposta seja implementada - use getters:
UPD: corrigido o bug mencionado por @mooski