Winston: Protokollierungsmethoden nehmen keinen Rückruf mehr entgegen und können ihn in einigen Umgebungen nicht zuverlässig verwenden (AWS Lambda).

Erstellt am 29. März 2018  ·  44Kommentare  ·  Quelle: winstonjs/winston

In Winston 3 nehmen die Protokollierungsfunktionen keinen Rückruf mehr entgegen. Bisher konnte dies verwendet werden, um zu warten, bis die Protokollierung abgeschlossen ist:

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

Mit Winston 3 können Sie das Ereignis logged auf Ihren Transporten abhören, aber das gibt mir keine einfache Möglichkeit zu erkennen, wann diese Nachricht, dass ich mich gerade anmelde, abgeschlossen ist. Da alles asynchron ist, hängt das nächste logged -Ereignis, das nach dem Schreiben Ihrer Nachricht auftritt, möglicherweise nicht mit der gerade protokollierten Nachricht zusammen. Und es ist komplexer, wenn Sie das Ereignis auf mehreren Transporten abhören.

Dies macht es schwierig, Winston in bestimmten Umgebungen zu verwenden. In AWS Lambda setzen wir beispielsweise den Parameter callbackWaitsForEmptyEventLoop auf false ( Dokumentation ). (Nebenbemerkung: Sie tun dies, wenn Sie eine Datenbankverbindung haben oder etwas, das nicht unref ed sein kann, da Lambda sonst Ihren Prozess niemals einfrieren wird). Bei Einstellung auf false friert Lambda Ihren Prozess ein, sobald Sie Ergebnisse an den Anrufer zurücksenden, und beendet möglicherweise sogar Ihren Prozess. Wenn Ihr Protokollierungstransport zu diesem Zeitpunkt noch nicht fertig geschrieben ist, verlieren Sie entweder Protokolle oder (schlimmer noch) die Protokolle werden später geschrieben, wenn Lambda Ihren Prozess auftaut.

TL; DR: Unser Lambda-Prozess wartet normalerweise auf Winston (oder das entsprechende Rückrufverfahren), bevor er die Ergebnisse an den Anrufer zurückgibt, um sicherzustellen, dass die Protokollierung durchgeführt wird, bevor der Prozess eingefroren wird.

Gibt es eine andere Möglichkeit, um zu erkennen, wann die Protokollierung abgeschlossen ist?

help wanted important investigate

Hilfreichster Kommentar

Ich bekomme immer wieder Benachrichtigungen zu diesem Thread, also werde ich Ihnen mitteilen, warum ich denke, dass dies bei vielen Menschen immer noch nicht funktioniert. Hintergrund: Unser Anwendungsfall (und der Kontext, in dem dieses Problem geöffnet wurde) ist AWS Lambda. Was ich hier spreche, gilt also nur dort.

Lambda führt Node im Lambda-Ausführungskontext aus . Relevante Informationen sind:

Nachdem eine Lambda-Funktion ausgeführt wurde, behält AWS Lambda den Ausführungskontext für einige Zeit in Erwartung eines weiteren Aufrufs der Lambda-Funktion bei.

und

  • Alle Deklarationen in Ihrem Lambda-Funktionscode (außerhalb des Handler-Codes, siehe Programmiermodell ) bleiben initialisiert und bieten zusätzliche Optimierung, wenn die Funktion erneut aufgerufen wird.

Um den Start zu beschleunigen, "friert" Lambda Umgebungen ein und "taut sie auf", anstatt sie vollständig herunterzufahren. Zu diesem Zweck wartet Lambda nicht auf das Beenden des Knotens, sondern auf das Beenden Ihrer Handlerfunktion. Alle asynchronen Prozesse werden außerhalb der Handlerfunktion ausgeführt und können daher mit dem Rest des Lambda-Ausführungskontexts eingefroren werden, wenn nicht auf sie gewartet wird.

Schauen wir uns nun die vorgeschlagene Lösung für das Warten auf Winston an - angepasst von

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();
}

Finde das Problem? logger löst logger.end() im Kontext der Handlerfunktion aus, aber die von logger.on('finish') ausgelöste Funktion wird außerhalb des Handlerkontexts ausgeführt. Alle asynchronen Prozesse, die mit CustomAsyncTransport verknüpft sind, verhindern, dass das Ereignis finish ausgelöst wird, sodass es wahrscheinlich ist, dass der Ausführungskontext vor dem Auslösen dieses Ereignisses einfriert.

