Winston: методы ведения журнала больше не принимают обратный вызов, не могут надежно использовать его в некоторых средах (AWS Lambda)

Созданный на 29 мар. 2018  ·  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, а ожидает выхода вашей функции-обработчика. Любые асинхронные процессы выполняются вне функции обработчика и, таким образом, могут быть заморожены вместе с остальной частью контекста выполнения лямбда, если они не ожидаются.

Теперь давайте посмотрим на предлагаемое решение для ожидания 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 должен закрываться перед обработчиком лямбда-выражения, сохраняя эти процессы от замораживания (при условии событие 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. Writeable потоки выставить в .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, используя 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 для выполнения интерфейса Writeable в Node.js. Он получает callback и передает его методу log .
  2. Console transport в 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 передается по конвейеру для всех транспортов, когда логгер закрыт, я понимал, что он не будет генерировать финиш, пока не будут выполнены все его целевые каналы, но я могу ошибаться в этом.

Собираюсь проверить с некоторыми людьми.

Благодаря некоторому вкладу от @mcollina, @davidmarkclements и @mafintosh я узнал, что мое понимание семантики pipe было ошибочным.

  • События 'error' и 'finish' не передаются обратно по конвейерной цепочке.
  • События 'error' вызывают отключение трубы.

Собираюсь изучить, как либо распространить эти события обратно, либо предоставить явный метод для Logger который реализует технику, которую вы сейчас используете @dpraul (возможно, с использованием конца потока ).

@natesilva собирается оставить эту проблему открытой до тех пор, пока не будет

  • Слушайте события 'finish' на всех транспортах
  • Звоните logger.end()
  • Когда все события 'finish' отправлены, вы закончите регистрацию.

Важные модули, которые могут пригодиться: pump и end-of-stream.

@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 (см .: PR-код )

@natesilva @dpraul 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 module и _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') {

Добавьте настраиваемый транспорт и проверьте уровень журнала и добавьте настраиваемую логику на соответствующем уровне. Это не эквивалент выполнения чего-либо в обратном вызове. Но, однако, если вы хотите настроить и отправить журналы в другое место, например, в аэродинамический тормоз, это поможет.

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 (...), включите свой CustomTransport в конфигурацию в массив опции «транспортов».

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

Здравствуйте, я использовал рекомендованный ниже код, но он не завершает запись файлов до закрытия процесса. Я вижу ошибки, написанные в консоли. Если я не вызываю process.exit, файлы записываются правильно. Я вижу, что проблема закрыта, но была ли она решена?

logger.log ('информация', 'какое-то сообщение');
logger.on ('закончить', () => process.exit ());
logger.end ();

У кого-нибудь это работает правильно? Любые идеи?

@tsaockham
У меня есть решение :)

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

После 2 часов поиска решения я реализовал это, даже функция waitForLogger от @dpraul не сработала, я закончил.

Я продолжаю получать предупреждения в этой теме, так что я поделюсь, почему я думаю, что это все еще не работает для многих людей. Предыстория: нашим вариантом использования (и контекстом, в котором была открыта эта проблема) является AWS Lambda, поэтому то, о чем я говорю здесь, применимо только к нему.

Lambda запускает Node в контексте выполнения Lambda . Соответствующая информация:

После выполнения функции Lambda AWS Lambda в течение некоторого времени поддерживает контекст выполнения в ожидании вызова другой функции Lambda.

а также

  • Любые объявления в вашем коде функции Lambda (вне кода обработчика, см. Модель программирования ) остаются инициализированными, обеспечивая дополнительную оптимизацию при повторном вызове функции.

т.е. для ускорения запусков Lambda «замораживает» и «оттаивает» среды вместо того, чтобы полностью их отключать. Для этого Lambda не ждет выхода Node, а ожидает выхода вашей функции-обработчика. Любые асинхронные процессы выполняются вне функции обработчика и, таким образом, могут быть заморожены вместе с остальной частью контекста выполнения лямбда, если они не ожидаются.

