多进程使用Log4js写入同一文件时进程退出的架构解决方案咨询
解决方案
1. 启用Log4js集群模式(推荐)
Log4js原生支持多进程场景的集群Appender,核心逻辑是让主进程单独负责日志文件的写入与滚动,所有子进程仅需将日志发送给主进程,完全避免多进程直接操作同一文件的竞争问题,从根源上解决EPERM错误。
配置示例:
// 主进程配置 const log4js = require('log4js'); log4js.configure({ appenders: { cluster: { type: 'cluster', appender: { type: 'file', filename: 'E:/scripting_logs/myScript.log', maxLogSize: 10485760, // 10MB backups: 5, compress: true // 压缩备份日志,节省磁盘空间 } } }, categories: { default: { appenders: ['cluster'], level: 'info' } } }); // 子进程无需单独配置文件写入,直接调用日志实例 const logger = require('log4js').getLogger(); logger.info('子进程日志内容');
该方案既保证日志统一写入单个文件,方便排查问题,又通过压缩备份缓解磁盘占用压力,完全适配原有滚动需求。
2. 实现文件锁机制,避免多进程并发滚动
如果不想大幅改动现有架构,可以给日志文件加排他锁,确保同一时间只有一个进程能执行日志滚动操作,其他进程等待锁释放后再继续写入。
借助lockfile库实现的示例:
const lockfile = require('lockfile'); const log4js = require('log4js'); function initLogger() { lockfile.lock('E:/scripting_logs/myScript.lock', (err) => { if (err) { // 其他进程已持有锁,延迟重试 setTimeout(initLogger, 1000); return; } log4js.configure({ appenders: { file: { type: 'file', filename: 'E:/scripting_logs/myScript.log', maxLogSize: 10485760, backups: 5, compress: true, // 滚动完成后释放锁 onRollComplete: () => { lockfile.unlock('E:/scripting_logs/myScript.lock', () => {}); } } }, categories: { default: { appenders: ['file'], level: 'info' } } }); const logger = log4js.getLogger(); logger.info('日志初始化完成'); }); } initLogger();
需注意设置合理的锁超时时间,避免出现死锁,该方案适合对现有代码改动较小的场景。
3. 搭建集中式日志收集服务
搭建本地TCP/UDP日志收集服务,所有业务进程将日志以网络请求的方式发送到该服务,由服务统一处理文件写入、滚动和压缩操作,彻底隔离业务进程与日志文件的直接交互。
简易TCP日志服务示例:
// 日志服务进程(单独运行) const net = require('net'); const log4js = require('log4js'); log4js.configure({ appenders: { file: { type: 'file', filename: 'E:/scripting_logs/myScript.log', maxLogSize: 10485760, backups: 5, compress: true } }, categories: { default: { appenders: ['file'], level: 'info' } } }); const logger = log4js.getLogger(); const server = net.createServer((socket) => { socket.on('data', (data) => { const logContent = data.toString().trim(); logger.info(logContent); }); }); server.listen(3000, 'localhost', () => { console.log('日志服务启动,监听3000端口'); }); // 业务进程代码:发送日志到服务 const net = require('net'); const client = new net.Socket(); client.connect(3000, 'localhost', () => { client.write(`[进程${process.pid}] 业务日志内容\n`); });
该方案不仅解决文件竞争问题,还支持后续扩展到分布式日志系统(如ELK),适合长期维护的项目。
4. 优化独立日志文件策略(备选)
若必须使用独立日志文件,可通过以下方式解决空间与排查问题:
- 按时间+进程ID命名日志文件,例如
myScript_${process.pid}_20240520.log,排查时可根据进程ID快速定位目标文件。 - 配置自动清理规则,例如保留最近30天的日志,或当磁盘占用超过80%时自动删除最旧的日志文件。
- 开启Log4js的
compress: true配置,自动压缩备份日志,大幅减少磁盘占用。
内容的提问来源于stack exchange,提问作者Michael Sorensen
相关产品推荐
相关产品推荐