Um dies zu lösen, muss lambdaHandler warten, bis der Logger beendet ist, bevor er Folgendes auflöst:

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;
}

Da lambdaHandler erst beendet wird, wenn logger das finish -Ereignis auslöst, sollte unser CustomAsyncTransport vor unserem Lambda-Handler geschlossen werden, um zu verhindern, dass diese Prozesse eingefroren werden (vorausgesetzt Das finish -Ereignis wird von @indexzero korrekt implementiert.

Dies kann auf etwas verallgemeinert werden, das dem Code ähnelt, den ich zuvor geteilt habe:

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);
}

Hoffe das hilft einigen Leuten.

Alle 44 Kommentare

Wir verwenden eine andere Lösung.

Macht es nicht zu einem Problem. Danke, dass du es angesprochen hast.

EDIT: das funktioniert nicht

============
Ich bin mir nicht sicher, ob dies der beste Weg ist oder ob es in allen Fällen funktioniert, aber bisher behandeln wir dies folgendermaßen:

Ich habe eine wait-for-logger.js -Funktion als solche erstellt:

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

module.exports = waitForLogger;

Am Ende unseres Handlers warten wir auf die Funktionsausgabe, bevor wir zurückkehren:

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);
}

Wir verwenden die Lambda-Laufzeit von nodejs8.10. Wenn Sie eine ältere Laufzeit verwenden, können Sie wahrscheinlich Folgendes tun:

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

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

Danke @dpraul. Einer unserer Transporte protokolliert zu einem externen Dienst, und das kann einige zusätzliche ms dauern. Wird logger.close() warten, dass dies abgeschlossen ist?

@natesilva dachte noch etwas darüber nach

  1. In winston@3 sowohl die Logger als auch die Transport Node.js Streams .
  2. Beschreibbare Streams legen eine .end -Methode und ein 'finish' -Ereignis offen.

Daher sollte dies theoretisch funktionieren, aber ich habe es noch nicht getestet:

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();

Verifiziert:

{"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

Fügen Sie hierzu ein Beispiel hinzu, um das Problem zu beheben. Bitte öffnen Sie erneut, wenn dies in Lambda bei Ihnen nicht funktioniert. Functions-as-a-Service-Plattformen befinden sich zu 100% in der winston Achten Sie also darauf, dass dies ordnungsgemäß funktioniert.

Wäre großartig, wenn Sie ein End-to-End-AWS-Lambda-Beispiel mit winston beitragen könnten, wenn Sie die Zeit @natesilva haben.

Ich überprüfe dies, während wir sprechen, und ich glaube, ich habe keine dieser Methoden überprüft, um zu funktionieren. Ich werde versuchen, hier in Kürze einen vollständigen Testfall zu teilen.

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();

Erwartete Ausgabe:

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

Tatsächliche Ausgabe:

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

Das finish -Ereignis wird ausgelöst, bevor alle Transporte abgeschlossen sind. Das gleiche Verhalten tritt auf, wenn .on('close', fn) und das Ereignis .close() am Ende verwendet werden.

Entschuldigung, ich habe meinen Kommentar gelöscht. Mein Verständnis dieser Stream-Ereignisse ist begrenzt, aber ich kann sehen, wie finish zur falschen Zeit ausgelöst werden kann, wie im Beispiel von @ dpraul angegeben.

Ich habe das gleiche Beispiel mit den Ereignissen end , finish und close durchlaufen und sowohl stream.Transform.close() als auch stream.Transform.end() - verwendet. Alle haben die gleichen Ergebnisse, bei denen die Ereignisse ausgelöst werden, bevor CustomerTransport seine callback .

Könnte es ein Problem geben, wie Ereignisse zwischen Transports und Logger ?

Unabhängig von @natesilva oder @indexzero möchten Sie dieses Problem möglicherweise erneut öffnen.

@dpraul das scheint plausibel, aber ich sehe nicht wo das wäre:

  1. Transport in winston-transport implementiert die _write -Methode, um die Writeable -Schnittstelle von Node.js zu erfüllen. Dies empfängt die callback und übergibt sie an die log -Methode .
  2. Console Transport in winston ruft process.{stderr,stdout}.write und ruft dann die callback . Afaik, das auf process.stdout und process.stderr schreibt, sind synchrone Operationen, daher sollte kein potenzielles Risiko für Gegendruck bestehen.

Es ist klar, dass in diesem Fluss etwas übersehen wird. Ich gehe mit einigen Leuten von Node.js chatten und sehe, was ich ausgraben kann.

Ich weiß immer noch nicht, was das Problem verursacht, aber ich habe eine Problemumgehung gefunden, die darauf wartet, dass alle Transporte abgeschlossen sind!

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!'));

Ausgabe:

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

Es scheint, dass sich logger.end() ordnungsgemäß auf die Transporte ausbreitet, aber das Ereignis finish auslöst, bevor alle Transporte ihre ausgelöst haben. Die Problemumgehung besteht darin, auf jedes Transport zu warten, um sein eigenes finish -Ereignis auszulösen, und sich überhaupt nicht auf das Logger zu verlassen.

Vielen Dank, dass Sie @dpraul weiter untersuchen. Das logger wird an alle Transporte weitergeleitet. Wenn der Logger geschlossen ist, habe ich verstanden, dass er erst dann fertig sein wird, wenn alle Pipe-Ziele erreicht sind, aber ich kann mich irren.

Ich werde mich bei einigen Leuten verifizieren.

Dank einiger Eingaben von @mcollina @davidmarkclements & @mafintosh habe ich erfahren, dass mein Verständnis der pipe -Semantik fehlerhaft war.

  • 'error' und 'finish' -Ereignisse breiten sich nicht über die Pipe-Kette aus.
  • 'error' Ereignisse führen dazu, dass Unpipe auftritt.

Hier erfahren Sie, wie Sie diese Ereignisse entweder zurückgeben oder eine explizite Methode für Logger verfügbar machen , die die Technik implementiert, die Sie jetzt verwenden des Stream-Endes ).

