Winston: Los métodos de registro ya no reciben una devolución de llamada, no pueden usarlo de manera confiable en algunos entornos (AWS Lambda)

Creado en 29 mar. 2018  ·  44Comentarios  ·  Fuente: winstonjs/winston

En Winston 3, las funciones de registro ya no reciben una devolución de llamada. Anteriormente, esto se podía usar para esperar hasta que se completara el registro:

logger.info('some message', { foo: 42 }, callback);  // Winston 2.x

Winston 3 le permite escuchar el evento logged en sus transportes, pero eso no me da una manera fácil de saber cuándo se ha completado _este mensaje que estoy registrando actualmente_. Dado que todo es asincrónico, es posible que el próximo evento logged que ocurra después de escribir su mensaje no esté relacionado con el mensaje que acaba de registrar. Y es más complejo si está escuchando el evento en varios transportes.

Eso hace que Winston sea difícil de usar en ciertos entornos. Por ejemplo, en AWS Lambda establecemos el parámetro callbackWaitsForEmptyEventLoop en false ( documentación ). (nota al margen: haga esto si tiene una conexión de base de datos o algo que no puede ser unref ed, porque de lo contrario Lambda nunca congelará su proceso). Si se establece en false , Lambda congela su proceso tan pronto como devuelve los resultados a la persona que llama e incluso puede terminar su proceso. Si su transporte de registro no ha terminado de escribir en el momento en que sucede, entonces perderá los registros o (peor) los registros se escribirán más tarde cuando Lambda descongele su proceso.

TL; DR: nuestro proceso Lambda normalmente esperaría (o el equivalente de devolución de llamada) en Winston antes de devolver los resultados a la persona que llama, lo que garantiza que el registro se realiza antes de que el proceso se congele.

¿Hay alguna otra forma que recomendaría para detectar cuándo se completó el registro?

help wanted important investigate

Comentario más útil

Sigo recibiendo alertas en este hilo, así que imagino que compartiré por qué creo que esto todavía no funciona para mucha gente. Antecedentes: nuestro caso de uso (y el contexto en el que se abrió este problema) es AWS Lambda, por lo que de lo que hablo aquí solo se aplica allí.

Lambda ejecuta Node en el contexto de ejecución de Lambda . La información pertinente es:

Después de que se ejecuta una función de Lambda, AWS Lambda mantiene el contexto de ejecución durante algún tiempo antes de la invocación de otra función de Lambda.

y

  • Cualquier declaración en el código de función de Lambda (fuera del código del controlador, consulte Modelo de programación ) permanece inicializada, lo que proporciona una optimización adicional cuando se vuelve a invocar la función.

es decir, para acelerar los lanzamientos, Lambda "congela" y "descongela" los entornos en lugar de apagarlos por completo. Para hacer esto, Lambda no espera a que Node salga, espera a que salga su función de controlador. Todos los procesos asincrónicos se ejecutan fuera de la función del controlador y, por lo tanto, pueden congelarse con el resto del contexto de ejecución de Lambda si no se esperan.

Ahora veamos la solución sugerida para esperar a Winston, adaptada de UPGRADE-3.0.md , asumiendo que estamos ejecutando Lambda:

async function lambdaHandler(event, context) {
  const logger = winston.createLogger({
    transports: [
      new winston.transports.Console(),
      new CustomAsyncTransport(),
    ],
  });
  logger.log('info', 'some message');
  logger.on('finish', () => process.exit());
  logger.end();
}

¿Detecta el problema? logger dispara logger.end() dentro del contexto de la función del controlador, pero la función activada por logger.on('finish') ejecuta fuera del contexto del controlador. Cualquier proceso asíncrono atado por CustomAsyncTransport impedirá que se active el evento finish , por lo que es probable que el contexto de ejecución se congele antes de que se active el evento.

Para resolver esto, lambdaHandler debe esperar a que el registrador salga antes de resolver:

async function lambdaHandler(event, context) {
  const logger = winston.createLogger({
    transports: [
      new winston.transports.Console(),
      new CustomAsyncTransport(),
    ],
  });
  const loggerFinished = new Promise(resolve => logger.on('finish', resolve));
  logger.log('info', 'some message');
  logger.end();
  await loggerFinished;
}

