winston: Got MaxListenersExceededWarning while using winston.

I got a warning message while using winston@3.0.0-rc5 after calling the createLogger function multiple times in my test cases.

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

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 23 (12 by maintainers)

Commits related to this issue

Most upvoted comments

Yep. My case: I’m trying to label messages from different modules with winston 3.0, e.g. [DB] Connected ok, [Main] Server started ok. So what i want to do, is simple call on the top of file, like this: const logger = createNamedLogger('Main');, where createNamedLogger is my wrapper to create logger with labeled Console and File transports.

I tried to find easy way to do such trivial thing, but i did not found it in docs.

Problem still exists for DailyRotateFile. Code above is enough to reproduce issue.

The creation of child loggers can (with current winston version) easily be achieved with … child loggers, e.g. to overwrite the label in child loggers - based on the code from @trykers

const subLogger = (label: string = 'APP') => logger.child({label});

Hi.

I was also having problems with my app. I started to get this warnings Possible EventEmitter memory leak detected. 16 unpipe listeners added. Use emitter.setMaxListeners() to increase limit. After installing this module max-listeners-exceeded-warning, I found out it was something wrong with winston. After searching for the fix, I found this issue and solution from @DABH helped me get rid of the warning.

We were using Console transport in such way:

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

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

After removing const transports = [new winston.transports.Console()]; and putting it directly into transports, the warnings were gone. Now I do it this way:

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

@DABH, thank you for your example. It pushed me to combine few solutions of my own and yours and to get result i need. Let me show how i did it, i think some of that ideas can be included in winston or winston modules because they are very common for users.

Goals:

  1. Allow >1 arguments to all logging methods
  2. Formatter that will print full stack of Error of any type
  3. Wrapper for labeling (our first issue)
  4. Colorize only Level in message

All above should work together. Here is my current realization:


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

There are few things to polish in future (and remove hardcode), of course .

@DABH, thank you for your example. It pushed me to combine few solutions of my own and yours and to get result i need. Let me show how i did it, i think some of that ideas can be included in winston or winston modules because they are very common for users.

Goals:

  1. Allow >1 arguments to all logging methods
  2. Formatter that will print full stack of Error of any type
  3. Wrapper for labeling (our first issue)
  4. Colorize only Level in message

All above should work together. Here is my current realization:

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

There are few things to polish in future (and remove hardcode), of course .

see my updated gist below… https://gist.github.com/radiumrasheed/9dafdadabd1674b8f9ea967acfbd3947

Yeah, the Console transport is less complex and has fewer event emitters/listeners.

A better (more efficient) design for your use case is to use a singleton logger+transport plus a custom formatter, something like

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

It is slightly awkward to pass arguments to formatters at log-time, but that is one potential solution (note: untested, there may be syntax errors etc.!). But the overall point is that you probably only need one Logger, and probably only one Transport per logging destination (file, console, etc.).

I found it would happen if using an instance of transport in createLogger more than maybe 10 times, for example:

const winston = require( 'winston' );

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

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

After running the code above, I got this result:

$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 don’t reuse transport instance if you are doing that.

But in my project, I don’t think I did anything like this, so I am still trying to find out what makes this issue in my code.