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

Node.js仅用core modules实现日志同时输出到console、stdio及文件的方法

实现方案

方案1:轻量封装自定义日志方法(无侵入全局原生方法,推荐)

该方案不需要修改Node.js原生全局对象或流逻辑,仅通过封装独立的日志调用函数实现双端输出,可控性强,不会影响第三方依赖的输出行为。

const fs = require('fs');
const path = require('path');

// 初始化日志文件追加写入流
const logStream = fs.createWriteStream(path.join(__dirname, 'app.log'), { flags: 'a', encoding: 'utf8' });

// 普通日志函数
function log(...args) {
  // 输出到控制台
  console.log(...args);
  // 格式化日志内容后写入文件
  const logContent = `[${new Date().toISOString()}] ${args.map(item => typeof item === 'object' ? JSON.stringify(item, null, 2) : String(item)).join(' ')}\n`;
  logStream.write(logContent);
}

// 错误日志函数
function error(...args) {
  console.error(...args);
  const errContent = `[${new Date().toISOString()}] [ERROR] ${args.map(item => typeof item === 'object' ? JSON.stringify(item, null, 2) : String(item)).join(' ')}\n`;
  logStream.write(errContent);
}

// 使用示例
log('服务启动成功', { port: 3000 });
error('接口请求异常', new Error('连接超时'));

方案2:劫持process.stdout实现全局静默输出(无需修改原有console调用)

如果项目中已经大量使用原生console.log/console.error,不想逐一修改调用方式,可以通过重写标准输出流的write方法实现双端输出,原有代码不需要做任何调整。

const fs = require('fs');
const path = require('path');

// 初始化日志写入流
const logStream = fs.createWriteStream(path.join(__dirname, 'app.log'), { flags: 'a', encoding: 'utf8' });
// 保存原生write方法,避免循环调用
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
const originalStderrWrite = process.stderr.write.bind(process.stderr);

// 重写标准输出流方法
process.stdout.write = (chunk, encoding, callback) => {
  // 先执行原生逻辑输出到控制台
  originalStdoutWrite(chunk, encoding, callback);
  // 同步写入日志文件
  logStream.write(chunk, encoding);
};

// 重写错误输出流方法
process.stderr.write = (chunk, encoding, callback) => {
  originalStderrWrite(chunk, encoding, callback);
  logStream.write(chunk, encoding);
};

// 原有console调用无需修改即可实现双端输出
console.log('启动成功');
console.error('运行异常');

方案2注意事项

  • 进程退出前需要关闭文件流避免日志丢失,可以通过监听系统事件自动处理:
process.on('exit', () => {
  logStream.end();
});
// 监听Ctrl+C退出信号
process.on('SIGINT', () => {
  logStream.end(() => process.exit());
});
  • 若需要区分普通日志和错误日志,可以单独创建error.log对应的写入流,将stderr的内容单独写入该文件。
  • 该方案会拦截所有向stdout/stderr输出的内容,包括第三方依赖的打印内容,不需要时可以添加过滤规则,仅记录带指定前缀的日志。

补充优化点

如果应用日志量较大,可以添加背压处理逻辑:判断logStream.write()的返回值,若返回false则监听drain事件后再继续写入,避免内存溢出,普通轻量应用无需该处理。

内容的提问来源于stack exchange,提问作者Jim Chertkov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 14:27:05