Winston: لم تعد طرق التسجيل تستقبل رد اتصال ، ولا يمكن استخدامها بشكل موثوق في بعض البيئات (AWS Lambda)

تم إنشاؤها على ٢٩ مارس ٢٠١٨  ·  44تعليقات  ·  مصدر: winstonjs/winston

في Winston 3 ، لم تعد وظائف التسجيل تأخذ رد الاتصال. في السابق كان يمكن استخدام هذا للانتظار حتى اكتمال التسجيل:

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

يسمح لك Winston 3 بالاستماع إلى حدث logged في وسائل النقل الخاصة بك ، لكن هذا لا يمنحني أي طريقة سهلة لمعرفة متى اكتملت هذه الرسالة التي أقوم بتسجيلها حاليًا. نظرًا لأن كل شيء غير متزامن ، فقد لا يكون الحدث التالي logged الذي يحدث بعد كتابة رسالتك مرتبطًا بالرسالة التي قمت بتسجيلها للتو. ويكون الأمر أكثر تعقيدًا إذا كنت تستمع إلى الحدث في وسائل نقل متعددة.

هذا يجعل من الصعب استخدام Winston في بيئات معينة. على سبيل المثال ، في AWS Lambda ، قمنا بتعيين المعلمة callbackWaitsForEmptyEventLoop على false ( التوثيق ). (ملاحظة جانبية: يمكنك القيام بذلك إذا كان لديك اتصال قاعدة بيانات أو شيء لا يمكن أن يكون unref ed ، وإلا فلن تجمد Lambda العملية أبدًا). إذا تم التعيين على false ، فإن Lambda ستجمد العملية بمجرد إرجاع النتائج إلى المتصل وقد تنهي العملية. إذا لم تنته عملية نقل التسجيل من الكتابة بحلول الوقت الذي يحدث فيه ذلك ، فإنك إما تفقد السجلات أو (الأسوأ من ذلك) تتم كتابة السجلات لاحقًا عندما تقوم Lambda بإلغاء تجميد عمليتك.

TL ؛ DR: عادةً ما تنتظر عملية Lambda (أو ما يعادلها من رد الاتصال) على Winston قبل إعادة النتائج إلى المتصل ، مما يضمن أن التسجيل يتم قبل تجميد العملية.

هل هناك طريقة أخرى توصي بها للكشف عند اكتمال التسجيل؟

help wanted important investigate

التعليق الأكثر فائدة

أستمر في تلقي تنبيهات حول هذا الموضوع ، لذا سأشارك لماذا أعتقد أن هذا لا يزال لا يعمل مع الكثير من الناس. الخلفية: حالة الاستخدام الخاصة بنا (والسياق الذي تم فيه فتح هذه المشكلة) هي AWS Lambda ، لذا فإن ما أتحدث عنه هنا ينطبق فقط هناك.

تقوم Lambda بتشغيل Node ضمن سياق تنفيذ Lambda . المعلومات ذات الصلة هي:

بعد تنفيذ وظيفة Lambda ، تحتفظ AWS Lambda بسياق التنفيذ لبعض الوقت تحسباً لاستدعاء وظيفة Lambda أخرى.

و

  • تظل أي إعلانات في كود دالة Lambda (خارج رمز المعالج ، راجع نموذج البرمجة ) مهيأة ، مما يوفر تحسينًا إضافيًا عند استدعاء الوظيفة مرة أخرى.

أي لتسريع عمليات الإطلاق ، تقوم Lambda "بتجميد" و "إذابة الجليد" البيئات بدلاً من إغلاقها بالكامل. للقيام بذلك ، لا تنتظر Lambda خروج Node ، بل تنتظر خروج وظيفة المعالج. يتم تشغيل أي عمليات غير متزامنة خارج وظيفة المعالج ، وبالتالي يمكن تجميدها مع بقية سياق تنفيذ Lambda إذا لم يتم انتظارها.

الآن دعونا نلقي نظرة على الحل المقترح لانتظار Winston - مقتبس من UPGRADE-3.0.md ، بافتراض أننا نجري في 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();
}

بقعة المشكلة؟ logger تنشيط logger.end() داخل سياق دالة المعالج ، لكن الوظيفة التي يتم تشغيلها بواسطة logger.on('finish') تعمل خارج سياق المعالج. أي عمليات غير متزامنة مرتبطة بـ CustomAsyncTransport ستوقف إطلاق الحدث finish ، مما يجعل من المحتمل أن يتجمد سياق التنفيذ قبل اندلاع هذا الحدث.