Dado que lambdaHandler no sale hasta que logger activa el evento finish , nuestro CustomAsyncTransport debería cerrarse antes que nuestro controlador lambda, evitando que esos procesos se congelen (suponiendo el evento finish está implementado correctamente por @indexzero).

Esto se puede generalizar a algo similar al código que he compartido anteriormente:

async function waitForLogger(logger) {
  const loggerDone = new Promise(resolve => logger.on('finish', resolve));
  // alternatively, use end-of-stream https://www.npmjs.com/package/end-of-stream
  // although I haven't tested this
  // const loggerDone = new Promise(resolve => eos(logger, resolve));
  logger.end();
  return loggerDone;
}

async function lambdaHandler(event, context) {
  const logger = winston.createLogger({
    transports: [
      new winston.transports.Console(),
      new CustomAsyncTransport(),
    ],
  });
  logger.log('info', 'some message');
  await waitForLogger(logger);
}

Espero que esto ayude a algunas personas.

Todos 44 comentarios

Estamos usando una solución diferente.

No lo convierte en un problema. Gracias por sacar el tema.

EDITAR: esto no funciona

============
No estoy seguro de si esta es la mejor manera, ni si funciona en todos los casos, pero hasta ahora lo estamos manejando haciendo lo siguiente:

He creado una función wait-for-logger.js como tal:

function waitForLogger(logger) {
  return new Promise((resolve) => {
    logger.on('close', resolve);
    logger.close();
  });
}

module.exports = waitForLogger;

Al final de nuestro controlador, esperamos la salida de la función antes de regresar:

const logger = require('./logger');
const waitForLogger = require('./wait-for-logger');

async function handler(event, context) {
  // your functionality
  logger.log('info', 'This should make it through all the transports');
  // ...

  await waitForLogger(logger);
}

Estamos utilizando el tiempo de ejecución de Lambda de nodejs8.10; si está utilizando un tiempo de ejecución anterior, probablemente pueda hacer algo como lo siguiente:

function handler(event, context, callback) {
  // your functionality
  logger.log('info', 'This should make it through all the transports');
  // ...

   waitForLogger(logger).then(() => callback(null));
}

Gracias @dpraul. Uno de nuestros registros transporta los registros a un servicio externo, y eso podría tardar unos ms. ¿Esperará logger.close() a que se complete?

@natesilva pensó en esto un poco más

  1. En winston@3 tanto Logger como Transport son transmisiones de Node.js.
  2. Las secuencias grabables exponen un método .end y un evento 'finish' .

Por lo tanto, en teoría, esto debería funcionar, pero aún no lo he probado:

const winston = require('winston');
const logger = winston.createLogger({
  transports: [
    new winston.transports.Console()
  ]
});

process.on('exit', function () {
  console.log('Your process is exiting');
});

logger.on('finish', function () {
  console.log('Your logger is done logging');
});

logger.log('info', 'Hello, this is a raw logging event',   { 'foo': 'bar' });
logger.log('info', 'Hello, this is a raw logging event 2', { 'foo': 'bar' });

logger.end();

Verificado:

{"foo":"bar","level":"info","message":"Hello, this is a raw logging event"}
{"foo":"bar","level":"info","message":"Hello, this is a raw logging event 2"}
Your logger is done logging
Your process is exiting

voy a agregar un ejemplo con esto como una forma de resolver el problema. No dude en volver a abrir si esto no le funciona en Lambda. Las plataformas de funciones como servicio están 100% en la base de usuarios objetivo de winston , así que tenga cuidado de que esto funcione correctamente.

Sería genial si pudiera contribuir con un ejemplo lambda de AWS de un extremo a otro usando winston si tiene el tiempo @natesilva.

Estoy revisando esto mientras hablamos, y creo que no he verificado que ninguno de estos métodos funcione. Intentaré compartir un caso de prueba completo aquí en breve.

const winston = require('winston');

class CustomTransport extends winston.Transport {
  log({ message }, cb) {
    setTimeout(() => {
      console.log(`custom logger says: ${message}`);
      cb(null);
    }, 3000);
  }
}

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new CustomTransport(),
  ],
});

logger.on('finish', () => {
  console.log('done!');
});
logger.log('info', 'here is some content');
logger.end();

Rendimiento esperado:

{"level":"info","message":"here is some content"}
custom logger says: here is some content
done!

Salida real:

{"level":"info","message":"here is some content"}
done!
custom logger says: here is some content

