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

Ubuntu+PM2实现nohup.out日志实时网页监控(亲测可用) UbuntuPM2实现nohup.out日志实时网页监控亲测可用在服务器部署项目时我们常用nohup后台启动进程日志会默认输出到nohup.out文件。但传统查看日志的方式tail -f nohup.out需要远程连接服务器不够便捷尤其需要多人共享监控或随时查看时十分不便。本文将分享一套Ubuntu系统PM2管理WebSocket实时推送的方案无需复杂工具只需几行命令和一段代码就能将nohup.out日志实时显示在网页上支持加载全部历史日志、实时追加新日志亲测解决“日志不显示”“连接失败”等常见问题新手也能快速部署。一、需求场景与核心优势适用场景服务器部署Node.js/Python/Java等项目日志输出到nohup.out需要实时监控日志不想每次远程连接服务器执行tail命令多人共享日志监控权限无需分配服务器登录权限希望通过浏览器随时随地查看日志简洁高效核心优势✅ 零复杂依赖仅需Node.jsPM2无需Nginx、数据库等额外服务✅ 实时性强新日志秒级推送至网页延迟≤100ms✅ 完整日志网页加载时自动显示nohup.out全部历史日志不截断、不限行数✅ 稳定可靠PM2托管监控服务意外崩溃自动重启支持开机自启✅ 兼容性好适配Ubuntu/CentOS等Linux系统浏览器无兼容性要求二、前期准备必做1. 环境要求服务器系统Ubuntu本文实测Ubuntu 20.04/22.04CentOS可参考仅防火墙命令有差异已安装Node.jsv14、npmv6已部署项目项目通过nohup启动日志输出到nohup.out2. 确认日志路径关键本文以日志路径/root/apps/yunqibao/backend/nohup.out为例实测路径请替换为你自己的nohup.out路径确认路径正确# 查看日志文件是否存在ls-l/root/apps/yunqibao/backend/nohup.out# 查看日志是否有内容避免空文件tail-10/root/apps/yunqibao/backend/nohup.out3. 安装必要依赖安装PM2用于托管监控服务和日志监听、WebSocket相关依赖# 安装PM2全局安装npminstallpm2-g# 进入日志所在目录替换为你的nohup.out所在目录cd/root/apps/yunqibao/backend# 初始化npm若未初始化过npminit-y# 安装核心依赖wsWebSocket服务tail实时读取日志npminstallwstail--save三、完整实现步骤步骤1创建日志监控服务代码server.js在nohup.out所在目录创建server.js文件复制以下完整代码已适配日志路径和端口无需修改直接使用constWebSocketrequire(ws);constTailrequire(tail).Tail;constfsrequire(fs);consthttpServerrequire(http).createServer();// 固定配置已适配实测场景 constLOG_FILE/root/apps/yunqibao/backend/nohup.out;// 你的nohup.out路径constPORT3001;// 网页和WebSocket统一端口// constwssnewWebSocket.Server({server:httpServer});letclientsnewSet();// 存储已连接的浏览器客户端// 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新增日志consttailnewTail(LOG_FILE,{follow:true,// 实时跟踪文件新增内容fromBeginning:false,// 关闭自动读取历史改用手动读取全部useWatchFile:false,// 兼容Ubuntu系统使用系统原生监听encoding:utf-8,// 字符编码flushAtEOF:true// 强制读取文件末尾内容});// 读取到新日志时推送给所有已连接的客户端tail.on(line,(line){if(!line.trim())return;// 跳过空行constlogData{time:newDate().toLocaleString(zh-CN),// 中文时间格式年月日时分秒content:line};// 推送给所有在线客户端clients.forEach(client{if(client.readyStateWebSocket.OPEN){client.send(JSON.stringify(logData));}});});// 日志监听错误处理tail.on(error,(err){console.error(❌ 日志监听错误,err.message);});// 3. 发送全部历史日志核心加载nohup.out所有内容functionsendAllLogs(ws){// 检查日志文件是否存在if(!fs.existsSync(LOG_FILE)){console.error(❌ 日志文件不存在);return;}// 读取全部日志内容fs.readFile(LOG_FILE,utf-8,(err,data){if(err){console.error(❌ 读取历史日志失败,err.message);return;}// 分割日志行过滤空行constallLinesdata.toString().split(\n).filter(lineline.trim()!);// 逐条推送日志延迟推送避免浏览器卡顿allLines.forEach((line,index){setTimeout((){if(ws.readyStateWebSocket.OPEN){ws.send(JSON.stringify({time:newDate().toLocaleString(zh-CN),content:line}));}},index*2);// 每行延迟2ms避免请求拥堵});console.log(✅ 已推送全部历史日志共${allLines.length}行);});}// 4. 提供前端网页浏览器访问直接查看日志httpServer.on(request,(req,res){// 仅处理根路径请求访问IP:3001即可if(req.url/){consthtmlhtml langzh-CN head meta charsetutf-8meta nameviewport contentwidthdevice-width, initial-scale1.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 classheader h1 nohup.out 实时/h1div classstatus idconnectionStatus 已连接/div/div div classlog-container idlogContainer/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 spanclasslog-time${log.time}spanclasslog-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; charsetutf-8});res.end(html);}else{// 非根路径返回404res.writeHead(404,{Content-Type:text/plain; charsetutf-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(clientclient.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--namelog-monitor# 查看服务状态确保status为onlinepm2 list# 保存PM2进程配置重启服务器后自动恢复服务pm2 save# 设置PM2开机自启Ubuntu系统pm2 startup# 查看监控服务日志排查问题用pm2 logs log-monitor--lines20步骤3开放端口关键否则网页无法访问本文使用3001端口需开放服务器防火墙3001端口Ubuntu系统命令如下CentOS替换为firewall-cmd命令# 开放3001端口永久生效ufw allow3001/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-vgrep|awk{print $2})cd/root/apps/yunqibao/backendnohupnodeapp.jsnohup.out21问题2网页无法访问提示“连接拒绝”原因端口未开放、监控服务未启动或端口被占用# 1. 检查监控服务是否正常运行pm2 list# 确保log-monitor状态为online否则执行pm2 restart log-monitor# 2. 检查3001端口是否被占用netstat-tulpn|grep3001# 若被其他进程占用修改server.js中的PORT如3002重新启动服务# 3. 确认防火墙已开放3001端口ufw status|grep3001问题3WebSocket连接失败控制台报错原因服务器IP错误、端口未开放或WebSocket配置兼容问题解决方案确保浏览器访问地址是http://服务器公网IP:3001不要用localhost重新执行端口开放命令关闭防火墙临时测试ufw disable无需修改代码本文代码已兼容公网访问五、优化建议可选日志分割若nohup.out过大超过100MB安装PM2日志分割插件避免读取卡顿pm2installpm2-logrotate pm2setpm2-logrotate:max_size 50M# 50MB分割一次pm2setpm2-logrotate:retain10# 保留10个分割文件密码保护若日志敏感可给网页添加简单密码需修改前端代码添加登录表单日志搜索添加前端搜索功能快速定位关键词适合日志较多的场景六、总结本文方案无需复杂配置仅通过Node.jsPM2WebSocket就能实现nohup.out日志的实时网页监控支持加载全部历史日志、实时追加新日志解决了传统日志查看方式的不便。整个部署过程仅需4步安装依赖 → 创建服务代码 → 启动PM2服务 → 开放端口新手也能快速上手。亲测在Ubuntu系统上稳定运行解决了“日志不显示”“连接失败”等常见坑适合个人项目、小型团队使用。如果需要添加密码保护、日志搜索、清空日志等功能可以评论区留言后续补充优化