لحل هذه المشكلة ، يجب أن ينتظر lambdaHandler حتى يخرج المسجل قبل الحل:

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

نظرًا لأن lambdaHandler لا يخرج حتى يتم تشغيل حدث logger finish ، يجب إغلاق CustomAsyncTransport قبل معالج lambda الخاص بنا ، مما يحفظ هذه العمليات من التجميد (بافتراض تم تنفيذ الحدث finish بشكل صحيح بواسطةindexzero).

يمكن تعميم هذا على شيء مشابه للرمز الذي قمت بمشاركته سابقًا:

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

آمل أن يساعد هذا بعض الناس.

ال 44 كومينتر

نحن نستخدم حلاً مختلفًا.

لا تجعلها مشكلة. شكرا لإحضاره.

تحرير: هذا لا يعمل

============
لست متأكدًا مما إذا كانت هذه هي الطريقة الأفضل ، ولا ما إذا كانت تعمل في جميع الحالات ، لكننا حتى الآن نتعامل مع هذا من خلال القيام بما يلي:

لقد قمت بإنشاء وظيفة wait-for-logger.js النحو التالي:

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

module.exports = waitForLogger;

في نهاية المعالج الخاص بنا ، ننتظر إخراج الوظيفة قبل العودة:

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

نحن نستخدم وقت تشغيل nodejs8.10 Lambda - إذا كنت تستخدم وقت تشغيل أقدم ، فمن المحتمل أن تفعل شيئًا كالتالي:

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

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

شكرا @ dpraul. أحد سجلات النقل لدينا إلى خدمة خارجية ، وقد يستغرق ذلك بضع مللي ثانية إضافية. هل سينتظر logger.close() حتى يكتمل ذلك؟

natesilva فكر في هذا أكثر

  1. في winston@3 كلاً من Logger و Transport عبارة عن تدفقات Node.js.
  2. تيارات للكتابة تعرض على .end طريقة و و 'finish' الحدث .

لذلك من الناحية النظرية ، يجب أن يعمل هذا ، لكنني لم أختبره بعد:

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

تم التحقق:

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

سنضيف مثالاً بهذا كطريقة لحل المشكلة. لا تتردد في إعادة الفتح إذا كان هذا لا يناسبك في Lambda. تعد الأنظمة الأساسية للوظائف كخدمة 100٪ في قاعدة المستخدمين المستهدفين winston لذا اهتم بهذا العمل بشكل صحيح.

سيكون رائعًا إذا كان بإمكانك المساهمة بمثال شامل على AWS lambda باستخدام winston إذا كان لديك الوقتnatesilva.

أقوم بمراجعة هذا بينما نتحدث ، وأعتقد أنني لم أقم بالتحقق من أي من هذه الأساليب للعمل. سأحاول مشاركة حالة اختبار كاملة هنا قريبًا.

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

الناتج المتوقع:

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

الناتج الحقيقي:

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

بدأ الحدث finish قبل انتهاء جميع عمليات النقل. يحدث نفس السلوك عند استخدام حدث .on('close', fn) .close() وحدث

آسف ، لقد حذفت تعليقي. إن فهمي لأحداث البث هذه محدود ، لكن يمكنني أن أرى كيف يمكن أن ينطلق finish في وقت غير مناسب ، وفقًا لمثال @ dpraul.

لقد مررت بالمثال نفسه مع الأحداث end ، finish ، و close ، واستخدمت كلاً من stream.Transform.close() و stream.Transform.end() - جميعها لها نفس النتائج ، حيث أن الأحداث المشغلة قبل CustomerTransport يطلق عليها callback .

هل توجد مشكلة في كيفية تمرير الأحداث بين Transports و Logger ؟

بغض النظر عن natesilva أو indexzero ، قد ترغب في إعادة فتح هذه المشكلة.

@ dpraul الذي يبدو معقولًا ، لكنني لا أرى أين سيكون ذلك:

  1. ينفذ Transport in winston-transport طريقة _write لتنفيذ واجهة Node.js ' Writeable . هذا يتلقى callback ويمرره إلى طريقة log .
  2. Console النقل بـ winston يستدعي process.{stderr,stdout}.write ثم يستدعي callback . تكتب Afaik إلى process.stdout و process.stderr عمليات متزامنة لذا لا ينبغي أن يكون هناك أي خطر محتمل للضغط الخلفي.

