一、为什么需要优化JS日志存储?
在Ubuntu服务器上运行Node.js应用时,日志文件会快速增长,占用大量磁盘空间。如果不加以优化,可能导致:
- 磁盘空间耗尽,服务崩溃
- 日志查询效率低下
- 运维成本增加
- 难以进行日志分析
优化目标:
– 减少磁盘占用
– 提高日志查询效率
– 实现自动轮转和清理
– 保证日志完整性
二、Node.js日志基础配置
2.1 使用Winston日志库
// app.js
const winston = require('winston');
const path = require('path');
// 日志目录
const logDir = '/var/log/nodejs';
if (!require('fs').existsSync(logDir)) {
require('fs').mkdirSync(logDir, { recursive: true });
}
// 配置日志格式
const logFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
// 创建logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
transports: [
// 错误日志单独存储
new winston.transports.File({
filename: path.join(logDir, 'error.log'),
level: 'error',
maxsize: 10485760, // 10MB
maxFiles: 5
}),
// 综合日志
new winston.transports.File({
filename: path.join(logDir, 'combined.log'),
maxsize: 10485760,
maxFiles: 5
})
]
});
// 开发环境输出到控制台
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
module.exports = logger;
2.2 使用Morgan记录HTTP请求
const express = require('express');
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
const app = express();
// 创建日志流
const accessLogStream = fs.createWriteStream(
path.join('/var/log/nodejs', 'access.log'),
{ flags: 'a' }
);
// 使用morgan中间件
app.use(morgan('combined', { stream: accessLogStream }));
// 开发环境输出到控制台
if (process.env.NODE_ENV !== 'production') {
app.use(morgan('dev'));
}
app.get('/', (req, res) => {
logger.info('Homepage accessed');
res.send('Hello World');
});
app.listen(3000, () => {
logger.info('Server started on port 3000');
});
三、配置logrotate自动轮转
3.1 创建logrotate配置文件
sudo nano /etc/logrotate.d/nodejs
添加以下内容:
/var/log/nodejs/*.log {
daily # 每天轮转
missingok # 忽略缺失文件
rotate 14 # 保留14个备份
compress # 压缩旧日志
delaycompress # 延迟压缩(下次轮转时压缩)
notifempty # 不轮转空文件
create 0640 nodejs nodejs # 新文件权限
sharedscripts # 脚本只执行一次
postrotate
# 发送SIGUSR2信号给Node.js进程,重新打开日志文件
kill -SIGUSR2 $(cat /var/run/nodejs.pid)
endscript
}
3.2 测试logrotate配置
# 测试配置(不实际执行)
sudo logrotate -d /etc/logrotate.d/nodejs
# 强制轮转(测试用)
sudo logrotate -f /etc/logrotate.d/nodejs
# 查看轮转后的文件
ls -lh /var/log/nodejs/
3.3 按大小轮转
如果日志增长很快,可以按大小轮转:
/var/log/nodejs/*.log {
size 100M # 超过100MB时轮转
missingok
rotate 7
compress
delaycompress
notifempty
create 0640 nodejs nodejs
postrotate
kill -SIGUSR2 $(cat /var/run/nodejs.pid)
endscript
}
四、日志压缩与归档
4.1 手动压缩历史日志
# 压缩7天前的日志
find /var/log/nodejs/ -name "*.log.*" -mtime +7 -exec gzip {} \;
# 删除30天前的压缩日志
find /var/log/nodejs/ -name "*.log.*.gz" -mtime +30 -delete
4.2 自动压缩脚本
创建自动压缩脚本:
#!/bin/bash
# /opt/scripts/compress_nodejs_logs.sh
LOG_DIR="/var/log/nodejs"
RETENTION_DAYS=30
echo "[$(date)] 开始压缩Node.js日志..."
# 压缩7天前的日志
find "$LOG_DIR" -name "*.log.*" -mtime +7 ! -name "*.gz" -exec gzip {} \;
# 删除30天前的压缩日志
find "$LOG_DIR" -name "*.log.*.gz" -mtime +$RETENTION_DAYS -delete
# 查看压缩后的空间占用
echo "压缩后的日志目录大小:"
du -sh "$LOG_DIR"
echo "[$(date)] 日志压缩完成"
添加执行权限并配置定时任务:
sudo chmod +x /opt/scripts/compress_nodejs_logs.sh
# 每天凌晨2点执行
sudo crontab -e
0 2 * * * /opt/scripts/compress_nodejs_logs.sh >> /var/log/nodejs/compress.log 2>&1
五、使用Systemd Journal存储日志
5.1 配置Systemd存储Node.js日志
编辑Node.js服务的systemd配置:
sudo nano /etc/systemd/system/nodejs.service
配置内容:
[Unit]
Description=Node.js Application
After=network.target
[Service]
Type=simple
User=nodejs
WorkingDirectory=/opt/app
ExecStart=/usr/bin/node app.js
Restart=on-failure
# 日志配置
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodejs-app
# 环境变量
Environment=NODE_ENV=production
Environment=LOG_LEVEL=info
[Install]
WantedBy=multi-user.target
5.2 查看Journal日志
# 查看Node.js应用日志
sudo journalctl -u nodejs.service -f
# 查看指定时间段的日志
sudo journalctl -u nodejs.service --since "2026-01-01" --until "2026-01-02"
# 按日志级别过滤
sudo journalctl -u nodejs.service -p err
# 导出日志到文件
sudo journalctl -u nodejs.service --since "2026-01-01" > nodejs-logs-2026-01-01.log
5.3 限制Journal日志大小
编辑systemd配置:
sudo nano /etc/systemd/journald.conf
修改以下配置:
[Journal]
Storage=persistent
Compress=yes
Seal=yes
SizeLimit=500M
MaxFileSec=7day
MaxRetentionSec=30day
重启journald服务:
sudo systemctl restart systemd-journald
六、集中化日志管理
6.1 使用ELK Stack (Elasticsearch + Logstash + Kibana)
安装ELK Stack:
# 安装Elasticsearch
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
sudo sh -c 'echo "deb https://artifacts.elastic.co/packages/8.x/apt stable main" > /etc/apt/sources.list.d/elastic-8.x.list'
sudo apt update
sudo apt install elasticsearch logstash kibana
配置Logstash:
sudo nano /etc/logstash/conf.d/nodejs.conf
input {
file {
path => "/var/log/nodejs/combined.log"
start_position => "beginning"
codec => "json"
}
}
filter {
if [level] == "error" {
mutate {
add_tag => ["alert"]
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "nodejs-logs-%{+YYYY.MM.dd}"
}
}
6.2 使用Fluentd + Elasticsearch
安装Fluentd:
curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-focal-td-agent4.sh | sh
配置Fluentd:
# /etc/td-agent/td-agent.conf
<source>
@type tail
path /var/log/nodejs/combined.log
pos_file /var/log/td-agent/nodejs.pos
tag nodejs.log
<parse>
@type json
</parse>
</source>
<match nodejs.**>
@type elasticsearch
host localhost
port 9200
index_name nodejs-logs
type_name _doc
</match>
七、日志存储优化最佳实践
7.1 日志级别管理
// 根据环境设置日志级别
const getLogLevel = () => {
const env = process.env.NODE_ENV;
if (env === 'production') return 'warn';
if (env === 'test') return 'error';
return 'debug';
};
logger.level = getLogLevel();
7.2 日志采样
对于高流量应用,使用日志采样减少存储压力:
const logger = require('./logger');
// 采样率10%
const shouldLog = () => Math.random() < 0.1;
app.get('/api/data', (req, res) => {
if (shouldLog()) {
logger.info('API /api/data accessed', {
ip: req.ip,
userAgent: req.get('User-Agent')
});
}
res.json({ data: 'test' });
});
7.3 异步日志写入
使用Bunyan等支持异步写入的日志库:
const bunyan = require('bunyan');
const RotatingFileStream = require('bunyan-rotating-file-stream');
const logger = bunyan.createLogger({
name: 'app',
streams: [{
type: 'rotating-file',
path: '/var/log/nodejs/app.log',
period: '1d',
count: 7,
level: 'info'
}, {
type: 'rotating-file',
path: '/var/log/nodejs/error.log',
period: '1d',
count: 7,
level: 'error'
}]
});
八、监控与告警
8.1 磁盘空间监控
#!/bin/bash
# /opt/scripts/check_log_disk.sh
LOG_DIR="/var/log/nodejs"
THRESHOLD=80
USAGE=$(df -h "$LOG_DIR" | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $USAGE -gt $THRESHOLD ]; then
echo "警告:日志目录磁盘使用率${USAGE}%,超过${THRESHOLD}%" | \
mail -s "Node.js日志磁盘告警" admin@example.com
fi
配置定时检查:
# 每小时检查一次
0 * * * * /opt/scripts/check_log_disk.sh
8.2 日志大小告警
// 检查日志文件大小
const checkLogSize = () => {
const fs = require('fs');
const path = require('path');
const logDir = '/var/log/nodejs';
const files = fs.readdirSync(logDir);
files.forEach(file => {
const filePath = path.join(logDir, file);
const stats = fs.statSync(filePath);
const sizeMB = stats.size / (1024 * 1024);
if (sizeMB > 100) {
logger.warn(`日志文件过大: ${file}, 大小: ${sizeMB.toFixed(2)}MB`);
}
});
};
// 每小时检查一次
setInterval(checkLogSize, 60 * 60 * 1000);
九、总结
优化Ubuntu JS日志存储是确保Node.js应用稳定运行的重要环节:
- 使用专业日志库:Winston、Morgan、Bunyan
- 配置logrotate:自动轮转、压缩、清理
- 集中化管理:ELK Stack、Fluentd
- 监控告警:磁盘空间、日志大小
- 最佳实践:日志级别、采样、异步写入
通过这些方法,可以有效控制日志存储成本,提高运维效率。
本文基于Ubuntu 22.04 + Node.js 22.x编写,适用于大多数生产环境。