需求
想在管理页面上看到日志文件/进程的实时输出日志,这对于某些不能直接ssh登录服务器,又没有良好的日志工具,或者单纯只是想检查运行输出的场景会比较有用
思路
来
使用tail -f
来得到实时的文件流或者用pm2 logs
获取pm2进程的输出流,考虑为每个流加上一个订阅id
,这样就可以实现多端同时订阅了
去
在web页面上说到实时,自然就想到用websocket
啦,逻辑并不复杂,客户端传来一个订阅id
,就把它与id对应的流的data
事件挂钩起来,这样便能将数据传递过去
核心代码
本文主要是给出一些实践的思路,所以不会有socket.io
工作原理的说明
产生流
使用child_process.exec
运行子进程,该函数所返回的stdout
属性就是一个可读流:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const exec = require('child_process').exec;
const streams = {};
function watchFile(file) { return watchProcess(`tail -f ${file}`); }
function watchProcess(cmd) { return watchStream(exec(cmd).stdout); }
function watchStream(stream) { const id = Date.now(); streams[id] = stream; stream._buff = '';
stream.on('data', data => { stream._buff += data; let lines = stream._buff.split('\n'); stream._buff = lines.pop(); lines.forEach(line => stream.emit('line', line)); }); }
|
将流与socket.io订阅绑定
1 2 3 4 5 6 7 8 9 10 11 12 13
| const io = SocketIO(httpServer);
io.on('connection', function (socket) { socket.on('sub', function (id) { if (!streams[id]) return socket.emit('line', `该订阅id不存在: ${id}`);
const pipe = line => socket.emit('line', ansiHTML(line));
streams[id].on('line', pipe); }); });
|
前端订阅id
1 2 3 4 5 6 7 8 9 10 11
| var socket = io.connect(); socket.on('line', function (data) { app.rawLines.push(data); if (app.rawLines.length > 2000) app.rawLines.splice(0, app.rawLines.length - 2000); });
socket.on('connect', function () { console.log('connect succeed');
socket.emit('sub', 'time'); });
|
前端页面为了方便渲染非常多行的元素,使用的是vue,代码就不贴了,最终效果如下
总结
这个东西功能接近于webshell了,最起码输出是可以显示了,再加个输入的接口就可以远程执行命令了,这里要提醒下允许远程执行命令是 非常危险 的。
主要的是思路,代码本身并不难,demo已上传github,由于是demo,各种错误捕获和回收处理都不完善,这个还请自己研究了~
2017-06-14补充
监听逐行输出可以直接使用Node自带的readline
模块,示例代码已更新