من الواضح أن هناك شيئًا ما يتم فقده في هذا التدفق. الذهاب للدردشة مع بعض الأشخاص في Node.js ومعرفة ما يمكنني البحث عنه.

ما زلت لا أعرف سبب المشكلة ، لكنني وجدت حلاً بديلًا ينتظر انتهاء جميع عمليات النقل!

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

انتاج:

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

يبدو أن logger.end() ينتشر بشكل صحيح إلى وسائل النقل ، لكنه يطلق الحدث finish قبل أن تطلق جميع وسائل النقل أحداثها. الحل البديل هو انتظار كل Transport لإطلاق حدث finish وعدم الاعتماد على Logger إطلاقًا.

شكرًا لمواصلة التحقيق معdpraul. يتم نقل logger إلى جميع وسائل النقل ، عندما يتم إغلاق المسجل ، كنت أفهم أنه لن ينبعث منه حتى يتم الانتهاء من جميع أهداف الأنابيب الخاصة به ، ولكن قد أكون مخطئًا في ذلك.

ذاهب للتحقق مع بعض الناس.

وبفضل بعض المدخلات منmcollinadavidmarkclements وmafintosh تعلمت أن أفهم pipe دلالات كانت معيبة.

  • أحداث 'error' و 'finish' لا تنتشر مرة أخرى عبر سلسلة الأنابيب.
  • تتسبب أحداث 'error' حدوث عدم وجود أنبوب.

الذهاب لاستكشاف كيفية إعادة نشر هذه الأحداث أو الكشف عن طريقة صريحة على Logger تنفذ التقنية التي تستخدمها الآن dpraul (ربما تستخدم نهاية الدفق ).

ستتركnatesilva هذه المشكلة مفتوحة حتى يتم الإصلاح الصريح ، ولكن في هذه الأثناء ، يجب أن تكون قادرًا على استخدام التقنية أعلاه:

  • استمع إلى أحداث 'finish' في جميع وسائل النقل
  • اتصل بـ logger.end()
  • عندما يتم إرسال جميع أحداث 'finish' تكون قد انتهيت من التسجيل.

وحدات مهمة قد تكون في متناول اليد: المضخة ونهاية التدفق.

mcollina نظريًا من منظور تدفقات Node.js ، يجب أن نكون قادرين على تنفيذ _final على المسجل لإجبار أحداث finish ليتم إصدارها لاحقًا ، أليس كذلك؟

indexzero نعم ، سيكون ذلك صحيحًا.

قل لي من فضلك ، في winston @ 3 لا يمكنني استخدام عمليات الاسترجاعات كما في الإصدار 2؟
(لا يوجد جواب في الوثائق)

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

وإذا لم يتم استخدام رد الاتصال ، فسيتم إغلاق تطبيقي بشكل أسرع من تسجيل الرسالة = (

TuxGit FYI تم توثيق هذا في UPGRADE-3.0 (انظر: كود العلاقات العامة )

natesilvadpraul PR مفتوح لمعالجة هذا الأمر الآن: https://github.com/winstonjs/winston/pull/1346

أعتقد أن هذا ما زال مكسورًا. على الرغم من أن finish الحدث الآن يضخم بشكل صحيح ويتم ينتظر، على الأقل في File النقل، والكامنة WriteableStream لم ينته التنظيف للكتابة على القرص حتى الان.

أعتقد أن PassthroughStream بدلاً من ذلك يصدر حدثه finish ، وهذا هو السبب في أنه لا ينتظر الكتابة.

لا بأس - PassthruStream جيد ، لكنه لا يعمل عند الاستماع إلى حدث finish .

أستطيع أن أؤكد أن توصيل الأسلاك حتى end-of-stream واستخدام ذلك للاستماع إلى transport._stream يعمل بالفعل.

أي تحديث على هذا ؟ أحاول logger.on('finish', () => process.exit(0)) لكن لا يبدو أنه يخرج. إنه موجود فقط بعد انتهاء المهلة

أواجه نفس المشكلة عند استخدام نقل وحدة التحكم (لم أحاول الملف بصراحة). يبدو أن Logger يمتد stream.Transform من stream-readable الوحدة النمطية و _final لا يتم استدعاء الطريقة في _stream_transform ، فقط _stream_writable . لم أرغب حقًا في الخوض في الأمر لأن هذه هي المرة الأولى التي أستخدم فيها أيًا من هذه الوحدات وأعتذر إذا أسأت فهم شيء ما.

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

أضف وسيلة نقل مخصصة وتحقق من مستوى السجل وأضف منطقًا مخصصًا في المستوى المعني. هذا ليس مكافئًا لفعل شيء ما في رد الاتصال. ولكن إذا كنت تريد التعديل وإرسال بعض السجلات إلى وجهة أخرى مثل airbrake ، فسيساعدك ذلك.

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;

وفي الملف حيث تقوم بإنشاء مسجل winston المخصص باستخدام winston.createLogger (...) ، قم بتضمين النقل المخصص في التكوين ، إلى مصفوفة خيار "النقل".

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

مرحبًا ، لقد استخدمت الكود الموصى به أدناه ، لكنه لم ينته من كتابة الملفات قبل إغلاق العملية. أرى الأخطاء المكتوبة على وحدة التحكم. إذا لم أستدعي العملية process.exit ، فإن الملفات تكتب بشكل صحيح. أرى أن هذه المشكلة مغلقة ، ولكن هل تم حلها؟

logger.log ("معلومات" ، "بعض الرسائل") ؛
logger.on ('finish'، () => process.exit ()) ؛
logger.end () ؛

هل لدى أي شخص هذا يعمل بشكل صحيح؟ أيه أفكار؟

تضمين التغريدة
لدي حل :)

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

