You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 02:15:37