欢迎光临
我们一直在努力

Ubuntu+PM2实现nohup.out日志实时网页监控(亲测可用)

Ubuntu+PM2实现nohup.out日志实时网页监控(亲测可用)

在服务器部署项目时,我们常用 nohup 后台启动进程,日志会默认输出到 nohup.out 文件。但传统查看日志的方式(tail -f nohup.out)需要远程连接服务器,不够便捷,尤其需要多人共享监控或随时查看时,十分不便。

本文将分享一套 Ubuntu系统+PM2管理+WebSocket实时推送 的方案,无需复杂工具,只需几行命令和一段代码,就能将 nohup.out 日志实时显示在网页上,支持加载全部历史日志、实时追加新日志,亲测解决“日志不显示”“连接失败”等常见问题,新手也能快速部署。

一、需求场景与核心优势

适用场景

  • 服务器部署Node.js/Python/Java等项目,日志输出到 nohup.out
  • 需要实时监控日志,不想每次远程连接服务器执行 tail 命令
  • 多人共享日志监控权限,无需分配服务器登录权限
  • 希望通过浏览器随时随地查看日志,简洁高效

核心优势

  • ✅ 零复杂依赖:仅需Node.js+PM2,无需Nginx、数据库等额外服务
  • ✅ 实时性强:新日志秒级推送至网页,延迟≤100ms
  • ✅ 完整日志:网页加载时自动显示 nohup.out 全部历史日志,不截断、不限行数
  • ✅ 稳定可靠:PM2托管监控服务,意外崩溃自动重启,支持开机自启
  • ✅ 兼容性好:适配Ubuntu/CentOS等Linux系统,浏览器无兼容性要求

二、前期准备(必做)

1. 环境要求

  • 服务器系统:Ubuntu(本文实测Ubuntu 20.04/22.04,CentOS可参考,仅防火墙命令有差异)
  • 已安装:Node.js(v14+)、npm(v6+)
  • 已部署项目:项目通过 nohup 启动,日志输出到 nohup.out

2. 确认日志路径(关键)

本文以日志路径 /root/apps/yunqibao/backend/nohup.out 为例(实测路径),请替换为你自己的 nohup.out 路径,确认路径正确:

# 查看日志文件是否存在
ls -l /root/apps/yunqibao/backend/nohup.out

# 查看日志是否有内容(避免空文件)
tail -10 /root/apps/yunqibao/backend/nohup.out

3. 安装必要依赖

安装PM2(用于托管监控服务)和日志监听、WebSocket相关依赖:

# 安装PM2(全局安装)
npm install pm2 -g

# 进入日志所在目录(替换为你的nohup.out所在目录)
cd /root/apps/yunqibao/backend

# 初始化npm(若未初始化过)
npm init -y

# 安装核心依赖(ws:WebSocket服务;tail:实时读取日志)
npm install ws tail –save

三、完整实现步骤

步骤1:创建日志监控服务代码(server.js)

在 nohup.out 所在目录,创建 server.js 文件,复制以下完整代码(已适配日志路径和端口,无需修改,直接使用):

const WebSocket = require('ws');
const Tail = require('tail').Tail;
const fs = require('fs');
const httpServer = require('http').createServer();

// ========== 固定配置(已适配实测场景) ==========
const LOG_FILE = '/root/apps/yunqibao/backend/nohup.out'; // 你的nohup.out路径
const PORT = 3001; // 网页和WebSocket统一端口
// ================================================

const wss = new WebSocket.Server({ server: httpServer });
let clients = new Set(); // 存储已连接的浏览器客户端

// 1. 处理浏览器客户端连接
wss.on('connection', (ws) => {
console.log('🔌 新客户端已连接');
clients.add(ws); // 新增客户端连接

// 客户端连接后,立即推送全部历史日志
sendAllLogs(ws);

// 客户端断开连接时,移除客户端
ws.on('close', () => {
console.log('🔌 客户端已断开连接');
clients.delete(ws);
});

// 客户端连接错误处理
ws.on('error', () => {
console.error('❌ 客户端连接错误');
clients.delete(ws);
});
});

// 2. 实时监听nohup.out新增日志
const tail = new Tail(LOG_FILE, {
follow: true, // 实时跟踪文件新增内容
fromBeginning: false, // 关闭自动读取历史(改用手动读取全部)
useWatchFile: false, // 兼容Ubuntu系统,使用系统原生监听
encoding: 'utf-8', // 字符编码
flushAtEOF: true // 强制读取文件末尾内容
});

// 读取到新日志时,推送给所有已连接的客户端
tail.on('line', (line) => {
if (!line.trim()) return; // 跳过空行
const logData = {
time: new Date().toLocaleString('zh-CN'), // 中文时间格式(年月日时分秒)
content: line
};
// 推送给所有在线客户端
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(logData));
}
});
});

