Winston: Получил MaxListenersExceededWarning при использовании Winston.

Созданный на 26 мая 2018  ·  22Комментарии  ·  Источник: winstonjs/winston

Я получил предупреждающее сообщение при использовании [email protected] после многократного вызова функции createLogger в моих тестовых примерах.

(node:28754) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 end listeners added. Use emitter.setMaxListeners() to increase limit
    at _addListener (events.js:280:19)
    at DerivedLogger.addListener (events.js:297:10)
    at DerivedLogger.Readable.on (_stream_readable.js:772:35)
    at DerivedLogger.once (events.js:341:8)
    at DerivedLogger.Readable.pipe (_stream_readable.js:580:9)
    at DerivedLogger.add (/Users/NS/yk/node_modules/winston/lib/winston/logger.js:299:8)
    at DerivedLogger.<anonymous> (/Users/NS/yk/node_modules/winston/lib/winston/logger.js:82:12)
    at Array.forEach (<anonymous>)
    at DerivedLogger.Logger.configure (/Users/NS/yk/node_modules/winston/lib/winston/logger.js:81:24)
    at DerivedLogger.Logger (/Users/NS/yk/node_modules/winston/lib/winston/logger.js:22:8)
    at new DerivedLogger (/Users/NS/yk/node_modules/winston/lib/winston/create-logger.js:24:44)
    at Object.module.exports [as createLogger] (/Users/NS/yk/node_modules/winston/lib/winston/create-logger.js:58:10)

Самый полезный комментарий

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

Все 22 Комментарий

Не могли бы вы привести пример того, как воспроизвести эту проблему на master ?

FWIW Я также вижу это в стресс-тесте файла на master с npm run test , что-то вроде
(node:32041) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added. Use emitter.setMaxListeners() to increase limit
Еще не совсем уверен, что происходит, но было бы хорошо разобраться в этом ...

Я обнаружил, что это произойдет, если использовать экземпляр transport в createLogger более чем 10 раз, например:

const winston = require( 'winston' );

const transports = [
    new winston.transports.Console(),
];

for( let i = 0, l = 10; i < l; i += 1 ) {
    winston.createLogger( { transports } );
}

После выполнения приведенного выше кода я получил такой результат:

$node winston.js
(node:39048) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 unpipe listeners added. Use emitter.setMaxListeners() to increase limit
(node:39048) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 error listeners added. Use emitter.setMaxListeners() to increase limit

@DABH не используйте повторно экземпляр transport если вы это делаете.

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

Это почти наверняка вызвано тем, что количество событий журнала, ожидающих опустошения буфера потока, превышает установленный по умолчанию максимальный предел для прослушивателей событий. ( См. Документацию по Node.js здесь )

В winston2 был код, который использовал функцию «setMaxListeners ()» до Infinity для передачи файлов, мы должны рассмотреть возможность увеличения лимита переданного значения по умолчанию 10 для потока, используемого в передаче файлов.

Изменить: только что заметил, что упомянутый здесь тестовый пример использует только консольный транспорт, но такая же проблема может возникнуть в файловом транспорте. @LvChengbin Используете ли вы при тестировании файловый транспорт, когда видите это?

@ChrisAlderson Я вижу, что вы делаете с веткой fix / gh-1334, но у меня есть некоторые опасения по поводу этого решения. Еще раз обратимся к документации Node.js по потокам . Как только вызов .write возвращает false, мы должны предотвратить дальнейшую запись до тех пор, пока буфер не очистится сам по себе из-за того, что операционная система принимает данные. В своем решении вы настраиваете одноразовое событие для прослушивания события утечки, но затем немедленно и принудительно генерируете событие утечки. Я не вижу, как это будет предполагаемый способ использования потоков узлов, и я боюсь, что, хотя он позволяет пройти текущие тестовые примеры, мы будем записывать в буфер потока до его фактического осушения и создавать дальнейшую резервную копию.

Спасибо @mempf за понимание. Мы пришли к аналогичным выводам в чате на нашем канале gitter.

Если мы просто сделаем что-то вроде этого https://github.com/winstonjs/winston/compare/master...DABH : no-max-listeners? Expand = 1, это, похоже, заглушит эти предупреждения. Но мне интересно, маскируем ли мы в таком случае ошибку? Или нужно как-то предупредить пользователя о потенциальной деградации производительности? и т. д. Я действительно думаю, что мы хотим избежать, например, вызова stream.emit ('сток'), поскольку я думаю, что это задача потока - генерировать это событие (мы должны просто слушать его).