Теперь давайте посмотрим на предлагаемое решение для ожидания 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 должен закрываться перед обработчиком лямбда-выражения, сохраняя эти процессы от замораживания (при условии событие 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 с модулем sqlTransport. Для этого я адаптировал sqlTransport для Winston3 согласно docs / transports.md.
Winston3 привел к зависанию программы в конце.
Я экспериментировал с различными предложениями из этого номера и пришел к следующему выводу:
В модуле sqlTransport должен быть реализован метод close (), который правильно закрывает модуль (т.е. клиент внутри).
Примечание: не используйте метод end ()! - Я правда не знаю почему.
Теперь logger.end () достаточно, чтобы правильно закрыть программу.
Logger.close (), что вызывает немедленное прекращение работы регистратора и потерю данных.

Если в конце программы требуется обработчик ошибок, вы можете использовать

function waitForLogger (myLogger) {
const loggerDone = new Promise ((разрешить, отклонить) => {
myLogger.on ('финиш', () => {return resolve ()})
myLogger.on ('ошибка', err => {return reject (err)})
})
myLogger.end ();
return loggerDone;
}
а также
waitForLogger (регистратор) .then (() => {console.log ('все готово!')}). catch ((err) => {console.error (err)});

Я все еще не могу заставить это работать. Я использую Winston 3.1.0 и этот slack-транспорт 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)

РЕДАКТИРОВАТЬ:
Я заметил, что теперь я должен создать регистратор внутри обработчика, если я этого не сделаю, лямбда может повторно использовать контекст и попытаться использовать "завершенный" экземпляр регистратора.

Я получаю ту же ошибку, что и @iudelsmann , можно ли проверить, закрыт ли уже регистратор?

С помощью AWS API Gateway мы можем регистрировать 1-2 сообщения, но после 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 = require ('винстон')
требуется ('Winston-syslog')
const hostname = require ("os"). hostname ()

const options = {
хост: PAPERTRAIL_URL,
порт: PAPERTRAIL_PORT,
имя_приложения: PAPERTRAIL_LOG_CHANNEL,
localhost: имя хоста
}

const ptTransport = новый 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 / task / node_mod

Похоже, транспорт системного журнала удаляется после метода завершения, и здесь мы получаем ошибку.

Забудьте мой пост, кажется, что по первому запросу он работает, по второму что-то просто таймауты 😕 было бы хорошо просто подождать, пока он очистится, вместо того, чтобы закрывать его

О, проблема в том, что я пытаюсь закрыть уже закрытый регистратор, поэтому обещание никогда не разрешается, это исправляет

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, удаляющих идентификатор запроса с помощью транспортов регистратора. Единственный способ, которым я смог включить requestID, - это, как показано ниже, с использованием объекта 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 Layer. По-видимому, контекст недоступен из импорта, поэтому, если у вас есть определение Logger, включенное в слой, и вы импортируете его в свои лямбды, вам придется ввести defaultMeta в logger объект внутри функции-обработчика.

PS : Я считаю, что определение 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_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);
    });
});

Это приведет к зависанию процесса и невозможности его корректного перезапуска.

Будет ли эта проблема устранена или она останется закрытой?

Решение @dpraul здесь (https://github.com/winstonjs/winston/issues/1250#issuecomment-452128291) очень хорошо объясняет проблему и обходной путь. Это сработало для меня в лямбде AWS.

Одно замечание, которое он вызвал ранее в цепочке, которую я повторю. Вам нужно будет создать свой регистратор и транспортные объекты в начале обработчика лямбда. Если вы попытаетесь использовать их повторно, вы получите ошибки, поскольку транспортный поток будет закрыт, когда среда выполнения лямбда будет заморожена.

Более простой способ, который мне очень нравится:

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.

Для транспорта WinstonCloudWatch существует возможность многократного использования: kthxbye. Найдите kthxbye для получения дополнительной информации.

Если вы хотите написать общий обработчик process.on ('exit', закрывающий регистратор 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 рейтинги