El evento finish se activa antes de que todos los transportes hayan finalizado. El mismo comportamiento ocurre cuando se usa .on('close', fn) y el evento .close() al final.

Lo siento, borré mi comentario. Mi comprensión de estos eventos de transmisión es limitada, pero puedo ver cómo finish podría dispararse en el momento equivocado, según el ejemplo de @ dpraul.

Pasé por el mismo ejemplo con los eventos end , finish y close , y utilicé tanto stream.Transform.close() como stream.Transform.end() - todos tienen los mismos resultados, donde los eventos se activan antes de que CustomerTransport haya llamado a su callback .

¿Podría haber algún problema en cómo se pasan los eventos entre Transports y Logger ?

Independientemente, @natesilva o @indexzero , es posible que desee volver a abrir este problema.

@dpraul eso parece plausible, pero no veo dónde sería:

  1. Transport en winston-transport implementa el método _write para cumplir con la interfaz Writeable Node.js. Esto recibe el callback y lo pasa al método log .
  2. Console transporte en winston llama a process.{stderr,stdout}.write y luego invoca el callback . La escritura de Afaik en process.stdout y process.stderr son operaciones sincrónicas por lo que _no debería_ haber ningún riesgo potencial de contrapresión.

Claramente, hay algo que se pierde en este flujo. Ir a charlar con algunas personas de Node.js y ver qué puedo desenterrar.

Todavía no sé qué está causando el problema, ¡pero encontré una solución que espera a que finalicen todos los transportes!

const winston = require('winston');

class CustomTransport extends winston.Transport {
  log({ message }, cb) {
    setTimeout(() => {
      console.log(`custom logger says: ${message}`);
      cb(null);
    }, 3000);
  }
}

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new CustomTransport(),
  ],
});

async function waitForLogger(l) {
  const transportsFinished = l.transports.map(t => new Promise(resolve => t.on('finish', resolve)));
  l.end();
  return Promise.all(transportsFinished);
}

logger.log('info', 'here is some content');

waitForLogger(logger).then(() => console.log('all done!'));

Producción:

{"level":"info","message":"here is some content"}
custom logger says: here is some content
all done!

Parece que logger.end() se propaga correctamente a los transportes, pero activa el evento finish antes de que todos los transportes hayan activado los suyos. La solución es esperar a que cada Transport active su propio evento finish y no depender en absoluto del Logger .

Gracias por continuar investigando @dpraul. El logger se envía a todos los transportes, cuando el registrador está cerrado, tenía entendido que no emitiría fin hasta que todos sus objetivos de tubería lo hicieran, pero puedo estar equivocado al respecto.

Voy a verificar con algunas personas.

Gracias a algunos aportes de @mcollina @davidmarkclements & @mafintosh , aprendí que mi comprensión de la semántica de pipe era defectuosa.

  • 'error' eventos 'finish' no se propagan hacia atrás a través de la cadena de tuberías.
  • 'error' eventos

Voy a explorar cómo propagar estos eventos hacia atrás o exponer un método explícito en Logger que implemente la técnica que está usando ahora @dpraul (tal vez usando el fin de flujo ).

@natesilva dejará este problema abierto hasta que la solución explícita esté disponible, pero mientras tanto, debería poder usar la técnica anterior:

  • Escuche los eventos de 'finish' en todos los transportes
  • Llamar logger.end()
  • Cuando se hayan emitido todos los eventos 'finish' , habrá terminado de registrar.

Módulos importantes que pueden ser útiles: bombeo y fin de flujo.

@mcollina teóricamente, desde la perspectiva de las transmisiones de Node.js, deberíamos poder implementar _final en el registrador para forzar que los eventos finish se emitan más tarde, ¿verdad?

@indexzero sí, eso sería correcto.

Dime, por favor, en winston @ 3 no puedo usar devoluciones de llamada como en la versión 2.
(no hay respuesta en la documentación)

// ... some check the connection to db ... and if fail:
logger.error('message',  function() {
  process.exit(1);
});