@natesilva wird dieses Problem offen lassen, bis der explizite Fix

  • Achten Sie auf 'finish' Ereignisse auf allen Transporten
  • Rufen Sie logger.end()
  • Wenn alle 'finish' -Ereignisse ausgegeben wurden, ist die Protokollierung abgeschlossen.

Wichtige Module, die nützlich sein könnten: Pumpe und Stream-End.

@mcollina Theoretisch sollten wir aus der Perspektive von Node.js Streams in der Lage sein, _final auf dem Logger zu finish Ereignisse später ausgegeben werden, oder?

@indexzero ja, das wäre richtig.

Sagen Sie mir bitte, in Winston @ 3 kann ich keine Rückrufe wie in Version 2 verwenden?
(Es gibt keine Antwort in der Dokumentation)

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

Und wenn Sie keinen Rückruf verwenden, wird meine App schneller geschlossen, als die Nachricht protokolliert wurde = (

@TuxGit FYI dies wurde in UPGRADE-3.0 dokumentiert (siehe: der PR-Code )

@natesilva @dpraul PR ist offen, um dies jetzt zu https://github.com/winstonjs/winston/pull/1346

Ich glaube das ist noch kaputt. Obwohl sich das finish -Ereignis nun korrekt ausbreitet und erwartet wird, zumindest beim File -Transport, hat das zugrunde liegende WriteableStream das Schreiben auf die Festplatte noch nicht abgeschlossen.

Ich glaube, dass für das PassthroughStream stattdessen sein finish -Ereignis ausgegeben wird, weshalb es nicht auf die Schreibvorgänge wartet.

Nevermind - das PassthruStream ist in Ordnung, aber es funktioniert nicht, wenn man das finish -Ereignis hört.

Ich kann bestätigen, dass die Verkabelung bis zu end-of-stream und die Verwendung zum Anhören von transport._stream tatsächlich funktioniert.

Gibt es hierzu Neuigkeiten ? Ich versuche logger.on('finish', () => process.exit(0)) aber es scheint nicht zu beenden. Es existiert nur nach Ablauf des Timeouts

Ich habe das gleiche Problem bei der Verwendung des Konsolentransports (ich habe die Datei nicht ausprobiert, um ehrlich zu sein). Es scheint, dass Logger stream.Transform von stream-readable Modul erweitert und _final Methode nicht in _stream_transform aufgerufen wird, sondern nur in _stream_writable . Ich wollte wirklich nicht weiter darauf eingehen, da ich dieses Modul zum ersten Mal benutze und mich entschuldige, wenn ich etwas falsch verstanden habe.

../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') {

Fügen Sie einen benutzerdefinierten Transport hinzu, überprüfen Sie die Protokollebene und fügen Sie in der jeweiligen Ebene eine benutzerdefinierte Logik hinzu. Dies ist nicht gleichbedeutend mit einem Rückruf. Wenn Sie jedoch einige Protokolle optimieren und an ein anderes Ziel wie die Luftbremse senden möchten, ist dies hilfreich.

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;

Fügen Sie in der Datei, in der Sie Ihren benutzerdefinierten Winston-Logger mit winston.createLogger (...) erstellen, Ihren CustomTransport in die Konfiguration in das Array der Option "transports" ein.

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

Hallo, ich habe den unten empfohlenen Code verwendet, aber das Schreiben der Dateien wird nicht abgeschlossen, bevor der Vorgang abgeschlossen wird. Ich sehe die Fehler, die in die Konsole geschrieben wurden. Wenn ich die Datei process.exit nicht aufrufe, werden die Dateien korrekt geschrieben. Ich sehe, dass dieses Problem geschlossen ist, aber behoben wurde.

logger.log ('info', 'irgendeine Nachricht');
logger.on ('finish', () => process.exit ());
logger.end ();

Hat jemand das richtig funktioniert? Irgendwelche Ideen?

@tsaockham
Ich habe Lösung :)

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