بعد ساعتين من حل البحث ، قمت بتنفيذ هذا ، حتى وظيفة waitForLogger بواسطة @ dpraul لم تنجح ، لقد انتهيت.

أستمر في تلقي تنبيهات حول هذا الموضوع ، لذا سأشارك لماذا أعتقد أن هذا لا يزال لا يعمل مع الكثير من الناس. الخلفية: حالة الاستخدام الخاصة بنا (والسياق الذي تم فيه فتح هذه المشكلة) هي AWS Lambda ، لذا فإن ما أتحدث عنه هنا ينطبق فقط هناك.

تقوم Lambda بتشغيل Node ضمن سياق تنفيذ Lambda . المعلومات ذات الصلة هي:

بعد تنفيذ وظيفة Lambda ، تحتفظ AWS Lambda بسياق التنفيذ لبعض الوقت تحسباً لاستدعاء وظيفة Lambda أخرى.

و

  • تظل أي إعلانات في كود دالة Lambda (خارج رمز المعالج ، راجع نموذج البرمجة ) مهيأة ، مما يوفر تحسينًا إضافيًا عند استدعاء الوظيفة مرة أخرى.

أي لتسريع عمليات الإطلاق ، تقوم Lambda "بتجميد" و "إذابة الجليد" البيئات بدلاً من إغلاقها بالكامل. للقيام بذلك ، لا تنتظر Lambda خروج Node ، بل تنتظر خروج وظيفة المعالج. يتم تشغيل أي عمليات غير متزامنة خارج وظيفة المعالج ، وبالتالي يمكن تجميدها مع بقية سياق تنفيذ Lambda إذا لم يتم انتظارها.

الآن دعونا نلقي نظرة على الحل المقترح لانتظار Winston - مقتبس من UPGRADE-3.0.md ، بافتراض أننا نجري في 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();
}

بقعة المشكلة؟ logger تنشيط logger.end() داخل سياق دالة المعالج ، لكن الوظيفة التي يتم تشغيلها بواسطة logger.on('finish') تعمل خارج سياق المعالج. أي عمليات غير متزامنة مرتبطة بـ CustomAsyncTransport ستوقف إطلاق الحدث finish ، مما يجعل من المحتمل أن يتجمد سياق التنفيذ قبل اندلاع هذا الحدث.

لحل هذه المشكلة ، يجب أن ينتظر lambdaHandler حتى يخرج المسجل قبل الحل:

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

نظرًا لأن lambdaHandler لا يخرج حتى يتم تشغيل حدث logger finish ، يجب إغلاق CustomAsyncTransport قبل معالج lambda الخاص بنا ، مما يحفظ هذه العمليات من التجميد (بافتراض تم تنفيذ الحدث finish بشكل صحيح بواسطةindexzero).

يمكن تعميم هذا على شيء مشابه للرمز الذي قمت بمشاركته سابقًا:

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

آمل أن يساعد هذا بعض الناس.

أعتقد أن هذا لا يعمل مع المسجل الافتراضي؟

