python3 使用flask_socketio实时推送服务器状态(top)和 日志信息(tail)
使用python3和flask_socketio ,实现服务器上的tail和top命令的实时展示,将结果实时展示在web上
tail在页面上限制了显示长度,自动滚动显示最新数据
效果如下:
tail效果
top效果
和Vue配合使用时,可能会出现如下问题
GET http://127.0.0.1:5000/socket.io/?EIO=3&transport=polling&t=M-9xlys 400 (BAD REQUEST) Access to XMLHttpRequest at 'http://127.0.0.1:5000/socket.io/?EIO=3&transport=polling&t=M-9xlys' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
提示的很明显,就是跨域了,然后理所当然的按平时的解决方式
比如使用flask_cors或者自己利用flask的钩子函数在请求前后,设置请求头等,但是依然会报错!!!
正确的解决方式是,在实例化SocketIO时,加上 cors_allowed_origins="*"
socketio = SocketIO(app , cors_allowed_origins="*")
主要代码如下:
# coding=utf-8 import os import re from threading import Lock from flask import Flask, render_template from flask_socketio import SocketIO from config import LOG_FILE, SECRET_KEY app = Flask(__name__) app.config['SECRET_KEY'] = SECRET_KEY socketio = SocketIO(app)
# 跨域时使用下面的
# socketio = SocketIO(app,cors_allowed_origins="*")
close = False thread = None thread_lock = Lock() client_num = 0 # tail页面连入数量 def get_tail_n_info(n): ''' tail按行获取 :param n: 行数 :return: ''' try: tail_pipe = os.popen(f'tail -n {n} {LOG_FILE} ') except: print('文件不存在') return '' else: tail_output = iter(tail_pipe.readlines()) tail_pipe.close() return tail_output def tail_close(): with thread_lock: global close, thread, client_num client_num -= 1 print('有客户端离开tail页面,当前页面客户端剩余数量为', client_num) if client_num <= 0: close = True client_num = 0 thread = None print('tail页面客户端全部关闭') def get_top_info(): '''获取top命令结果,并做处理''' top_pipe = os.popen('top -n 1') try: top_output = top_pipe.read() finally: top_pipe.close() # 用print输出top_output看着没问题,但是用repr输出会发现有很多其他字符,这些字符会被发往前端,导致页面上数据混乱 # 暂时先直接替换处理 top_output = top_output.replace("\x1b(B\x1b[m", "").replace("\x1b(B\x1b[m\x1b[39;49m\x1b[K", "").replace( "\x1b[?1h\x1b=\x1b[?25l\x1b[H\x1b[2J", "").replace("\x1b[39;49m\x1b[1m", "").replace("\x1b[39;49m\x1b[K", "").replace("\x1b[39;49m", "").replace( "\x1b[K", "").replace("\x1b[7m", "").replace("\x1b[?1l\x1b>\x1b[45;1H", "").replace("\x1b[?12l\x1b[?25h", "").replace("\x1b[1m", "") _html = '' for num, line in enumerate(top_output.split('\n')): if num >= 6: if num == 6: new_line = "<table> <tr>" else: new_line = "<tr>" td_list = re.split(r" +", line) if len(td_list) > 1: for td in td_list: if td.strip(): new_line += f"<td>{(8-len(td))*' '+td}</td>" new_line += "</tr>" else: new_line = '<div>' + line.replace(' ', " ") + '</div>' _html += new_line _html += '</table>' return _html @app.route('/') def index(): return render_template('index.html') @app.route('/tail', methods=['GET']) def tail_html(): return render_template('tail.html') @app.route('/top', methods=['GET']) def top_html(): return render_template('top.html') @socketio.on('connect', namespace="/shell") def connect(): print("connect..") @socketio.on('disconnect', namespace="/shell") def disconnect(): print("disconnect..") @socketio.on('open_tail', namespace="/shell") def open_tail(message): print('received open_tail message: ' + message.get('data', '')) global thread, close, client_num with thread_lock: client_num += 1 print('有客户端进入tail页面,当前页面客户端数量为', client_num) if thread is None: close = False thread = socketio.start_background_task(target=background_thread) else: # 有其他客户端正在使用时,则先发送最近30条过去 for line in get_tail_n_info(n=30): if line.strip(): socketio.emit('tail_response', {'text': line}, namespace='/shell') @socketio.on('close_tail', namespace="/shell") def close_tail(message): print('准备关闭tail', message.get('data', '')) tail_close() @socketio.on('handle_top', namespace="/shell") def handle_top(message): print('received handle_top message: ' + message.get('data', '')) top_info = get_top_info() socketio.emit('top_response', {'text': top_info}, namespace='/shell') def background_thread(): try: tail_pipe = os.popen('tail -f ' + LOG_FILE) except: print('文件不存在') return else: while not close: tail_output = tail_pipe.readline() if tail_output.strip(): socketio.emit('tail_response', {'text': tail_output}, namespace='/shell') tail_pipe.close() if __name__ == '__main__': socketio.run(app, host='0.0.0.0', port=8000)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="https://code.jquery.com/jquery-3.4.0.min.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script> <script type="text/javascript" charset="utf-8"> $(document).ready(function () { var child_num = 0; var namespace = '/shell'; var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace); socket.on('tail_response', function (res) { if (child_num < 40) { $('#terminal').append('<div>' + res.text + '</div>'); child_num += 1 } else { // 先删后加 $('#terminal div:first').remove(); $('#terminal').append('<div>' + res.text + '</div>'); } $(document).scrollTop($(document).height()); // 将滚动条滚到最下方 console.log(res.text); }); socket.on('connect', function (res) { socket.emit('open_tail', {'data': 'I\'m connected!'}); }); //socket.on('disconnect', function (data) { // socket.emit('close_tail', {'data': 'I\'m disconnected!'}); //}); $(window).bind('beforeunload', function () { // 离开页面前关闭tail socket.emit('close_tail', {'data': 'I\'m leave!'}); } ); }); $(window).resize(function () { var cliWidth = document.body.clientWidth; var cliHeight = document.body.clientHeight; var divWidth = cliWidth - 2; var divHeight = cliHeight - 2; $('#terminal').css("width", divWidth + "px"); $('#terminal').css("height", divHeight + "px"); $(document).scrollTop($(document).height()); // 将滚动条滚到最下方 }) </script> <style> html, body { height: 100%; margin: 0; } .outer { height: 100%; } #terminal { height: 100%; background-color: black; color: white; padding-left: 10px; } #terminal div { background-color: black; color: white; } </style> </head> <body> <div class="outer"> <div id="terminal"> </div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="https://code.jquery.com/jquery-3.4.0.min.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script> <script type="text/javascript" charset="utf-8"> $(document).ready(function () { var namespace = "/shell"; var socket = io.connect('http://' + document.domain + ':' + location.port + namespace); socket.on('connect', function (res) { socket.emit('handle_top', {'data': 'I\'m connected!'}); }); socket.on('top_response', function (res) { var top_info = res.text; document.getElementById("terminal").innerHTML = top_info; setTimeout(function () { socket.emit("handle_top", {"data": "handle_top"}); }, 2000) }); }); </script> <style type="text/css"> #terminal { background-color: black; color: white; } #terminal div { width: 1024px; text-align: justify; } table { width: 1024px; table-layout: fixed; text-align: right; } </style> </head> <body> <div> <div id="terminal"> </div> </div> </body> </html>
项目完整地址:https://github.com/Mark-IT/system_log_web
Access to XMLHttpRequest at 'http://127.0.0.1:5000/socket.io/?EIO=3&transport=polling&t=M-9xlys' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.