Nach 2 Stunden Suchlösung habe ich dies implementiert, auch die Funktion waitForLogger von

Ich bekomme immer wieder Benachrichtigungen zu diesem Thread, also werde ich Ihnen mitteilen, warum ich denke, dass dies bei vielen Menschen immer noch nicht funktioniert. Hintergrund: Unser Anwendungsfall (und der Kontext, in dem dieses Problem geöffnet wurde) ist AWS Lambda. Was ich hier spreche, gilt also nur dort.

Lambda führt Node im Lambda-Ausführungskontext aus . Relevante Informationen sind:

Nachdem eine Lambda-Funktion ausgeführt wurde, behält AWS Lambda den Ausführungskontext für einige Zeit in Erwartung eines weiteren Aufrufs der Lambda-Funktion bei.

und

  • Alle Deklarationen in Ihrem Lambda-Funktionscode (außerhalb des Handler-Codes, siehe Programmiermodell ) bleiben initialisiert und bieten zusätzliche Optimierung, wenn die Funktion erneut aufgerufen wird.

Um den Start zu beschleunigen, "friert" Lambda Umgebungen ein und "taut sie auf", anstatt sie vollständig herunterzufahren. Zu diesem Zweck wartet Lambda nicht auf das Beenden des Knotens, sondern auf das Beenden Ihrer Handlerfunktion. Alle asynchronen Prozesse werden außerhalb der Handlerfunktion ausgeführt und können daher mit dem Rest des Lambda-Ausführungskontexts eingefroren werden, wenn nicht auf sie gewartet wird.

Schauen wir uns nun die vorgeschlagene Lösung für das Warten auf Winston an - angepasst von

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();
}

Finde das Problem? logger löst logger.end() im Kontext der Handlerfunktion aus, aber die von logger.on('finish') ausgelöste Funktion wird außerhalb des Handlerkontexts ausgeführt. Alle asynchronen Prozesse, die mit CustomAsyncTransport verknüpft sind, verhindern, dass das Ereignis finish ausgelöst wird, sodass es wahrscheinlich ist, dass der Ausführungskontext vor dem Auslösen dieses Ereignisses einfriert.

Um dies zu lösen, muss lambdaHandler warten, bis der Logger beendet ist, bevor er Folgendes auflöst:

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;
}

Da lambdaHandler erst beendet wird, wenn logger das finish -Ereignis auslöst, sollte unser CustomAsyncTransport vor unserem Lambda-Handler geschlossen werden, um zu verhindern, dass diese Prozesse eingefroren werden (vorausgesetzt Das finish -Ereignis wird von @indexzero korrekt implementiert.

Dies kann auf etwas verallgemeinert werden, das dem Code ähnelt, den ich zuvor geteilt habe:

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);
}

Hoffe das hilft einigen Leuten.

Ich denke, das funktioniert nicht für den Standard-Logger?

winston.on() ist keine Funktion.