Y si no uso la devolución de llamada, mi aplicación se cierra más rápido de lo que se registró el mensaje = (

@TuxGit FYI esto ha sido documentado en UPGRADE-3.0 (ver: el código PR )

@natesilva @dpraul PR está abierto para abordar esto ahora: https://github.com/winstonjs/winston/pull/1346

Creo que esto todavía está roto. Aunque el evento finish ahora se está propagando correctamente y se está esperando, al menos en el transporte File , el WriteableStream subyacente aún no ha terminado de vaciar su escritura en el disco.

Creo que el PassthroughStream cambio está emitiendo su evento finish , por lo que no está esperando las escrituras.

No importa: el PassthruStream está bien, pero no funciona al escuchar el evento finish .

Puedo confirmar que el cableado de hasta end-of-stream y usarlo para escuchar el transport._stream sí funciona.

Algún avance en esto ? Estoy intentando logger.on('finish', () => process.exit(0)) pero no parece salir. Solo existe después de que expira el tiempo de espera

Tengo el mismo problema al usar el transporte de consola (no he probado el archivo para ser honesto). Parece que Logger se extiende stream.Transform de stream-readable módulo y _final método no está recibiendo llamadas en _stream_transform , sólo en _stream_writable . Realmente no quería detenerme en eso, ya que es la primera vez que uso cualquiera de estos módulos y me disculpo si entendí mal algo.

../lib/winston $ grep -n _final *.js
logger.js:249:  _final(callback) {

.../readable-stream/lib $ grep -n _final *.js
_stream_writable.js:130:  // if _final has been called
_stream_writable.js:276:    if (typeof options.final === 'function') this._final = options.final;
_stream_writable.js:601:  stream._final(function (err) {
_stream_writable.js:613:    if (typeof stream._final === 'function') {

Agregue un transporte personalizado y verifique el nivel de registro y agregue lógica personalizada en el nivel respectivo. Este no es el equivalente a hacer algo en una devolución de llamada. Sin embargo, si desea modificar y enviar algunos registros a algún otro destino como el freno de aire, esto ayudaría.

const Transport = require('winston-transport');

class YourCustomTransport extends Transport {
  log(info, callback) {
    setImmediate(() => {
      this.emit('logged', info);
    });
    if (info.level === 'error') {
      console.log('DO WHATEVER YOU WANT HERE.....', info.message);
    }
    callback();
  }
}

module.exports = YourCustomTransport;

Y en el archivo donde crea su registrador de Winston personalizado usando winston.createLogger (...), incluya su CustomTransport en la configuración, a la matriz de la opción "transportes".

transports: [
        new winston.transports.Console({ silent: isTestEnvironment }),
        new CustomTransport(),
      ],

Hola, he usado el código recomendado a continuación, pero no termina de escribir los archivos antes de cerrar el proceso. Veo los errores escritos en la consola. Si no llamo a process.exit, los archivos se escriben correctamente. Veo que este problema está cerrado, pero ¿se ha resuelto?

logger.log ('información', 'algún mensaje');
logger.on ('finalizar', () => process.exit ());
logger.end ();

¿Alguien tiene esto funcionando correctamente? ¿Algunas ideas?

@tsaockham
Tengo solución :)

function waitForLogger() {
    return new Promise(resolve => setTimeout(resolve, 2500));
}

Después de 2 h de buscar una solución, implementé esto, incluso la función waitForLogger de @dpraul no funcionó, ya

Sigo recibiendo alertas en este hilo, así que imagino que compartiré por qué creo que esto todavía no funciona para mucha gente. Antecedentes: nuestro caso de uso (y el contexto en el que se abrió este problema) es AWS Lambda, por lo que de lo que hablo aquí solo se aplica allí.

Lambda ejecuta Node en el contexto de ejecución de Lambda . La información pertinente es:

Después de que se ejecuta una función de Lambda, AWS Lambda mantiene el contexto de ejecución durante algún tiempo antes de la invocación de otra función de Lambda.

y

  • Cualquier declaración en el código de función de Lambda (fuera del código del controlador, consulte Modelo de programación ) permanece inicializada, lo que proporciona una optimización adicional cuando se vuelve a invocar la función.

es decir, para acelerar los lanzamientos, Lambda "congela" y "descongela" los entornos en lugar de apagarlos por completo. Para hacer esto, Lambda no espera a que Node salga, espera a que salga su función de controlador. Todos los procesos asincrónicos se ejecutan fuera de la función del controlador y, por lo tanto, pueden congelarse con el resto del contexto de ejecución de Lambda si no se esperan.

Ahora veamos la solución sugerida para esperar a Winston, adaptada de UPGRADE-3.0.md , asumiendo que estamos ejecutando Lambda:

async function lambdaHandler(event, context) {
  const logger = winston.createLogger({
    transports: [
      new winston.transports.Console(),
      new CustomAsyncTransport(),
    ],
  });
  logger.log('info', 'some message');
  logger.on('finish', () => process.exit());
  logger.end();
}

¿Detecta el problema? logger dispara logger.end() dentro del contexto de la función del controlador, pero la función activada por logger.on('finish') ejecuta fuera del contexto del controlador. Cualquier proceso asíncrono atado por CustomAsyncTransport impedirá que se active el evento finish , por lo que es probable que el contexto de ejecución se congele antes de que se active el evento.

Para resolver esto, lambdaHandler debe esperar a que el registrador salga antes de resolver:

async function lambdaHandler(event, context) {
  const logger = winston.createLogger({
    transports: [
      new winston.transports.Console(),
      new CustomAsyncTransport(),
    ],
  });
  const loggerFinished = new Promise(resolve => logger.on('finish', resolve));
  logger.log('info', 'some message');
  logger.end();
  await loggerFinished;
}

Dado que lambdaHandler no sale hasta que logger activa el evento finish , nuestro CustomAsyncTransport debería cerrarse antes que nuestro controlador lambda, evitando que esos procesos se congelen (suponiendo el evento finish está implementado correctamente por @indexzero).

Esto se puede generalizar a algo similar al código que he compartido anteriormente:

async function waitForLogger(logger) {
  const loggerDone = new Promise(resolve => logger.on('finish', resolve));
  // alternatively, use end-of-stream https://www.npmjs.com/package/end-of-stream
  // although I haven't tested this
  // const loggerDone = new Promise(resolve => eos(logger, resolve));
  logger.end();
  return loggerDone;
}

async function lambdaHandler(event, context) {
  const logger = winston.createLogger({
    transports: [
      new winston.transports.Console(),
      new CustomAsyncTransport(),
    ],
  });
  logger.log('info', 'some message');
  await waitForLogger(logger);
}

Espero que esto ayude a algunas personas.

¿Supongo que esto no funciona para el registrador predeterminado?

winston.on() no es una función.

La solución

Lo adapté un poco:

  async close() {
    return new Promise((resolve, reject) => {
      logger.on('close', () => {
        return resolve()
      })
      logger.on('error', err => {
        return reject(err)
      })
      logger.close()
    })
  }

Solo para tener en cuenta, es una buena práctica configurar su controlador de errores al mismo tiempo que crea su logger . Los errores se pueden disparar en cualquier momento; si solo los escucha cuando cierra el registrador, es probable que los pierda.

Quiero usar winston3 en el registro de Nodejs8 en mariaDB con el módulo sqlTransport. Para ello, adapté sqlTransport para Winston3 de acuerdo con docs / transports.md.
Winston3 hizo que el programa se bloqueara al final.
Experimenté con varias propuestas de este número y llegué a la siguiente conclusión:
Debe haber un método close () implementado en el módulo sqlTransport que cierre el módulo (es decir, el cliente dentro) correctamente.
Nota: ¡no implemente un método end ()! - Realmente no sé por qué.
Ahora, logger.end () es suficiente para cerrar el programa correctamente.
Logger.close (), que provoca un aborto inmediato del registrador y una pérdida de datos.

Si se requiere un controlador de errores al final del programa, puede usar

function waitForLogger (myLogger) {
const loggerDone = new Promise ((resolver, rechazar) => {
myLogger.on ('finalizar', () => {return resolve ()})
myLogger.on ('error', err => {return rechazar (err)})
})
myLogger.end ();
return loggerDone;
}
y
waitForLogger (logger) .then (() => {console.log ('¡todo listo!')}). catch ((err) => {console.error (err)});

Todavía no puedo hacer que esto funcione. Estoy usando winston 3.1.0 y este transporte flojo winston-slack-webhook-transport que realiza una solicitud http. Comenzó a recibir este error:

Error: write after end
at writeAfterEnd (/var/task/node_modules/winston/node_modules/readable-stream/lib/_stream_writable.js:257:12)
at DerivedLogger.Writable.write (/var/task/node_modules/winston/node_modules/readable-stream/lib/_stream_writable.js:301:21)
at DerivedLogger.(anonymous function) [as error] (/var/task/node_modules/winston/lib/winston/create-logger.js:81:14)
at errorHandler (/var/task/error/error-handler.js:27:36)
at Layer.handle_error (/var/task/node_modules/express/lib/router/layer.js:71:5)
at trim_prefix (/var/task/node_modules/express/lib/router/index.js:315:13)
at /var/task/node_modules/express/lib/router/index.js:284:7
at Function.process_params (/var/task/node_modules/express/lib/router/index.js:335:12)
at Immediate.next (/var/task/node_modules/express/lib/router/index.js:275:10)
at Immediate._onImmediate (/var/task/node_modules/express/lib/router/index.js:635:15)

EDITAR:
Me di cuenta ahora de que debo crear el registrador dentro del controlador, si no lo hago, la lambda puede reutilizar el contexto e intentar usar una instancia de registrador "finalizada".

Recibo el mismo error que @iudelsmann , ¿es posible verificar si el registrador ya está cerrado?

Con AWS API Gateway, podemos registrar 1-2 mensajes, pero después de logger.end (); todavía está intentando usar el mismo registrador y estamos obteniendo
UnhandledPromiseRejectionWarning: Error: write after end at writeAfterEnd (/var/task/node_modules/winston/node_modules/readable-stream/lib/_stream_writable.js:257:12) at DerivedLogger.Writable.write (/var/task/node_modules/winston/node_modules/readable-stream/lib/_stream_writable.js:301:21) at DerivedLogger.log (/var/task/node_modules/winston/lib/winston/logger.js:223:12)

Estamos usando el registro de Papertrail con winston-syslog.

Aquí está el código.

`const winston = require ('winston')
require ('winston-syslog')
const hostname = require ("os"). hostname ()

opciones const = {
host: PAPERTRAIL_URL,
puerto: PAPERTRAIL_PORT,
nombre_aplicación: PAPERTRAIL_LOG_CHANNEL,
localhost: nombre de host
}

const ptTransport = new winston.transports.Syslog (opciones)
const logger = winston.createLogger ({
transportes: [ptTransport],
formato: winston.format.combine (
winston.format.colorize ({
todo cierto
}),
winston.format.simple ()
)
}) `

@hdpa probablemente se

// logger.js
let logger;

const recreateLogger = () => {
  logger = winston.createLogger({
  ....
};

const getLogger = () => {
  if (logger && !logger.writable) {
    recreateLogger();
  }
  return logger;
}

const closeLogger = async () => {
  const loggerClosed = new Promise(resolve => logger.on('finish', resolve));
  // https://github.com/winstonjs/winston/issues/1250
  logger.end();
  return loggerClosed;
}

luego, en nuestra función, simplemente llamamos getLogger() para obtener el registrador que lo vuelve a abrir automáticamente si está cerrado y al final de la función usamos closeLogger() para asegurarnos de que descargue todos los registros en Cloudwatch

Implementó los cambios anteriores pero sigue teniendo el mismo problema. Imprimí el objeto del registrador en la consola. A continuación se muestra la respuesta para la primera y la segunda solicitud,

DerivedLogger { _readableState: ReadableState { objectMode: true, highWaterMark: 16, buffer: BufferList { head: null, tail: null, length: 0 }, length: 0, pipes: Syslog { _writableState: [Object], writable: true, domain: null, _events: [Object], _eventsCount: 6 

DerivedLogger { _readableState: ReadableState { objectMode: true, highWaterMark: 16, buffer: BufferList { head: null, tail: null, length: 0 }, length: 0, pipes: null, pipesCount: 0, flowing: false, ended: true, endEmitted: false, reading: false, sync: false, needReadable: 

Error: escriba después del final en writeAfterEnd (/var/task/node_modules/winston/node_modules/readable-stream/lib/_stream_writable.js:257:12) en DerivedLogger.Writable.write (/ var / task / node_modules / winston / node_modules /readable-stream/lib/_stream_writable.js:301:21) en DerivedLogger. (función anónima) [como error] (/ var / task / node_mod

Parece que el transporte de Syslog se está eliminando después del método de finalización y ahí es donde obtenemos un error.

Olvida mi publicación, parece que en la primera solicitud funciona, en la segunda algo solo se agota el tiempo de espera 😕 sería bueno esperar a que se descargue en lugar de cerrarlo

Oh, el problema con lo anterior es que trato de cerrar un registrador ya cerrado, así que la promesa nunca se resuelve, esto lo soluciona

const closeLogger = async () => {
  if (logger && !logger.writable) {
    // If it's already closed don't try to close it again
    return Promise.resolve();
  }
  const loggerClosed = new Promise(resolve => logger.on('finish', resolve));
  // https://github.com/winstonjs/winston/issues/1250
  logger.end();
  return loggerClosed;
}

El problema # 1081 se refiere a este hilo, pero no hay una solución real para los registros de Cloudwatch que eliminan el ID de solicitud mediante transportes de registradores. La única forma en que pude incluir requestID es la siguiente usando el objeto context Lambda -

createLogger({
    defaultMeta: { service: process.env.AWS_LAMBDA_FUNCTION_NAME,
                    requestID: context.awsRequestId
                   },
    transports: [
        new transports.Console({
            level: 'debug',
            format: format.combine(
                format.timestamp({
                    format: 'YYYY-MM-DD HH:mm:ss'
                  }),
                format.errors({ stack: true }),
                format.splat(),
                format.json()
            )
        })
    ]
});

Sin embargo, este método no funciona si su Logger.js está fuera de su controlador Lambda, por ejemplo, en una Capa Lambda. Aparentemente, no se puede acceder al contexto desde las importaciones, por lo que si tiene su definición de Logger incluida en una Capa y la importa en sus Lambda (s), entonces tendría que inyectar defaultMeta al logger objeto dentro de la función del controlador.

PD : Creo que definir Logger por función Lambda individual es una idea terrible, de ahí el uso de Capas.

const logger = require('./opt/nodejs/Logger'); //import from Lambda Layer

exports.handler = async (event, context) => {
    //inject defaultMeta
    logger.log.defaultMeta =  { service: process.env.AWS_LAMBDA_FUNCTION_NAME,
                            requestID: context.awsRequestId };
    logger.log.info(`This is just an info message`);
    logger.log.error("This is an error message");
    await logger.waitForLogger(logger.log);
    return event;
};

logger.end() funciona solo para la primera llamada a la función. La segunda llamada genera un error:

{
    "errorType": "Error",
    "errorMessage": "write after end",
    "code": "ERR_STREAM_WRITE_AFTER_END",
    "stack": [
        "Error: write after end",
        "    at writeAfterEnd (/var/task/node_modules/readable-stream/lib/_stream_writable.js:257:12)",
        "    at DerivedLogger.Writable.write (/var/task/node_modules/readable-stream/lib/_stream_writable.js:301:21)",
        "    at DerivedLogger.(anonymous function) [as info] (/var/task/node_modules/winston/lib/winston/create-logger.js:81:14)",
        "    at Runtime.handler (/var/task/lambda_logger.js:65:12)",
        "    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
    ]
}

Yo uso winston como decorador para el controlador lambda:

// lambda_logger.js
const winston = require('winston')
const WinstonCloudWatch = require('winston-cloudwatch')
const { format } = winston

async function closeLogger(logger) {
  const transportsFinished = logger.transports.map(
    t => new Promise(resolve => t.on('finish', resolve))
  )
  logger.end()
  return Promise.all(transportsFinished)
}

const decorator = (wrapped, loggerName = null) => {
  return async (event, context, callback) => {
    const LAMBDA_LOGGER_NAME =
      loggerName || `${process.env.AWS_LAMBDA_FUNCTION_NAME}-lambda-logger`

    winston.loggers.add(LAMBDA_LOGGER_NAME, {
      format: format.combine(
        format.timestamp({ format: 'DD-MM-YYYY HH:mm:ss' }),
        format.errors(),
        format.label({
          label: `${process.env.AWS_LAMBDA_FUNCTION_NAME}:${process.env.AWS_LAMBDA_FUNCTION_VERSION}`,
        }),
        format.splat(),
        format.json()
      ),
      transports: [
        new WinstonCloudWatch({
          logGroupName: process.env.AWS_LAMBDA_LOG_GROUP_NAME,
          logStreamName: process.env.AWS_LAMBDA_LOG_STREAM_NAME,
          awsRegion: process.env.AWS_REGION,
          jsonMessage: true,
          retentionInDays: 3,
        }),
      ],
      defaultMeta: { context, logger_name: LAMBDA_LOGGER_NAME },
    })
    const logger = winston.loggers.get(LAMBDA_LOGGER_NAME)

    logger.info({ env: process.env, event })
    let res

    try {
      res = await wrapped(event, {
        context,
        callback,
        loggerName: LAMBDA_LOGGER_NAME,
      })
      logger.debug('RES:', res)
    } catch (e) {
      console.error(e)
      throw e
    }

    await closeLogger(logger)

    return res
  }
}

module.exports = decorator
// handler.js
const winston = require('winston')
const loggingDecorator = require('./lambda_logger')

const hello = async (event, opts) => {
  const { loggerName } = opts
  const logger = winston.loggers.get(loggerName)

  logger.warn({ logger })

  const res = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'We use Winston logger as a decorator for lambda handdler!!'
    }),
  }

  return res
}

module.exports.hello = loggingDecorator(hello)

¿Alguna idea para solucionarlo?

Creo que esta cuestión debería ser la primera prioridad.
Para procesos con código como:

process.on('uncaughtException', err => {
    logger.error(err, () => {
        process.exit(1);
    });
});

Hará que el proceso se cuelgue para siempre y no pueda reiniciarse correctamente.

¿Se solucionará este problema o simplemente se mantendrá cerrado?

La solución de @dpraul aquí (https://github.com/winstonjs/winston/issues/1250#issuecomment-452128291) explicó muy bien el problema y la solución. Esto funcionó para mí en AWS lambda.

Una nota que llamó anteriormente en el hilo que repetiré. Deberá crear su registrador Y transportar objetos al comienzo del controlador lambda. Si intenta reutilizarlos, obtendrá errores, ya que el flujo de transporte se cerrará cuando se congele el entorno de ejecución lambda.

De una manera más simple me gusto mucho a mí mismo:

function noop() {}

class AsyncTransport extends Transport {
  constructor(opts) {
    super(opts)
    this.callsInProgress = new Map()
    this.index = 0
  }

  async finish() {
    return Promise.all(this.callsInProgress.values())
  }

  log(info) {
    const promise = someAsyncFn(info)
    const i = this.index++
    this.callsInProgress.set(
      i,
      new Promise(resolve => {
        promise.then(resolve)
        setTimeout(resolve, 3000)
      }).then(() => {
        this.callsInProgress.delete(i)
      }),
    )
  }
}

const asyncTransport = new AsyncTransport({ level: 'info' })

const logger = createLogger({
  transports: [
    asyncTransport,
  ],
})
logger.flush= async () => asyncTransport.flush()

module.exports = logger

Luego, en tu lambda puedes nuevamente:

exports.handler = async function() {
  logger.info('I will be awaited at the end!')
  await logger.flush()
}

Hay documentación flotando que dice que puede llamar a end () en un registrador de Winston para vaciar, pero no dice que end () sea lo mismo que llamar a close (). Solo tienes que saberlo. Por lo tanto, no hay forma de vaciar un registrador y continuar usándolo, que es un caso de uso para las lambdas de AWS de JavaScript.

Hay una opción de descarga reutilizable para el transporte de WinstonCloudWatch: kthxbye. Busque kthxbye para obtener más información.

Si desea escribir un process.on (controlador de 'salida' genérico que cierra un registrador de Winston, esto es lo que debe hacer.

// Send kthxbye to WinstonCloudWatch transports
if (logger.writable) {
  await new Promise(resolve => {
    logger.on('error', resolve).on('close', resolve).on('finish',
      () => logger.close()).end();
  });
}
// Send kthxbye to WinstonCloudWatch transports again

El acto de cerrar los registradores puede provocar que se envíen excepciones no controladas (incluidas aquellas lanzadas antes de que se ejecute el código anterior) a los transportes (particularmente WinstonCloudWatch), por lo que debe llamar a kthxbye nuevamente después de que finalice el código anterior. Si. En realidad. Es de esperar que otros transportes como el archivo se vacíen en este escenario; lo he probado y parece que se vacían, probablemente porque nodeJS elimina los identificadores de archivos al salir (lo que en algunos casos puede que no sea lo suficientemente pronto).

Por lo tanto, Winston 3 todavía no tiene una operación de descarga que funcione con todos los transportes.

¿Fue útil esta página
0 / 5 - 0 calificaciones