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?
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
winston@3
tanto Logger
como Transport
son transmisiones de Node.js..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:
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
.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:
'finish'
en todos los transporteslogger.end()
'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.
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:
y
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:
¿Detecta el problema?
logger
disparalogger.end()
dentro del contexto de la función del controlador, pero la función activada porlogger.on('finish')
ejecuta fuera del contexto del controlador. Cualquier proceso asíncrono atado porCustomAsyncTransport
impedirá que se active el eventofinish
, 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:Dado que
lambdaHandler
no sale hasta quelogger
activa el eventofinish
, nuestroCustomAsyncTransport
debería cerrarse antes que nuestro controlador lambda, evitando que esos procesos se congelen (suponiendo el eventofinish
está implementado correctamente por @indexzero).Esto se puede generalizar a algo similar al código que he compartido anteriormente:
Espero que esto ayude a algunas personas.