@dpraul Lösung hat bei mir funktioniert.

Ein wenig angepasst:

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

Zu beachten ist, dass es empfehlenswert ist, den Fehlerbehandler gleichzeitig mit der Erstellung Ihres logger . Die Fehler können jederzeit ausgelöst werden. Wenn Sie sie nur beim Schließen des Loggers anhören, werden Sie sie wahrscheinlich übersehen.

Ich möchte winston3 unter Nodejs8 verwenden, um mich mit dem sqlTransport-Modul in mariaDB anzumelden. Dafür habe ich den sqlTransport für Winston3 gemäß docs / transports.md angepasst.
Winston3 ließ das Programm am Ende hängen.
Ich habe mit verschiedenen Vorschlägen aus dieser Ausgabe experimentiert und bin zu folgendem Schluss gekommen:
Im sqlTransport-Modul muss eine close () -Methode implementiert sein, die das Modul (dh den darin enthaltenen Client) korrekt schließt.
Hinweis: Implementieren Sie keine end () -Methode! - Ich weiß wirklich nicht warum.
Jetzt reicht logger.end () aus, um das Programm korrekt zu schließen.
Logger.close (), was einen sofortigen Abbruch des Loggers und einen Datenverlust verursacht.

Wenn am Ende des Programms eine Fehlerbehandlungsroutine erforderlich ist, können Sie diese verwenden

Funktion waitForLogger (myLogger) {
const loggerDone = neues Versprechen ((auflösen, ablehnen) => {
myLogger.on ('finish', () => {return resolve ()})
myLogger.on ('error', err => {return reverse (err)})
})
myLogger.end ();
return loggerDone;
}}
und
waitForLogger (logger) .then (() => {console.log ('alles erledigt!')}). catch ((err) => {console.error (err)});

Ich kann das immer noch nicht zum Laufen bringen. Ich verwende Winston 3.1.0 und diesen Slack-Transport Winston-Slack-Webhook-Transport, der eine http-Anfrage stellt. Empfangen dieses Fehlers:

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)

BEARBEITEN:
Ich habe jetzt festgestellt, dass ich den Logger im Handler erstellen muss. Andernfalls kann das Lambda den Kontext wiederverwenden und versuchen, eine "beendete" Logger-Instanz zu verwenden.

Ich erhalte den gleichen Fehler wie

Mit AWS API Gateway können wir 1-2 Nachrichten protokollieren, jedoch nach logger.end (). Es wird immer noch versucht, denselben Logger zu verwenden, und wir bekommen
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)

Wir verwenden die Papertrail-Protokollierung mit winston-syslog.

Hier ist der Code.

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

const options = {
Host: PAPERTRAIL_URL,
Port: PAPERTRAIL_PORT,
app_name: PAPERTRAIL_LOG_CHANNEL,
localhost: Hostname
}}

