想象一下你负责维护一个日活百万的电商系统,每天产生的日志文件散落在十几台服务器上。某天凌晨两点,客服突然打电话说订单系统异常,这时候你需要快速定位问题。如果面对的是几百个零散的日志文件,光是收集和整理就要花掉半小时,而问题可能在这期间持续恶化。
这就是为什么我们需要自动化日志打包与备份系统。通过Node.js的archiver模块,可以轻松实现:
我曾经在一个支付系统中实现这套方案后,故障排查时间从平均45分钟缩短到5分钟。更棒的是,整个过程完全自动化,再也不用半夜手动下载日志了。
先来看个最简单的例子,把单个日志文件打包成zip:
javascript复制const fs = require('fs');
const archiver = require('archiver');
// 创建输出流
const output = fs.createWriteStream('/var/log/orders/orders-20230701.zip');
const archive = archiver('zip', {
zlib: { level: 9 } // 最高压缩级别
});
// 管道连接
archive.pipe(output);
// 添加文件
archive.file('/var/log/orders/20230701.log', {
name: '20230701.log' // 压缩包内文件名
});
// 完成压缩
archive.finalize();
这里有几个实用技巧:
orders-20230701.zip,方便后续查找javascript复制archive.on('error', (err) => {
console.error('压缩失败:', err);
process.exit(1); // 非零退出码让运维系统感知异常
});
实际项目中更常见的场景是需要打包某个目录下的所有日志文件:
javascript复制const path = require('path');
const fs = require('fs');
// 按日期获取日志文件
function getLogFiles(logDir, date) {
const dateStr = date.toISOString().split('T')[0];
return fs.readdirSync(logDir)
.filter(file => file.includes(dateStr))
.map(file => path.join(logDir, file));
}
// 打包某日所有日志
async function archiveDailyLogs(serviceName, date) {
const logDir = `/var/log/${serviceName}`;
const outputPath = `/backup/${serviceName}-${dateStr}.zip`;
const archive = archiver('zip');
archive.pipe(fs.createWriteStream(outputPath));
getLogFiles(logDir, date).forEach(file => {
archive.file(file, {
name: path.basename(file)
});
});
await archive.finalize();
return outputPath;
}
这个方案有几个优化点:
我曾经在ELK方案上线前用这个脚本临时处理了3TB的散落日志,节省了90%的存储空间。
对于复杂的日志目录结构,比如按日期分层的Nginx日志:
code复制/var/log/nginx/
├── 2023/
│ ├── 07/
│ │ ├── access-01.log
│ │ └── error-01.log
└── current/
可以使用archive.directory()的进阶用法:
javascript复制// 保留原始目录结构
archive.directory('/var/log/nginx/2023/07', 'nginx/2023/07');
// 或者展平结构(所有文件放压缩包根目录)
archive.directory('/var/log/nginx/current', false);
特别实用的一个功能是动态重命名:
javascript复制// 将压缩包内路径从/var/log改为/logs
archive.directory('/var/log', 'logs', {
date: new Date(), // 可以在文件名中加入时间戳
name: (entry) => entry.name.replace('.log', '-backup.log')
});
手动执行打包脚本太原始,我们需要用cron或者系统定时任务来实现自动化:
bash复制# 每天凌晨1点打包前一天的日志
0 1 * * * /usr/bin/node /scripts/archive-logs.js
对应的Node.js脚本可以这样写:
javascript复制// archive-logs.js
const cron = require('node-cron');
const { archiveDailyLogs } = require('./log-utils');
// 每天1点执行
cron.schedule('0 1 * * *', async () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
try {
await archiveDailyLogs('nginx', yesterday);
await archiveDailyLogs('order-service', yesterday);
console.log('日志打包完成');
} catch (err) {
console.error('打包失败:', err);
// 这里可以接入邮件/钉钉告警
}
});
推荐几个生产环境必备的功能:
本地打包只是第一步,关键是要上传到云端实现异地容灾。以阿里云OSS为例:
javascript复制const OSS = require('ali-oss');
async function uploadToOSS(localPath, remotePath) {
const client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: process.env.OSS_KEY,
accessKeySecret: process.env.OSS_SECRET,
bucket: 'my-log-backup'
});
try {
const result = await client.put(remotePath, localPath);
console.log('上传成功:', result.url);
return true;
} catch (err) {
console.error('上传失败:', err);
return false;
}
}
安全建议:
javascript复制// 分片上传示例
const result = await client.multipartUpload(remotePath, localPath, {
parallel: 4, // 并发数
partSize: 1024 * 1024 // 1MB分片
});
一个健壮的日志备份系统应该包含以下组件:
code复制[日志源] → [定时触发器] → [打包服务] → [云存储]
↘_________[监控告警]
具体实现可以参考这个类设计:
javascript复制class LogBackupSystem {
constructor(services, cloudConfig) {
this.services = services; // 需要备份的服务列表
this.cloud = cloudConfig; // 云存储配置
}
async backup(dayOffset = -1) {
const date = new Date();
date.setDate(date.getDate() + dayOffset);
for (const service of this.services) {
const zipPath = await this.archiveServiceLogs(service, date);
await this.uploadToCloud(zipPath);
this.cleanLocal(zipPath); // 上传后删除本地文件
}
}
// 其他私有方法...
}
部署时建议:
当处理GB级别的日志时,可能会遇到这些问题:
常见问题1:内存溢出
javascript复制// 错误示范:一次性读取大文件
archive.append(fs.readFileSync('huge.log'), {name: 'huge.log'});
// 正确做法:使用流
archive.append(fs.createReadStream('huge.log'), {name: 'huge.log'});
常见问题2:压缩速度慢
javascript复制// 适当降低压缩级别
const archive = archiver('zip', {
zlib: { level: 6 } // 平衡压缩率和速度
});
常见问题3:上传超时
javascript复制// 增加超时时间
const client = new OSS({
timeout: 300000 // 5分钟
});
我曾经优化过一个系统,通过以下调整将总处理时间从2小时降到15分钟:
这个方案不仅适用于日志,还可以用于:
数据库备份
javascript复制// MySQL dump + 压缩 + 上传
archive.append(mysqlDumpStream, {name: 'db-backup.sql'});
静态资源归档
javascript复制// 打包用户上传的图片
archive.directory('/uploads/2023/07', 'images');
CI/CD产物保存
javascript复制// 构建产物打包
archive.directory('/dist', false);
关键是要理解archiver的核心能力:将任意数据源转化为压缩包。掌握了这个思路,能玩出很多花样。