winston.on() ليست وظيفة.

حل @ dpraul يعمل بالنسبة لي.

تكييفها قليلاً:

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

تجدر الإشارة فقط إلى أنه من الممارسات الجيدة إعداد معالج الأخطاء في نفس الوقت الذي تنشئ فيه logger . يمكن إطلاق الأخطاء في أي وقت - إذا استمعت إليها فقط عند إغلاق المسجل ، فمن المحتمل أن تفوتك.

أريد استخدام winston3 تحت تسجيل Nodejs8 إلى mariaDB باستخدام وحدة النقل sql. لهذا ، قمت بتكييف sqlTransport لـ Winston3 وفقًا لـ docs / transports.md.
تسبب Winston3 في توقف البرنامج في النهاية.
جربت العديد من المقترحات من هذه القضية وتوصلت إلى الاستنتاج التالي:
يجب أن يكون هناك طريقة close () مطبقة في وحدة sqlTransport التي تغلق الوحدة (أي العميل بالداخل) بشكل صحيح.
ملاحظة: لا تطبق طريقة end ()! - أنا حقا لا أعرف لماذا.
الآن ، logger.end () كافٍ لإغلاق البرنامج بشكل صحيح.
Logger.close () ، مما يؤدي إلى إحباط فوري للمسجل وفقدان البيانات.

إذا كان معالج الأخطاء مطلوبًا في نهاية البرنامج ، يمكنك استخدامه

وظيفة waitForLogger (myLogger) {
const loggerDone = وعد جديد ((حل ، رفض) => {
myLogger.on ('finish'، () => {return resolution ()})
myLogger.on ('error'، err => {رفض الإرجاع (يخطئ)})
})
myLogger.end () ،
عودة المسجل
}
و
waitForLogger (logger) .then (() => {console.log ('all done!')}). catch ((err) => {console.error (err)})؛

ما زلت لا أستطيع الحصول على هذا للعمل. أنا أستخدم winston 3.1.0 وهذا النقل المرتبط بالنقل المتراكم winston-Slack-webhook-transport الذي يقدم طلب http. بدأ تلقي هذا الخطأ:

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)

تعديل:
لقد لاحظت الآن أنه يجب علي إنشاء المسجل داخل المعالج ، إذا لم أفعل ، فقد تعيد lambda استخدام السياق وتحاول استخدام مثيل المسجل "المنتهي".

أتلقى نفس الخطأ iudelsmann ، فهل من الممكن التحقق مما إذا كان المسجل مغلقًا بالفعل؟

باستخدام AWS API Gateway ، يمكننا تسجيل رسالة أو رسالتين ولكن بعد logger.end () ؛ لا يزال يحاول استخدام نفس المسجل ونحن نحصل عليه
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)

نحن نستخدم تسجيل Papertrail مع winston-syslog.

ها هو الكود.

"const winston = تتطلب ('winston')
تتطلب ('winston-syslog')
const hostname = يتطلب ("os"). hostname ()

خيارات const = {
المضيف: PAPERTRAIL_URL ،
المنفذ: PAPERTRAIL_PORT ،
app_name: PAPERTRAIL_LOG_CHANNEL ،
المضيف المحلي: اسم المضيف
}