const ptTransport = new winston.transports.Syslog (Optionen)
const logger = winston.createLogger ({
Transporte: [ptTransport],
Format: winston.format.combine (
winston.format.colorize ({
alles wahr
}),
winston.format.simple ()
)
}) `

@hdpa Das liegt wahrscheinlich daran, dass Ihre Funktion nach dem Schließen wiederverwendet wird. Unsere aktuelle Lösung lautet:

// 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;
}

Dann rufen wir in unserer Funktion einfach getLogger() auf, um den Logger zu erhalten, der ihn automatisch wieder öffnet, wenn er geschlossen wird. Am Ende der Funktion verwenden wir closeLogger() , um sicherzustellen, dass alle Protokolle in Cloudwatch gelöscht werden

Implementierte die obigen Änderungen, bekam aber immer noch das gleiche Problem. Ich habe das Logger-Objekt in der Konsole gedruckt. Unten ist die Antwort für die erste und zweite Anforderung.

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: 

Fehler: Schreiben Sie nach dem Ende bei writeAfterEnd (/var/task/node_modules/winston/node_modules/readable-stream/lib/_stream_writable.js:257:12) bei DerivedLogger.Writable.write (/ var / task / node_modules / winston / node_modules) /readable-stream/lib/_stream_writable.js:301:21) bei DerivedLogger (anonyme Funktion) [als Fehler] (/ var / task / node_mod

Es sieht so aus, als würde der Syslog-Transport nach der Endmethode entfernt, und dort wird ein Fehler angezeigt.

Vergiss meinen Beitrag, es scheint, dass es bei der ersten Anfrage funktioniert, bei der zweiten etwas Zeitüberschreitung. Es wäre gut, nur darauf zu warten, dass er gespült wird, anstatt ihn zu schließen

Oh, das Problem mit oben ist, dass ich versuche, einen bereits geschlossenen Logger zu schließen, damit das Versprechen nie gelöst wird. Dies behebt es

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;
}

Problem Nr. 1081 bezieht sich auf diesen Thread, aber es gibt keine echte Lösung für Cloudwatch-Protokolle, bei denen die Anforderungs-ID mithilfe von Logger-Transporten entfernt wird. Die einzige Möglichkeit, requestID einzuschließen, ist die folgende: Verwenden des context -Objekts von 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()
            )
        })
    ]
});

Diese Methode funktioniert jedoch nicht, wenn sich Ihr Logger.js zufällig außerhalb Ihres Lambda-Handlers befindet, z. B. in einer Lambda-Ebene. Anscheinend ist der Kontext für Importe nicht zugänglich. Wenn Sie also Ihre Logger-Definition in einer Ebene enthalten und in Ihre Lambda (s) importieren, müssen Sie defaultMeta in die logger Objekt innerhalb der Handlerfunktion.

PS : Ich halte es für eine schreckliche Idee, Logger durch einzelne Lambda-Funktionen zu definieren, daher die Verwendung von Ebenen.

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() funktioniert nur beim ersten Funktionsaufruf. Der zweite Aufruf führt zu einem Fehler:

{
    "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)"
    ]
}

Ich benutze winston als Dekorateur für Lambda-Handler:

// 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)

Irgendwelche Ideen, wie man das Problem behebt?

Ich denke, dieses Thema sollte oberste Priorität haben.
Für den Prozess mit Code wie:

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

Dies führt dazu, dass der Prozess für immer hängen bleibt und nicht ordnungsgemäß neu gestartet werden kann.

Wird dieses Problem behoben oder bleibt es einfach geschlossen?

Die Lösung von @dpraul hier (https://github.com/winstonjs/winston/issues/1250#issuecomment-452128291) erklärte das Problem und die Problemumgehung sehr gut. Dies funktionierte bei mir in AWS Lambda.

Eine Notiz, die er früher in dem Thread gerufen hat, den ich noch einmal wiederholen werde. Sie müssen Ihren Logger erstellen UND Objekte am Anfang des Lambda-Handlers transportieren. Wenn Sie versuchen, sie wiederzuverwenden, werden Fehler angezeigt, da der Transportstrom geschlossen wird, wenn die Lambda-Ausführungsumgebung eingefroren ist.

Eine einfachere Art, wie ich mich wirklich mag:

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

Dann können Sie in Ihrem Lambda wieder:

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

Es gibt eine Dokumentation, die besagt, dass Sie end () auf einem Winston-Logger zum Leeren aufrufen können, aber nicht, dass end () mit dem Aufruf von close () identisch ist. Du musst es nur wissen. Daher gibt es keine Möglichkeit, einen Logger zu leeren und weiter zu verwenden. Dies ist ein Anwendungsfall für JavaScript AWS-Lambdas.

Für den WinstonCloudWatch-Transport gibt es eine wiederverwendbare Spüloption: kthxbye. Suchen Sie nach kthxbye für weitere Informationen.

Wenn Sie einen generischen process.on ('exit'-Handler schreiben möchten, der einen Winston-Logger schließt, müssen Sie Folgendes tun.

// 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

Das Schließen von Protokollierern kann dazu führen, dass nicht behandelte Ausnahmen (einschließlich der Ausnahmen, die ausgelöst werden, bevor der obige Code ausgeführt wird) an Transporte (insbesondere WinstonCloudWatch) gesendet werden, sodass Sie kthxbye nach Abschluss des obigen Codes erneut aufrufen müssen. Ja. Ja wirklich. Hoffentlich werden andere Transporte wie die Datei in diesem Szenario gelöscht - ich habe dies getestet und sie scheinen gelöscht zu werden, wahrscheinlich weil nodeJS die Dateihandles beim Beenden löscht (was in einigen Fällen möglicherweise nicht früh genug ist).

Daher verfügt Winston 3 immer noch nicht über einen Spülvorgang, der mit allen Transporten funktioniert.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen