大致思路:
1.利用tornado提供的websocket功能与浏览器建立长连接,读取实时日志并输出到浏览器
2.写一个实时读取日志的脚本,利用saltstack远程执行,并把实时日志发往redis中。
3.tornado读取redis中的信息,发往浏览器。
此过程用到了redis的发布和订阅功能。
先看一下tornado中是如何处理的:
import osimport sysimport tornado.websocketimport tornado.webimport tornado.ioloopimport redisimport salt.clientfrom tornado import genfrom tornado.escape import to_unicodefrom logs.utility import get_last_linesfrom logs import settingsclass SubWebSocket(tornado.websocket.WebSocketHandler): """ 此handler处理远程日志查看 """ def open(self, *args, **kwargs): print("opened") @gen.coroutine def on_message(self, message): # 主机名,要查看的日志路径,运行脚本的命令这些信息从浏览器传过来 hostname, log_path, cmd = message.split("||") local = salt.client.LocalClient() r = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, password=settings.REDIS_PASSWD, db=5) # 订阅频道,服务器和日志路径确定一个频道 key = settings.LOG_KEY.format(server=hostname.strip(), log_path=log_path.strip()) channel = r.pubsub() channel.subscribe(key) # 异步方式执行命令,远程运行脚本 local.cmd_async(hostname, "cmd.run", [cmd]) try: while True: data = channel.get_message() if not data: # 如果读取不到消息,间隔一定时间,避免无谓的CPU消耗 yield gen.sleep(0.05) continue if data["type"] == "message": line = format_line(data["data"]) self.write_message(line) except tornado.websocket.WebSocketClosedError: self.close() def on_close(self): global FLAG FLAG = False print("closed")def format_line(line): line = to_unicode(line) if "INFO" in line: color = "#46A3FF" elif "WARN" in line: color = "#FFFF37" elif "ERROR" in line: color = "red" elif "CRITICAL" in line: color = "red" else: color = "#FFFFFF" return "<span >{}</span>".format(color, line)class EchoWebSocket(tornado.websocket.WebSocketHandler): def open(self): print("WebSocket opened") @gen.coroutine def on_message(self, message): log = message print "log file: ", log try: with open(log, 'r') as f: for line in get_last_lines(f): line1 = format_line(line) self.write_message(line1) while True: line = f.readline() if not line: yield gen.sleep(0.05) continue self.write_message(format_line(line.strip())) except tornado.websocket.WebSocketClosedError as e: print e self.close() # def check_origin(self, origin): # print origin, self.request.headers.get("Host") # # super(EchoWebSocket, self).check_origin() # return True def on_close(self): print("WebSocket closed")class Application(tornado.web.Application): def __init__(self): handlers = [ (r'/log/', MainHandler), # 提供浏览页面,页面中的JS与服务器建立连接 (r'/log/local', EchoWebSocket), # 处理本地日志实时查看,比较简单 (r'/log/remote', SubWebSocket), # 处理远程日志实时查看,稍微复杂 ] settings = { "debug": True, "template_path": os.path.join(os.path.dirname(__file__), "templates"), "static_path": os.path.join(os.path.dirname(__file__), "static"), } super(Application, self).__init__(handlers, **settings)class MainHandler(tornado.web.RequestHandler): def get(self): # 要查看的日志路径 log = self.get_argument("log", None) # hostname实际上是saltstack中这台机器对应的minion id hostname = self.get_argument("hostname", None) # 本地日志还是远程日志 type = self.get_argument("type", "local") # 运行读取实时日志的脚本,参数比较多,后面会有 cmd = self.get_argument("cmd", "") context = { "log": log, "hostname": hostname, "type": type, "cmd": cmd, } self.render("index.html", **context)
新闻热点
疑难解答