Winston: Punya MaxListenersExceededWarning saat menggunakan winston.

Dibuat pada 26 Mei 2018  ·  22Komentar  ·  Sumber: winstonjs/winston

Saya mendapat pesan peringatan saat menggunakan [email protected] setelah memanggil fungsi createLogger beberapa kali dalam kasus pengujian saya.

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

Komentar yang paling membantu

Masalah masih ada untuk DailyRotateFile. Kode di atas cukup untuk mereproduksi masalah.

Semua 22 komentar

Bisakah Anda memberikan contoh tentang cara mereproduksi terbitan ini di master ?

FWIW Saya juga melihat ini di file stress test pada master dengan npm run test , sesuatu seperti
(node:32041) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added. Use emitter.setMaxListeners() to increase limit
Belum yakin apa yang sebenarnya terjadi, tetapi alangkah baiknya jika kita membahas ini ...

Saya menemukan itu akan terjadi jika menggunakan contoh transport di createLogger lebih dari mungkin 10 kali, misalnya:

const winston = require( 'winston' );

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

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

Setelah menjalankan kode di atas, saya mendapatkan hasil ini:

$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 jangan gunakan kembali transport jika Anda melakukannya.

Tetapi dalam proyek saya, saya tidak berpikir saya melakukan hal seperti ini, jadi saya masih mencoba mencari tahu apa yang membuat masalah ini di kode saya.

Hal ini hampir pasti disebabkan oleh jumlah peristiwa log yang menunggu buffer streaming menguras melebihi batas maks default untuk pendengar peristiwa. ( Lihat dokumentasi Node.js di sini )

Di winston2 ada kode yang menggunakan fungsi "setMaxListeners ()" ke Infinity untuk transportasi file, kita harus mempertimbangkan untuk meningkatkan batas melewati nilai default 10 untuk aliran yang digunakan dalam transportasi file.

Sunting: Baru saja memperhatikan kasus uji yang disebutkan di sini hanya menggunakan transportasi konsol tetapi masalah yang sama dapat terjadi dalam transportasi file. @LvChengbin Apakah menggunakan transportasi file dalam pengujian Anda saat melihat ini?

@ChrisAlderson Saya melihat apa yang Anda lakukan dengan cabang fix / gh-1334 Anda tetapi saya memiliki beberapa kekhawatiran tentang solusi itu. Sekali lagi mengacu pada dokumentasi Node.js tentang aliran . Setelah panggilan ke .write mengembalikan nilai false, kita harus mencegah penulisan lebih lanjut agar tidak terjadi hingga buffer selesai dengan sendirinya karena sistem operasi menerima datanya. Dalam solusi Anda, Anda menyiapkan acara satu kali untuk mendengarkan acara pembuangan tetapi kemudian segera dan secara paksa memancarkan acara pembuangan. Saya tidak dapat melihat bagaimana ini akan menjadi cara yang dimaksudkan untuk menggunakan aliran node dan saya khawatir sementara itu memungkinkan kasus uji saat ini berlalu, kami kemudian akan menulis ke buffer aliran sebelum benar-benar dikeringkan dan membuat cadangan lebih lanjut.

Terima kasih @mempf untuk wawasannya. Kami sampai pada beberapa kesimpulan serupa mengobrol di saluran gitter kami.

Jika kita hanya melakukan sesuatu seperti ini https://github.com/winstonjs/winston/compare/master...DABH : no-max-listener? Expand = 1, tampaknya akan menonaktifkan peringatan tersebut. Tapi saya bertanya-tanya apakah kita menutupi bug dalam kasus itu? Atau apakah kita harus memperingatkan pengguna tentang potensi penurunan kinerja? dll. Saya pikir kita ingin menghindari misalnya memanggil stream.emit ('drain') karena menurut saya itu adalah tugas stream untuk memancarkan acara itu (kita harus mendengarkannya saja).

"Secara default EventEmitters akan mencetak peringatan jika lebih dari 10 pendengar ditambahkan untuk acara tertentu. Ini adalah default berguna yang membantu menemukan kebocoran memori. Jelas, tidak semua acara harus dibatasi hanya untuk 10 pendengar."

Jadi bagi saya ini berarti kita memiliki setidaknya 10 pesan log yang berpotensi menunggu streaming buffer habis ketika kita mulai melihat pesan ini. Jadi saya kira pertanyaan sebenarnya adalah berapa banyak pesan log yang harus dibiarkan menunggu buffer habis sebelum peringatan dan kinerja menjadi perhatian. Fakta bahwa kita masuk ke kondisi ini di tempat pertama berarti kita gagal mengikuti penulisan disk level OS (mudah-mudahan hanya sementara). Jadi apa panggilan yang benar di sini? Tidak yakin, tapi mungkin bukan batas acara 10.

Sunting: Dalam pengujian kasus terburuk saya, saya telah menemukan bahwa batas peristiwa 16 tampaknya menjadi tempat peringatan pergi. (Jumlah ini mungkin berbeda dari satu sistem ke sistem dengan sistem yang lebih lambat yang membutuhkan nilai lebih tinggi karena ia memiliki lebih banyak kesulitan untuk mengikutinya.

Kedengarannya masuk akal bagi saya, akan membiarkan @indexzero mempertimbangkan dan melihat apakah hanya menetapkan batas ke sekitar 30 terdengar seperti panggilan yang tepat di sini ...

Hmmm ... sepertinya cukup masuk akal untuk meningkatkan batas, tetapi saya tidak yakin apakah peringatan ini akan tetap ada karena berasal dari simpul itu sendiri iirc.

Peringatan dikeluarkan murni untuk membantu pengembang mengidentifikasi potensi kebocoran memori. Dalam hal ini kami tidak membocorkan memori sebanyak tertinggal di belakang penulisan sistem file tetapi tampaknya menyebabkan maksimum default (di mana ia mulai memperingatkan) terlampaui selama uji tekanan file dan tampaknya mungkin terjadi dalam skenario yang lebih tidak bersalah tetapi dalam pengujian saya yang paling ekstrem (mencoba menulis beberapa GB data log hanya dalam beberapa detik) hanya memasang maksimal 16 pendengar drain.

Penyebab asli dari laporan bug ini bahkan tidak berasal dari pengangkutan file tetapi dari kemungkinan penggunaan ulang pengangkutan dengan cara yang tidak diinginkan. Saya lebih khawatir dengan @DABH yang menyebutkan bahwa dia melihat kesalahan serupa dalam uji tekanan file.

Akhirnya jika kita merujuk kembali ke kode winston2, kita melihat bahwa batas pendengar ini disetel ke Infinity.

Ya, itu hanya efisiensi yang benar-benar seperti catatan @mempf . Jika transport cukup tertinggal maka itu membuat "terlalu banyak" (> 10) pendengar menunggu event drain untuk diaktifkan sehingga penulisan dapat dilanjutkan. Atau, dalam kasus OP, jika Anda mengatur sekelompok penebang semua berbagi transportasi yang sama, maka itu juga akan membuat (saya kira) beberapa pendengar pada transportasi per logger, yang bertambah (apakah itu penggunaan anti-pola cerita lain - jika Anda membuat banyak logger semua dengan transportasi yang sama, bukankah seharusnya Anda hanya membagikan satu logger tunggal di seluruh kode Anda ...).

Saya telah membuka # 1344 yang meningkatkan batas menjadi 30 dan harus membungkam peringatan ini, setidaknya dalam kasus uji winston. Jika masalah OP tetap ada, mungkin ada transportasi lain yang batasnya harus dilanggar, tetapi saya akan sedikit skeptis melakukannya sesuai pembahasan di atas.

Diperbaiki di # 1344

Masalah masih ada untuk DailyRotateFile. Kode di atas cukup untuk mereproduksi masalah.

Cuplikan di atas tempat sekelompok transportasi dibuat? Jika ya, mengapa Anda membuat begitu banyak transportasi? Akan sangat bagus untuk memahami kasus penggunaan Anda dengan lebih baik, mungkin ada pola penggunaan yang lebih baik. Hal pendengar maksimal hanyalah sebuah peringatan, jadi seharusnya tidak merusak apa pun, tetapi kinerja dapat menurun dengan sekelompok pendengar (misalnya banyak transportasi).

Ya. Kasus saya: Saya mencoba memberi label pesan dari modul yang berbeda dengan winston 3.0, mis
[DB] Connected ok , [Main] Server started ok .
Jadi yang ingin saya lakukan, adalah panggilan sederhana di atas file, seperti ini: const logger = createNamedLogger('Main'); , di mana createNamedLogger adalah pembungkus saya untuk membuat logger dengan konsol berlabel dan File transports.

Saya mencoba menemukan cara mudah untuk melakukan hal yang sepele, tetapi saya tidak menemukannya di dokumen.

Menariknya, pengangkutan Konsol tidak menyebabkan kesalahan seperti itu, hanya File. Saya tidak membandingkan kode sumber dari kedua transport, tetapi sepertinya ada bug potensial.

Ya, pengangkutan Konsol tidak terlalu rumit dan memiliki lebih sedikit pemancar / pendengar acara.

Desain yang lebih baik (lebih efisien) untuk kasus penggunaan Anda adalah dengan menggunakan logger + transport tunggal plus pemformat kustom, seperti

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

Agak canggung untuk menyampaikan argumen ke pemformat pada waktu log, tetapi itu adalah salah satu solusi potensial (catatan: belum teruji, mungkin ada kesalahan sintaks, dll.!). Tetapi poin keseluruhannya adalah Anda mungkin hanya membutuhkan satu Logger , dan mungkin hanya satu Transport per tujuan logging (file, konsol, dll.).

@DABH , terima kasih atas teladan Anda. Itu mendorong saya untuk menggabungkan beberapa solusi saya sendiri dan solusi Anda dan untuk mendapatkan hasil yang saya butuhkan. Izinkan saya menunjukkan bagaimana saya melakukannya, saya pikir beberapa ide itu dapat dimasukkan dalam modul winston atau winston karena sangat umum bagi pengguna.

Tujuan:

  1. Izinkan> 1 argumen untuk semua metode logging
  2. Pemformat yang akan mencetak tumpukan penuh Kesalahan jenis apa pun
  3. Pembungkus untuk pelabelan (edisi pertama kami)
  4. Warnai hanya Level dalam pesan

Semua hal di atas harus bekerja sama. Inilah realisasi saya saat ini:

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

Ada beberapa hal yang perlu diperbaiki di masa mendatang (dan menghapus hardcode), tentunya.

Hai.

Saya juga mengalami masalah dengan aplikasi saya. Saya mulai mendapatkan peringatan ini Possible EventEmitter memory leak detected. 16 unpipe listeners added. Use emitter.setMaxListeners() to increase limit . Setelah menginstal modul ini max-listeners-exceeded-warning , saya menemukan ada yang salah dengan winston . Setelah mencari perbaikan, saya menemukan masalah ini dan solusi dari @DABH membantu saya menyingkirkan peringatan tersebut.

Kami menggunakan transportasi Console dengan cara seperti ini:

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

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

Setelah menghapus const transports = [new winston.transports.Console()]; dan memasukkannya langsung ke transports , peringatan itu hilang. Sekarang saya melakukannya dengan cara ini:

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

@DABH , terima kasih atas teladan Anda. Itu mendorong saya untuk menggabungkan beberapa solusi saya sendiri dan solusi Anda dan untuk mendapatkan hasil yang saya butuhkan. Izinkan saya menunjukkan bagaimana saya melakukannya, saya pikir beberapa ide itu dapat dimasukkan dalam modul winston atau winston karena sangat umum bagi pengguna.

Tujuan:

  1. Izinkan> 1 argumen untuk semua metode logging
  2. Pemformat yang akan mencetak tumpukan penuh Kesalahan jenis apa pun
  3. Pembungkus untuk pelabelan (edisi pertama kami)
  4. Warnai hanya Level dalam pesan

Semua hal di atas harus bekerja sama. Inilah realisasi saya saat ini:

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

Ada beberapa hal yang perlu diperbaiki di masa mendatang (dan menghapus hardcode), tentunya.

lihat inti saya yang diperbarui di bawah ini ...
https://gist.github.com/radiumrasheed/9dafdadabd1674b8f9ea967acfbd3947

Saya memiliki masalah yang sama, itu diperbaiki dengan menelepon winstonLoggerInstance.clear() yang menghapus semua transportasi

Saya memiliki masalah yang sama, dan itu karena saya menggunakan ts-node-dev untuk menjalankan aplikasi node TypeScript di komputer lokal saya. Saat membangun aplikasi TS dan menjalankan node ./dist selanjutnya, Winston tidak akan crash.

Saya memiliki masalah yang sama, saya menyelesaikannya menggunakan metode https://github.com/winstonjs/winston/issues/1334#issuecomment -399634717 (dengan Proxy) seperti ini:

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

Sekarang saya bisa menelepon seperti ini:

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

Menghasilkan:

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

tanpa MaxListenersExceededWarning 😄

Apakah halaman ini membantu?
0 / 5 - 0 peringkat