Node.js使用Pino日志触发EventEmitter内存泄漏警告的解决方法
使用Pino日志时避免EventEmitter内存泄漏的方案及日志实践建议
问题场景
在项目中使用Pino实现日志功能,核心代码如下:
Logger.js 代码
require('dotenv').config(); const pino = require('pino'); const logger = ( name, level = (process.env.LOG_LEVEL || 'info'), file = process.env.LOG_FILE || './log.log', ) => pino({ name, level, transport: { targets: [ { // 输出到控制台 target: 'pino-pretty', level, options: { colorize: true, translateTime: true, sync: true, }, }, { // 输出到文件 target: 'pino-pretty', level, options: { colorize: false, translateTime: true, sync: true, destination: file, }, }, ], }, }); module.exports = logger;
类中引入日志实例示例
const logger = require('./Logger')('User'); class testClass1 { constructor(a) { this.a = a; } async increase(x) { logger.debug(`x=${x}`); this.a += x; return this.a } } module.exports = testClass1;
项目中使用类的示例
const testClass1 = require('./testClass1'); async function test() { const test1 = new testClass1(2); test1.increase(2); } test();
当项目中使用日志的类数量超过10个时,会触发警告:Possible EventEmitter memory leak detected。
问题原因
每次调用require('./Logger')('User')都会创建一个全新的Pino实例,每个实例都会初始化独立的EventEmitter(因为配置了pino-pretty传输器)。当实例数量超过Node.js默认的EventEmitter监听器阈值(10个)时,就会触发内存泄漏警告。
解决方法
方法1:缓存Logger实例(单例模式)
通过缓存已创建的Logger实例,避免为同一个name重复创建实例:
修改后的Logger.js:
require('dotenv').config(); const pino = require('pino'); // 用Map缓存已创建的logger实例 const loggerCache = new Map(); const logger = ( name, level = process.env.LOG_LEVEL || 'info', file = process.env.LOG_FILE || './log.log', ) => { // 如果已有对应name的实例,直接返回 if (loggerCache.has(name)) { return loggerCache.get(name); } const newLogger = pino({ name, level, transport: { targets: [ { target: 'pino-pretty', level, options: { colorize: true, translateTime: true, sync: true, }, }, { target: 'pino-pretty', level, options: { colorize: false, translateTime: true, sync: true, destination: file, }, }, ], }, }); loggerCache.set(name, newLogger); return newLogger; }; module.exports = logger;
方法2:使用全局基础Logger + Child Logger
创建一个全局基础Logger实例,各个模块通过child logger添加name标识,所有日志复用同一个基础实例的传输通道:
修改后的Logger.js:
require('dotenv').config(); const pino = require('pino'); // 创建全局基础logger const baseLogger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { targets: [ { target: 'pino-pretty', level: process.env.LOG_LEVEL || 'info', options: { colorize: true, translateTime: true, sync: true, }, }, { target: 'pino-pretty', level: process.env.LOG_LEVEL || 'info', options: { colorize: false, translateTime: true, sync: true, destination: process.env.LOG_FILE || './log.log', }, }, ], }, }); // 导出child logger创建函数 const logger = (name) => baseLogger.child({ name }); module.exports = logger;
类中使用方式保持不变,这种方式性能更优,因为所有child logger共享同一个EventEmitter。
日志记录实践建议
- 复用Logger实例:避免每个模块创建全新Logger,优先使用单例或child logger模式,减少资源占用。
- 生产环境关闭sync模式:
sync: true会阻塞事件循环,影响性能,生产环境建议使用默认的异步写入。 - 优化日志格式化:避免在高频函数中使用字符串模板,比如将
logger.debug(x=${x})改为logger.debug('x=%d', x),Pino会在对应日志级别开启时才格式化字符串,提升性能。 - 生产环境禁用pino-pretty:pino-pretty仅用于开发环境美化日志,生产环境建议使用JSON格式日志,或使用
pino-file等轻量传输工具,降低性能开销。 - 配置日志轮转:使用
pino-rotating-file等工具实现日志自动轮转,避免单个日志文件体积过大。 - 合理设置日志级别:生产环境用
info/warn级别减少日志量,开发环境用debug级别方便调试。
内容的提问来源于stack exchange,提问作者Stanislav Shevchenko
相关产品推荐
相关产品推荐