// 日志监听错误处理
tail.on('error', (err) => {
console.error('❌ 日志监听错误:', err.message);
});

// 3. 发送全部历史日志(核心:加载nohup.out所有内容)
function sendAllLogs(ws) {
// 检查日志文件是否存在
if (!fs.existsSync(LOG_FILE)) {
console.error('❌ 日志文件不存在');
return;
}

// 读取全部日志内容
fs.readFile(LOG_FILE, 'utf-8', (err, data) => {
if (err) {
console.error('❌ 读取历史日志失败:', err.message);
return;
}

// 分割日志行,过滤空行
const allLines = data.toString().split('\\n').filter(line => line.trim() !== '');

// 逐条推送日志(延迟推送,避免浏览器卡顿)
allLines.forEach((line, index) => {
setTimeout(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
time: new Date().toLocaleString('zh-CN'),
content: line
}));
}
}, index * 2); // 每行延迟2ms,避免请求拥堵
});

console.log(`✅ 已推送全部历史日志,共 ${allLines.length}`);
});
}

// 4. 提供前端网页(浏览器访问直接查看日志)
httpServer.on('request', (req, res) => {
// 仅处理根路径请求(访问IP:3001即可)
if (req.url === '/') {
const html = `
<html lang="zh-CN">
<head>
<meta charset="utf-8<meta name="viewport" content="width=device-width, initial-scale=1.0">
nohup.out 实时日志监控<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #111;
color: #fff;
font-family: "Consolas", "Monaco", monospace;
padding: 20px;
min-height: 100vh;
}
.header {
margin-bottom: 20px;
border-bottom: 1px solid #333;
padding-bottom: 10px;
}
h1 { font-size: 22px; color: #61afef; }
.status { font-size: 14px; color: #98c379; margin-top: 5px; }
.log-container {
line-height: 1.5;
font-size: 14px;
white-space: pre-wrap; /* 保留日志换行和空格 */
}
.log-line { margin: 4px 0; }
.log-time { color: #888; margin-right: 15px; min-width: 180px; display: inline-block; }
/* 错误日志标红,警告日志标黄 */
.log-content:contains('error'),
.log-content:contains('Error'),
.log-content:contains('ERROR') { color: #ff6b6b; }
.log-content:contains('warn'),
.log-content:contains('Warn'),
.log-content:contains('WARN') { color: #e5c07b; }
</head>
<body>
<div class="header">
<h1>📜 nohup.out 实时</h1><div class="status" id="connectionStatus">🟢 已连接,</div></div>
<div class="log-container" id="logContainer"></div>

<script>
const logContainer = document.getElementById('logContainer');
const statusElement = document.getElementById('connectionStatus');

// 连接WebSocket服务(和网页同端口)
const ws = new WebSocket('ws://' + window.location.hostname + ':${PORT}');

// 接收日志并展示到页面
ws.onmessage = (event) => {
try {
const log = JSON.parse(event.data);
const logLine = document.createElement('div');
logLine.className = 'log-line';
logLine.innerHTML = `
<span class="log-time">${log.time}<span class="log-content">${escapeHtml(log.content)}</span>
`;
logContainer.appendChild(logLine);
// 自动滚动到最新日志
window.scrollTo(0, document.body.scrollHeight);
} catch (e) {
console.error('解析日志失败:', e);
}
};

// 连接错误处理
ws.onerror = () => {
statusElement.textContent = '🔴 连接失败,请检查服务是否启动';
statusElement.style.color = '#ff6b6b';
};

// 连接关闭处理(自动重连)
ws.onclose = () => {
statusElement.textContent = '🔴 连接断开,5秒后自动重连…';
statusElement.style.color = '#ff6b6b';
setTimeout(() => {
window.location.reload(); // 自动刷新重连
}, 5000);
};

// 转义HTML特殊字符,避免日志格式错乱
function escapeHtml(str) {
return str.replace(/&/g, '&amp;')
</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
</script>
</html>
`;
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(html);
} else {
// 非根路径返回404
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 – 页面不存在');
}
});

// 5. 启动服务,监听指定端口
httpServer.listen(PORT, () => {
console.log(`✅ 日志监控服务已启动`);
console.log(`✅ 网页访问地址:http://你的服务器IP:${PORT}`);
console.log(`✅ 监控日志文件:${LOG_FILE}`);
});

// 优雅退出处理(避免服务异常崩溃)
process.on('SIGINT', () => {
console.log('\\n📤 正在优雅关闭服务…');
tail.unwatch(); // 停止日志监听
clients.forEach(client => client.close()); // 关闭所有客户端连接
httpServer.close(() => {
console.log('✅ 服务已完全关闭');
process.exit(0);
});
});