«По умолчанию EventEmitters будет выводить предупреждение, если для определенного события добавлено более 10 слушателей. Это полезное значение по умолчанию, которое помогает находить утечки памяти. Очевидно, что не все события следует ограничивать только 10 слушателями».

Для меня это означает, что у нас есть как минимум 10 сообщений журнала, которые потенциально ждут, когда буфер потока опустеет, когда мы начнем видеть это сообщение. Итак, я предполагаю, что реальный вопрос заключается в том, сколько сообщений журнала должно ждать, пока буфер не опустеет, прежде чем предупреждения и производительность станут проблемой. Тот факт, что мы попадаем в это состояние, в первую очередь означает, что мы уже не справляемся с записью на диск на уровне ОС (надеюсь, только временно). Итак, какой звонок здесь правильный? Не уверен, но, вероятно, не предел событий в 10.

Изменить: в моем худшем случае тестирования я обнаружил, что предел событий 16, похоже, является тем местом, где исчезают предупреждения. (Это число, вероятно, будет варьироваться от системы к системе с более медленными системами, требующими более высокого значения, потому что им еще труднее поддерживать

Для меня это звучит разумно, позволю @indexzero взвесить и посмотреть, будет ли просто установка лимита примерно на 30

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

Предупреждение создается исключительно для того, чтобы помочь разработчику определить потенциальную утечку памяти. В этом случае мы не столько утекаем память, сколько отстаем от записи файловой системы, но, похоже, это приводит к превышению максимума по умолчанию (где он начинает предупреждать) во время стресс-теста файла и, по-видимому, это возможно в более невинных сценариях но в моем самом экстремальном тестировании (попытка записать несколько ГБ данных журнала всего за несколько секунд) подключалось не более 16 прослушивателей стока.

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

Наконец, если мы вернемся к коду winston2, мы увидим, что эти ограничения прослушивателя были установлены на Infinity.

Да, на самом деле это просто вопрос эффективности, как отмечает @mempf . Если транспорт значительно отстает, он создает "слишком много" (> 10) слушателей, ожидающих срабатывания события утечки, чтобы можно было продолжить запись. Или, в случае OP, если вы настроите кучу регистраторов, все использующих один и тот же транспорт, тогда это также создаст (я думаю) несколько прослушивателей на транспорте для каждого регистратора, которые складываются (независимо от того, является ли это анти-шаблон использования другая история - если вы создаете много регистраторов с одним и тем же транспортом, не следует ли вам просто использовать один одноэлементный регистратор в своем коде ...).

Я открыл # 1344, который увеличивает лимит до 30 и должен отключить эти предупреждения, по крайней мере, в тестовых примерах Winston. Если проблемы с OP не устранены, возможно, есть другой транспорт, на котором следует увеличить лимит, но я бы немного скептически отнесся к этому в соответствии с приведенным выше обсуждением.

Исправлено в # 1344

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

Фрагмент выше, где создается группа транспортов? Если да, то зачем вы создаете столько транспортов? Было бы здорово лучше понять ваш вариант использования, возможно, есть лучший шаблон использования. Максимальное количество слушателей - это просто предупреждение, поэтому оно не должно ничего ломать, но производительность может ухудшиться из-за множества слушателей (например, многих транспортов).

Ага. Мой случай: я пытаюсь пометить сообщения из разных модулей с помощью winston 3.0, например
[DB] Connected ok , [Main] Server started ok .
Итак, что я хочу сделать, это простой вызов в верхней части файла, например: const logger = createNamedLogger('Main'); , где createNamedLogger - это моя оболочка для создания регистратора с помеченными переносами консоли и файлов.

Я пытался найти простой способ сделать такую ​​тривиальную вещь, но не нашел его в документации.

Интересно, что консольный транспорт не вызывает такой ошибки, только File. Я не сравнивал исходный код обоих транспортов, но это похоже на потенциальную ошибку.

Да, консольный транспорт менее сложен и имеет меньше источников / слушателей событий.

Лучший (более эффективный) дизайн для вашего варианта использования - использовать одноэлементный регистратор + транспорт плюс настраиваемое средство форматирования, что-то вроде

// logger.js
export const namedFormatter = (info, opts) => {
  info.message = `[${opts.name}] ${info.message}`; 
  return info;
};

export const globalLogger = winston.createLogger({
  level: 'info',
  format: namedFormatter,
  transports: [
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

export const namedLog = (name) => {
    return (level, message, meta?) => globalLogger.log({name: name, level, message, meta});
};
// DB.js
import namedLog from 'logger.js';

const log = namedLog('DB');

// ...

log('info', 'Connected ok');

Немного неудобно передавать аргументы средствам форматирования во время регистрации, но это одно из возможных решений (примечание: непроверено, могут быть синтаксические ошибки и т. Д.!). Но общая суть в том, что вам, вероятно, понадобится только один Logger и, вероятно, только один Transport каждого места назначения журнала (файл, консоль и т. Д.).

@DABH , спасибо за пример. Это подтолкнуло меня объединить несколько собственных и ваших решений и получить нужный мне результат. Позвольте мне показать, как я это сделал, я думаю, что некоторые из этих идей можно включить в модули winston или winston, потому что они очень распространены среди пользователей.

Цели:

  1. Разрешить> 1 аргумент для всех методов ведения журнала
  2. Средство форматирования, которое будет печатать полный стек ошибок любого типа
  3. Обертка для маркировки (наш первый выпуск)
  4. Только раскрашивание Уровень в сообщении

Все вышеперечисленное должно работать вместе. Вот моя текущая реализация:

const loggerParams = {
  level: process.env.NODE_ENV === 'development' ? 'info' : 'info',
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.timestamp({
          format: 'YYYY-MM-DD HH:mm:ss'
        }),
        winston.format.printf(
          info =>
            `${info.timestamp} [${winston.format
              .colorize()
              .colorize(info.level, info.level.toUpperCase())}]: ${
              info.group ? `[${info.group}]` : ``
            } ${info.message}`
        )
      )
    }),
    new DailyRotateFile({
      filename: config.logFileName,
      dirname: config.logFileDir,
      maxsize: 2097152, //2MB
      maxFiles: 25
    })
  ]
};

