在写程序的时候,经常会打很多log,以监测程序或业务的运行情况。现在有这样一个需求,需要将业务执行情况即一部分log输出或全部输出展示到前台的可视化界面上。用户在页面上进行不同的任务操作,可以直接看到相应的执行情况。
对当前进程进行监听,每次有输出事件时,则反馈到前台
当前项目是用koa写的,那就koa+socket.io
可参见 socket.io 的官方文档,express等也是类似的用法。
html部分
... <div id="result"> 运行结果 <pre id="console"></pre> </div> ... <script src="/javascript/cmd.js"></script> ...
cmd.js
/* global io,$,window */ /* eslint prefer-arrow-callback: 0 */ const elConsole = document.getElementById('console') // 与Server连接 const socket = io.connect(location.host) // 替换特殊字符 const escapeHtml = function(html) { return html.replace(/&/g, '&') .replace(/>/g, '>') .replace(/</g, '<') } const output = function(data) { $('#console').append(`${escapeHtml(data)}/n`) elConsole.scrollTop = 9e5 } // PubSub模型 // 监听Server端触发的output事件 socket.on('output', output) socket.on('connect', function() { output('连接成功') }) socket.on('disconnect', function() { output('连接已断开/n') }) // 可自定义任意事件,emit触发即可 ....
之前查找node的process相关资料时,发现了这样一段话
那就监听process.on('data',...)就好啦
于是,参考网上一些代码,贴出关键部分
// app.js const app = require('koa')() // 使用./public下的静态文件 app.use(serve('./public')); ... // 使用路由 app.use(router(app)); ..... // 这一行代码一定要在最后一个app.use后面使用 var server = require('http').createServer(app.callback()), io = require('socket.io')(server); .... app.get('/', function *(next) { yield this.render('index', { my: 'data' }); }); .... // npm install --save socket.io const server = require('http').createServer(app.callback()) const io = require('socket.io')(server) // 关键代码 Socket.io的标准用法 io.on('connection', function(socket){ const output = function(msg) { socket.emit('output', msg.toString()) } process.stdout.on('data', output) }); // 开启服务器 server.listen(1337); console.info('Now running on localhost:1337');
解释一下
app.callback()
返回一个可被http.createServer()接受的程序实例,也可以将这个返回函数挂载在一个 Connect/Express 应用中
哈哈,这下大功告成了有没有,来赶紧起一下server见证奇迹!
界面上除了“连接成功”四个大字,啥也没有!看一下终端,哗哗——的满屏输出啊!
why??
这才发现尽管终端满屏输出, 但process并没有监听到任何data事件!为什么呢?
Console.prototype.log = function() { this._stdout.write(util.format.apply(this, arguments) + '/n'); };
可见,console.log底层调用了process.stdout.write,而这句调用并不会触发process监听的data事件!
那就简单啦,每次log的时候,加一句process.stdout.emit('data', msg)就ok啦!
很好,我们来写一个自己的console.log吧(最简单的哟)
// console.js function log(msg){ console.log(msg) process.stdout.emit('data', msg) } module.exports.log = log function error(msg){ console.error(msg) // error 可设置不向前台输出 // process.stdout.emit('data', msg) } module.exports.error = error function info(msg){ console.info(msg) process.stdout.emit('data', msg) } module.exports.info = info
然后在app.js加上
global.myconsole = require('./console') ... // 调用 myconsole.log('test')
ps:用法很多,随意随意啦!
来看一下结果
需求解决啦,但是作为一个web-shell, 怎么能只有输出,没有输入呢!在原有的基础上加一些扩展就好啦!
对于每个连接的客户端,我们可以为其单独开一个shell,以供交互
// app.js ... const spawn = require('child_process').spawn io.on('connection', (socket) => { const shell = spawn('bash') const output = function(msg) { socket.emit('output', msg.toString()) } shell.stdout.on('data', output) shell.stderr.on('data', output) shell.on('close', () => { output('Exit') socket.disconnect(true) }) // 监听输入事件 socket.on('input', (data) => { shell.stdin.write(data) }) socket.on('disconnect', () => { shell.kill('SIGKILL') }) myconsole.debug('控制台连接成功') })
html
<input type='text' id='cmd' placeholder="输入命令"/>
cmd.js
... window.unload = function() { // 离开页面则关闭该进程 socket.emit('disconnect') } elCmd.addEventListener('keypress', function(e) { if (e.keyCode === 13) { const data = `${elCmd.value}` output(`$ ${data}`) socket.emit('input', `${data}/n`) elCmd.value = '' } })
效果如下图
以上例子中,使用
shell.stdout.pipe(process.stdout)可以传递shell的输出到父进程!
反着则不行,好像不是同一个类型的对象!
也是东拼西凑啦,还有很多完善的空间!
譬如,界面输入命令对ctrl+c等快捷键的支持,子进程父进程之间的通信,界面结果显示颜色高亮,console.js拓展等!
另外,是不是也有办法能直接监听到process.stdout.write呢,思考.......