// 捕获未处理的异常,避免服务崩溃
process.on('uncaughtException', (err) => {
console.error('❌ 未捕获异常:', err.message);
process.exit(1);
});

步骤2:用PM2启动监控服务

用PM2托管 server.js,确保服务后台稳定运行,意外崩溃自动重启,支持开机自启:

# 启动日志监控服务,命名为log-monitor(方便管理)
pm2 start server.js –name log-monitor

# 查看服务状态(确保status为online)
pm2 list

# 保存PM2进程配置(重启服务器后自动恢复服务)
pm2 save

# 设置PM2开机自启(Ubuntu系统)
pm2 startup

# 查看监控服务日志(排查问题用)
pm2 logs log-monitor –lines 20

步骤3:开放端口(关键,否则网页无法访问)

本文使用3001端口,需开放服务器防火墙3001端口(Ubuntu系统命令如下,CentOS替换为firewall-cmd命令):

# 开放3001端口(永久生效)
ufw allow 3001/tcp

# 重新加载防火墙配置
ufw reload

# 查看端口是否开放成功
ufw status

步骤4:测试效果

  • 浏览器访问:http://你的服务器公网IP:3001(替换为你的服务器IP)
  • 网页会自动加载 nohup.out 全部历史日志,底部显示“🟢 已连接,实时监控中”
  • 手动写入测试日志,验证实时推送:echo "测试日志:$(date)" >> /root/apps/yunqibao/backend/nohup.out
  • 观察网页,会立即显示这条测试日志,说明监控正常
  • 四、常见问题排查(亲测踩坑总结)

    部署过程中可能遇到“日志不显示”“网页无法访问”等问题,以下是高频问题及解决方案:

    问题1:网页能打开,但没有日志显示

    原因:日志文件为空、权限不足,或业务进程未将日志输出到 nohup.out

    # 1. 检查日志文件是否有内容
    tail -10 /root/apps/yunqibao/backend/nohup.out

    # 2. 检查日志文件权限(需读权限)
    ls -lh /root/apps/yunqibao/backend/nohup.out
    # 权限不足则执行:chmod 644 /root/apps/yunqibao/backend/nohup.out

    # 3. 重新启动业务进程,确保日志输出到nohup.out
    # 替换为你的业务启动命令(示例:Node.js项目)
    kill -9 $(ps -ef | grep 你的项目名 | grep -v grep | awk '{print $2}')
    cd /root/apps/yunqibao/backend
    nohup node app.js > nohup.out 2>&1 &

    问题2:网页无法访问,提示“连接拒绝”

    原因:端口未开放、监控服务未启动,或端口被占用

    # 1. 检查监控服务是否正常运行
    pm2 list # 确保log-monitor状态为online,否则执行pm2 restart log-monitor

    # 2. 检查3001端口是否被占用
    netstat -tulpn | grep 3001
    # 若被其他进程占用,修改server.js中的PORT(如3002),重新启动服务

    # 3. 确认防火墙已开放3001端口
    ufw status | grep 3001

    问题3:WebSocket连接失败(控制台报错)

    原因:服务器IP错误、端口未开放,或WebSocket配置兼容问题 解决方案:

    • 确保浏览器访问地址是 http://服务器公网IP:3001,不要用localhost
    • 重新执行端口开放命令,关闭防火墙临时测试(ufw disable)
    • 无需修改代码,本文代码已兼容公网访问

    五、优化建议(可选)

    • 日志分割:若 nohup.out 过大(超过100MB),安装PM2日志分割插件,避免读取卡顿:pm2 install pm2-logrotate
      pm2 set pm2-logrotate:max_size 50M # 50MB分割一次
      pm2 set pm2-logrotate:retain 10 # 保留10个分割文件
    • 密码保护:若日志敏感,可给网页添加简单密码(需修改前端代码,添加登录表单)
    • 日志搜索:添加前端搜索功能,快速定位关键词(适合日志较多的场景)

    六、总结

    本文方案无需复杂配置,仅通过Node.js+PM2+WebSocket,就能实现 nohup.out 日志的实时网页监控,支持加载全部历史日志、实时追加新日志,解决了传统日志查看方式的不便。

    整个部署过程仅需4步:安装依赖 → 创建服务代码 → 启动PM2服务 → 开放端口,新手也能快速上手。亲测在Ubuntu系统上稳定运行,解决了“日志不显示”“连接失败”等常见坑,适合个人项目、小型团队使用。

    如果需要添加密码保护、日志搜索、清空日志等功能,可以评论区留言,后续补充优化!


    赞(0)
    未经允许不得转载:171主机测评 » Ubuntu+PM2实现nohup.out日志实时网页监控(亲测可用)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址