const cleverConcatenate = args =>
  args.reduce((accum, current) => {
    if (current && current.stack) {
      return process.env.NODE_ENV === 'development'
        ? `${accum}
        ${current.stack}
        `
        : `${accum} ${current.message}`;
    } else if (current === undefined) {
      return `${accum} undefined`;
    } else {
      return `${accum} ${current.toString()}`;
    }
  }, '');

const proxify = (logger, group) =>
  new Proxy(logger, {
    get(target, propKey) {
      if (
        ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'].indexOf(
          propKey
        ) > -1
      ) {
        return (...args) => {
          if (args.length > 1) {
            args = cleverConcatenate(args);
          }
          return target.log({ group, message: args, level: propKey });
        };
      } else {
        return target[propKey];
      }
    }
  });

const simpleLogger = winston.createLogger(loggerParams);
const logger = proxify(simpleLogger, null);
const createNamedLogger = group => proxify(simpleLogger, group);

export default logger;
export { createNamedLogger };

Конечно, есть несколько вещей, которые нужно отполировать (и удалить жесткий код).

Привет.

У меня также были проблемы с моим приложением. Я начал получать это предупреждение Possible EventEmitter memory leak detected. 16 unpipe listeners added. Use emitter.setMaxListeners() to increase limit . После установки этого модуля max-listeners-exceeded-warning я обнаружил, что что-то не так с winston . После поиска исправления я обнаружил эту проблему, и решение от @DABH помогло мне избавиться от предупреждения.

Мы использовали транспорт Console таким образом:

...
const transports = [new winston.transports.Console()];

function logger(name: string, level?: string): Logger {
    if (!level) {
        level = getLoggingLevel();
    }
    return createLogger({
        format: createLogFormat(name),
        transports,
        level
    });
}
...

После удаления const transports = [new winston.transports.Console()]; и помещения его непосредственно в transports предупреждения исчезли. Теперь делаю так:

...
function logger(name: string, level?: string): Logger {
    if (!level) {
        level = getLoggingLevel();
    }
    return createLogger({
        format: createLogFormat(name),
        transports: [
            new winston.transports.Console()
        ],
        level
    });
}
...

@DABH , спасибо за пример. Это подтолкнуло меня объединить несколько собственных и ваших решений и получить нужный мне результат. Позвольте мне показать, как я это сделал, я думаю, что некоторые из этих идей можно включить в модули winston или winston, потому что они очень распространены среди пользователей.

Цели:

  1. Разрешить> 1 аргумент для всех методов ведения журнала
  2. Средство форматирования, которое будет печатать полный стек ошибок любого типа
  3. Обертка для маркировки (наш первый выпуск)
  4. Только раскрашивание Уровень в сообщении

Все вышеперечисленное должно работать вместе. Вот моя текущая реализация:

const loggerParams = {
  level: process.env.NODE_ENV === 'development' ? 'info' : 'info',
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.timestamp({
          format: 'YYYY-MM-DD HH:mm:ss'
        }),
        winston.format.printf(
          info =>
            `${info.timestamp} [${winston.format
              .colorize()
              .colorize(info.level, info.level.toUpperCase())}]: ${
              info.group ? `[${info.group}]` : ``
            } ${info.message}`
        )
      )
    }),
    new DailyRotateFile({
      filename: config.logFileName,
      dirname: config.logFileDir,
      maxsize: 2097152, //2MB
      maxFiles: 25
    })
  ]
};

const cleverConcatenate = args =>
  args.reduce((accum, current) => {
    if (current && current.stack) {
      return process.env.NODE_ENV === 'development'
        ? `${accum}
        ${current.stack}
        `
        : `${accum} ${current.message}`;
    } else if (current === undefined) {
      return `${accum} undefined`;
    } else {
      return `${accum} ${current.toString()}`;
    }
  }, '');

const proxify = (logger, group) =>
  new Proxy(logger, {
    get(target, propKey) {
      if (
        ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'].indexOf(
          propKey
        ) > -1
      ) {
        return (...args) => {
          if (args.length > 1) {
            args = cleverConcatenate(args);
          }
          return target.log({ group, message: args, level: propKey });
        };
      } else {
        return target[propKey];
      }
    }
  });

const simpleLogger = winston.createLogger(loggerParams);
const logger = proxify(simpleLogger, null);
const createNamedLogger = group => proxify(simpleLogger, group);

export default logger;
export { createNamedLogger };

Конечно, есть несколько вещей, которые нужно отполировать (и удалить жесткий код).

см. мою обновленную суть ниже ...
https://gist.github.com/radiumrasheed/9dafdadabd1674b8f9ea967acfbd3947

У меня была такая же проблема, она была исправлена ​​вызовом winstonLoggerInstance.clear() который очищает все транспорты.

У меня была такая же проблема, потому что я использовал ts-node-dev для запуска приложения узла TypeScript на моем локальном компьютере. При создании приложения TS и последующем запуске node ./dist Winston не аварийно завершит работу.

У меня была такая же проблема, я решил с помощью метода https://github.com/winstonjs/winston/issues/1334#issuecomment -399634717 (с прокси) следующим образом:

const myCustomLevels = {
  levels: {
    error: 0,
    warn: 1,
    info: 2,
    success: 3,
    debug: 4,
    silly: 5
  } as config.AbstractConfigSetLevels,
  colors: {
    error: 'bold red',
    warn: 'bold yellow',
    info: 'bold magenta',
    success: 'bold green',
    debug: 'bold blue',
    silly: 'bold gray'
  } as config.AbstractConfigSetColors
};

interface CustomLevels extends Logger {
  success: LeveledLogMethod;
}

const dailyRotateFileTransport = new (transports.DailyRotateFile)({
  filename: 'logs/application-%DATE%.log',
  datePattern: 'YYYY-MM-DD-HH',
  zippedArchive: true,
  maxSize: '20m',
  maxFiles: '30d'
});

const transportsConfig = [
  new transports.Console({ level: 'silly' }),
  dailyRotateFileTransport
];

const myFormat = printf(({ level, message, label, timestamp, ms, showMs }) => {
  return `${timestamp} [${label}] ${level}: ${message} ${showMs ? `==> (\u001b[33m${ms}\u001b[39m)` : ''}`;
});

const logger = <CustomLevels>createLogger({
  transports: transportsConfig,
  levels: myCustomLevels.levels,
  format: combine(
    timestamp(),
    colorize(),
    ms(),
    myFormat)
});

addColors(myCustomLevels.colors);

const subLogger = (label: string = 'APP', showMs: boolean = false) => new Proxy(logger, {
  get(target, propKey) {
    if (Object.keys(myCustomLevels.levels).includes(String(propKey))) {
      return (...args) => target.log({ label, group: null, message: args.join(' '), level: String(propKey), showMs })
    } else {
      return target[propKey];
    }
  }
});

export { subLogger };

Теперь я могу позвонить так:

import subLogger from './logger';
const log = subLogger(`BDD`, true); // true will ask to show ms()
log.debug('Hello');

Производить :

2020-03-28T18:21:01.955Z [BDD] debug: Hello ==> (+0ms)

без MaxListenersExceededWarning 😄

Была ли эта страница полезной?
0 / 5 - 0 рейтинги