const ptTransport = new winston.transports.Syslog (خيارات)
const logger = winston.createLogger ({
النقل: [ptTransport] ،
التنسيق: winston.format.combine (
winston.format.colorize ({
صحيح بالكامل
}) ،
winston.format.simple ()
)
}) `

hdpa ربما يرجع ذلك إلى إعادة استخدام

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

ثم في وظيفتنا ، نطلب فقط getLogger() للحصول على المسجل الذي يعيد فتحه تلقائيًا إذا تم إغلاقه وفي نهاية الوظيفة ، نستخدم closeLogger() للتأكد من أنه يمسح جميع السجلات إلى cloudwatch

تم تنفيذ التغييرات المذكورة أعلاه ولكن لا تزال تواجه نفس المشكلة. لقد قمت بطباعة كائن المسجل في وحدة التحكم ، وفيما يلي الرد على الطلبين الأول والثاني ،

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

خطأ: اكتب بعد النهاية في writeAfterEnd (/var/task/node_modules/winston/node_modules/readable-stream/lib/_stream_writable.js:257:12) في DerivedLogger.Writable.write (/ var / task / node_modules / winston / node_modules /readable-stream/lib/_stream_writable.js:301:21) في DerivedLogger. (وظيفة مجهولة) [كخطأ] (/ var / مهمة / node_mod

يبدو أنه تتم إزالة نقل Syslog بعد طريقة النهاية وهذا هو المكان الذي نتلقى فيه خطأ.

ننسى رسالتي ، يبدو أنه في الطلب الأول يعمل ، وفي الطلب الثاني ، هناك شيء فقط مهلات سيكون من الجيد الانتظار حتى يتم مسحه بدلاً من إغلاقه

أوه ، المشكلة أعلاه هي أنني أحاول إغلاق مسجل مغلق بالفعل ، لذا فإن الوعد لا يحل أبدًا ، فهذا يعمل على إصلاحه

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

تشير المشكلة رقم 1081 إلى هذا الموضوع ، ولكن لا يوجد حل حقيقي لسجلات Cloudwatch تجريد معرّف الطلب باستخدام عمليات نقل المسجل. الطريقة الوحيدة التي تمكنت من تضمين معرف الطلب بها هي كما يلي باستخدام كائن Lambda context -

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

ومع ذلك ، لا تعمل هذه الطريقة إذا كان Logger.js الخاص بك خارج معالج Lambda الخاص بك على سبيل المثال في طبقة Lambda. على ما يبدو ، لا يمكن الوصول إلى السياق من الواردات ، لذلك إذا كان لديك تعريف المسجل الخاص بك مضمنًا في طبقة وقمت باستيراده في Lambda (s) ، فسيتعين عليك حقن defaultMeta إلى logger الكائن

ملاحظة : أشعر أن تعريف المسجل بواسطة وظيفة Lambda الفردية هو فكرة سيئة ، وبالتالي فإن استخدام الطبقات.

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() يعمل فقط لاستدعاء الوظيفة الأول. المكالمة الثانية تؤدي إلى خطأ:

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

أستخدم winston كديكور لمعالج 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)

أي أفكار كيفية اصلاحها؟

أعتقد أن هذه المسألة يجب أن تولى الأولوية الأولى.
للمعالجة برمز مثل:

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

سيؤدي ذلك إلى تعليق العملية إلى الأبد وعدم القدرة على إعادة التشغيل بأمان.

هل سيتم إصلاح هذه المشكلة أم أنها ستظل مغلقة فقط؟

شرح حل @

ملاحظة واحدة أنه نادى في وقت سابق في الموضوع الذي سأعيد تكراره. سوف تحتاج إلى إنشاء المسجل الخاص بك وكائنات النقل في بداية معالج lambda. إذا حاولت إعادة استخدامها ، فستحصل على أخطاء حيث سيتم إغلاق تدفق النقل عند تجميد بيئة تنفيذ lambda.

طريقة أبسط أحب نفسي حقًا:

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

ثم في لامدا الخاص بك يمكنك مرة أخرى:

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

هناك وثائق تدور حول أنه يمكنك استدعاء end () على مسجل Winston للتدفق ، لكنها لا تقول أن end () هي نفسها استدعاء close (). عليك فقط أن تعرف. لذلك ، لا توجد طريقة لمسح المسجل والاستمرار في استخدامه ، وهي حالة استخدام لـ JavaScript AWS lambdas.

هناك خيار تدفق قابل لإعادة الاستخدام لنقل WinstonCloudWatch: kthxbye. ابحث عن kthxbye لمزيد من المعلومات.

إذا كنت تريد كتابة عملية عامة على (معالج "الخروج" الذي يغلق مسجل Winston ، فإليك ما تحتاج إلى القيام به.

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

يمكن أن يتسبب إجراء إغلاق أجهزة تسجيل الحطاب في إرسال استثناءات غير معالجة (بما في ذلك تلك التي تم إلقاؤها قبل تنفيذ الكود أعلاه) إلى وسائل النقل (خاصة WinstonCloudWatch) لذلك تحتاج إلى الاتصال بـ kthxbye مرة أخرى بعد انتهاء الكود أعلاه. نعم. حقا. نأمل أن يتم مسح عمليات النقل الأخرى مثل الملف في هذا السيناريو - لقد اختبرت هذا ويبدو أنها تم مسحها ، ربما لأن nodeJS يتدفق مع مقابض الملفات عند الخروج (والذي قد لا يكون قريبًا في بعض الحالات).

لذلك ، لا يزال Winston 3 لا يحتوي على عملية تدفق تعمل مع جميع وسائل النقل.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات