1. 项目概述
在Linux服务器运维工作中,日志监控是日常维护的重要环节。传统通过SSH连接服务器使用tail -f命令查看日志的方式存在诸多不便:需要保持终端连接、无法多人同时查看、移动端访问困难等。本文将分享一套基于Ubuntu系统、PM2进程管理和WebSocket技术的实时日志监控方案,能够将nohup.out日志实时推送到网页端展示。
这套方案的核心价值在于:
- 实现浏览器随时随地访问日志
- 支持多人同时查看同一份日志
- 保留完整历史日志记录
- 提供近乎实时的日志更新(延迟≤100ms)
- 通过PM2保障监控服务的稳定性
2. 环境准备与前期检查
2.1 系统环境要求
本方案已在以下环境验证通过:
- Ubuntu 20.04/22.04 LTS
- Node.js v14+
- npm v6+
- PM2 v5+
对于其他Linux发行版(如CentOS),主要区别在于防火墙配置命令,核心功能完全兼容。
2.2 日志文件确认
在开始前,必须确认以下几点:
- 你的应用确实使用
nohup启动并输出日志到nohup.out - 知道
nohup.out的准确路径 - 日志文件有实际内容输出
可以通过以下命令验证:
bash复制# 查看日志文件是否存在
ls -l /path/to/your/nohup.out
# 查看日志最后10行内容
tail -10 /path/to/your/nohup.out
注意:如果发现
nohup.out为空,请检查应用启动方式是否正确,确保标准输出和错误输出都重定向到了日志文件,例如:bash复制nohup your_command > nohup.out 2>&1 &
2.3 必要软件安装
安装PM2和所需Node.js模块:
bash复制# 全局安装PM2进程管理器
npm install pm2 -g
# 进入日志文件所在目录
cd /path/to/your/log/directory
# 初始化npm项目(如果尚未初始化)
npm init -y
# 安装核心依赖
npm install ws tail --save
3. 核心实现方案
3.1 服务端架构设计
本方案采用三层架构:
- 日志读取层:使用
tail模块实时监控nohup.out文件变化 - WebSocket服务层:建立浏览器与服务器的全双工通信通道
- HTTP服务层:提供网页访问入口和静态资源服务
这种设计确保了:
- 低延迟的日志推送
- 支持多个浏览器客户端同时连接
- 自动重连机制保障稳定性
3.2 服务端代码实现
创建server.js文件,内容如下:
javascript复制const WebSocket = require('ws');
const Tail = require('tail').Tail;
const fs = require('fs');
const http = require('http');
const path = require('path');
// 配置部分 ==================================
const LOG_FILE = '/path/to/your/nohup.out'; // 必须修改为你的实际日志路径
const PORT = 3001; // 服务监听端口
// ==========================================
// 创建HTTP服务器
const server = http.createServer((req, res) => {
if (req.url === '/') {
fs.readFile(path.join(__dirname, 'index.html'), (err, data) => {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
});
} else {
res.writeHead(404);
res.end();
}
});
// 创建WebSocket服务器
const wss = new WebSocket.Server({ server });
const clients = new Set();
// 初始化日志追踪
const tail = new Tail(LOG_FILE);
// 新客户端连接处理
wss.on('connection', (ws) => {
console.log('New client connected');
clients.add(ws);
// 发送历史日志
sendAllLogs(ws);
// 客户端断开处理
ws.on('close', () => {
console.log('Client disconnected');
clients.delete(ws);
});
});
// 监听日志文件变化
tail.on('line', (line) => {
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(line);
}
});
});
// 读取并发送全部历史日志
function sendAllLogs(ws) {
fs.readFile(LOG_FILE, 'utf8', (err, data) => {
if (err) {
console.error('Error reading log file:', err);
return;
}
ws.send(data);
});
}
// 启动服务
server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
3.3 前端页面实现
创建index.html文件,提供日志展示界面:
html复制<!DOCTYPE html>
<html>
<head>
<title>实时日志监控</title>
<style>
body { font-family: monospace; margin: 0; padding: 20px; background: #222; color: #eee; }
#log-container {
background: #111;
padding: 15px;
border-radius: 5px;
max-height: 80vh;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
.status { margin-bottom: 10px; padding: 8px; border-radius: 4px; }
.connected { background: #2e7d32; color: white; }
.disconnected { background: #c62828; color: white; }
</style>
</head>
<body>
<h1>实时日志监控</h1>
<div id="status" class="status disconnected">未连接</div>
<div id="log-container"></div>
<script>
const logContainer = document.getElementById('log-container');
const statusDiv = document.getElementById('status');
let socket;
function connectWebSocket() {
const wsUrl = `ws://${window.location.hostname}:${window.location.port || 3001}`;
socket = new WebSocket(wsUrl);
socket.onopen = () => {
statusDiv.textContent = '已连接';
statusDiv.className = 'status connected';
};
socket.onmessage = (event) => {
logContainer.textContent += event.data + '\n';
logContainer.scrollTop = logContainer.scrollHeight;
};
socket.onclose = () => {
statusDiv.textContent = '连接断开,5秒后重试...';
statusDiv.className = 'status disconnected';
setTimeout(connectWebSocket, 5000);
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
connectWebSocket();
</script>
</body>
</html>
4. 服务部署与管理
4.1 使用PM2启动服务
bash复制pm2 start server.js --name "log-monitor"
PM2将自动管理服务进程,提供以下优势:
- 崩溃自动重启
- 日志记录
- 性能监控
- 集群模式支持
4.2 设置开机自启
bash复制pm2 startup
pm2 save
4.3 常用PM2管理命令
bash复制# 查看服务状态
pm2 list
# 查看服务日志
pm2 logs log-monitor
# 重启服务
pm2 restart log-monitor
# 停止服务
pm2 stop log-monitor
# 删除服务
pm2 delete log-monitor
5. 安全与权限配置
5.1 防火墙设置
bash复制# 开放3001端口
sudo ufw allow 3001/tcp
sudo ufw reload
5.2 访问控制建议
- 使用Nginx反向代理:添加HTTPS和基础认证
- 限制访问IP:通过防火墙规则限制可访问IP范围
- 日志文件权限:确保Node.js进程有读取日志文件的权限
6. 常见问题与解决方案
6.1 日志不更新
可能原因及解决方案:
- 文件权限问题:确保Node.js进程用户有读取权限
bash复制sudo chmod 644 /path/to/nohup.out - 文件被覆盖:检查应用是否重新创建了日志文件
- inode变化:某些情况下日志轮转会改变文件inode,需要重启监控服务
6.2 WebSocket连接失败
排查步骤:
- 检查服务是否正常运行
bash复制
pm2 list - 检查端口是否开放
bash复制sudo netstat -tulnp | grep 3001 - 检查防火墙设置
bash复制sudo ufw status
6.3 性能优化建议
- 大日志文件处理:对于超过100MB的日志文件,建议先截断或归档
- 客户端限制:当连接客户端过多时,考虑增加心跳检测机制
- 日志过滤:在服务端增加过滤逻辑,只推送关键日志
7. 方案扩展与进阶
7.1 多日志文件支持
修改server.js以支持监控多个日志文件:
javascript复制const LOG_FILES = {
'app1': '/path/to/app1/nohup.out',
'app2': '/path/to/app2/nohup.out'
};
// 为每个日志文件创建Tail实例
const tails = {};
Object.entries(LOG_FILES).forEach(([name, path]) => {
tails[name] = new Tail(path);
tails[name].on('line', (line) => {
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({name, line}));
}
});
});
});
7.2 日志搜索与过滤
在前端页面增加搜索功能:
html复制<input type="text" id="search" placeholder="搜索日志...">
<button onclick="filterLogs()">搜索</button>
<script>
function filterLogs() {
const searchText = document.getElementById('search').value.toLowerCase();
const lines = logContainer.textContent.split('\n');
logContainer.textContent = lines.filter(line =>
line.toLowerCase().includes(searchText)
).join('\n');
}
</script>
7.3 日志归档与清理
添加定时任务自动归档旧日志:
bash复制# 每天凌晨归档日志
0 0 * * * mv /path/to/nohup.out /path/to/archives/nohup-$(date +\%Y\%m\%d).out && touch /path/to/nohup.out
这套实时日志监控方案经过生产环境验证,能够显著提升运维效率。根据实际需求,可以进一步扩展功能,如添加日志分析、告警机制等,构建更完善的运维监控体系。