精华内容
下载资源
问答
  • 一、环境介绍二、RabbitMQ代码三、websocket代码wsclient.gowshub.go四、GIN路由五、HTML代码六、测试代码 一、环境介绍 ...AI算法训练过程中的日志实时在Web UI界面显示     &.

    一、环境介绍

    Go版本:1.13.1
    开发工具:IntelliJ IDEA 2019.2.3 x64
    开发环境:windows10 64位
    部署环境:centos7

    业务场景:
           AI算法训练过程中的日志,实时在Web UI界面显示
           每次训练的日志可以在多个Web UI展示,同一个训练的同一条消息可以被多个消费端消费
           不同训练的消息不能混合显示,使用RabbitMQ的路由匹配模式
    RountingKey=rtk.train.id,queue=que.logque.id,每个训练根据id匹配自己的日志。Id=训练id
    Exchange持久化, queue不持久化
           消息流转过程、RabbitMQ队列设计如下
    在这里插入图片描述
    代码已上传github:https://github.com/reachyu/realtimelog

    二、RabbitMQ代码

    路由模式生产者和消费者代码如下

    package msgmq
    
    import (
    	"aisvc/common/vo"
    	"encoding/json"
    	"fmt"
    	"github.com/streadway/amqp"
    	"log"
    	"strconv"
    	"unsafe"
    )
    
    // rabbitmq 路由routing模式
    
    // 发送消息(生产者)
    func PublishMsgRout(exName string,rtKey string, msg string) {
    
    	ch, err := MQInstance().GetMQChannel()
    	failOnError(err, "Failed to open a channel")
    	defer ch.Close()
    
    	err = ch.ExchangeDeclare(
    		exName, // name
    		"direct",      // type
    		true,         // durable
    		false,        // auto-deleted
    		false,        // internal
    		false,        // no-wait
    		nil,          // arguments
    	)
    	failOnError(err, "Failed to declare an exchange")
    	err = ch.Publish(
    		exName,          // exchange
    		rtKey, // routing key
    		//如果为true,根据exchange类型和routekey类型,如果无法找到符合条件的队列,name会把发送的信息返回给发送者
    		true, // mandatory
    		false, // immediate
    		amqp.Publishing{
    			// 消息持久化
    			DeliveryMode: amqp.Persistent,
    			ContentType: "text/plain",
    			Body:        []byte(msg),
    		})
    	failOnError(err, "Failed to publish a message")
    }
    
    
    type Callback func(trainId string,msg string)
    
    // 消费消息(消费者)
    func ConsumeMsgRout(exName string,queName string,rtKey string,callback Callback) {
    	ch, err := MQInstance().GetMQChannel()
    	failOnError(err, "Failed to receive a message")
    	defer ch.Close()
    
    	err = ch.ExchangeDeclare(
    		exName,   // name
    		"direct", // type
    		true,     // durable
    		false,    // auto-deleted
    		false,    // internal
    		false,    // no-wait
    		nil,      // arguments
    	)
    	failOnError(err, "Failed to receive a message")
    
    	_, err = ch.QueueDeclare(
    		queName,    // name
    		false, // durable
    		false, // delete when usused
    		false,  // exclusive
    		false, // no-wait
    		nil,   // arguments
    	)
    	failOnError(err, "Failed to receive a message")
    
    	err = ch.QueueBind(
    		queName, // queue name
    		rtKey,     // routing key
    		exName, // exchange
    		false,
    		nil,
    	)
    	failOnError(err, "Failed to receive a message")
    
    	msgs, err := ch.Consume(
    		queName, // queue
    		"",     // consumer
    		true,   // auto-ack
    		false,  // exclusive
    		false,  // no-local
    		false,  // no-wait
    		nil,    // args
    	)
    
    	forever := make(chan bool)
    	go func() {
    		for d := range msgs {
    			var msgTrainLog vo.TrainLog
    			_ = json.Unmarshal(d.Body, &msgTrainLog)
    			trainId := msgTrainLog.TrainId
    			id64 := strconv.FormatInt(trainId,10)
    
    			// 避免循环引用  callback = ws.SendLogsToWeb(trainId string,msg string)
    			strAddress := &callback
    			strPointer := fmt.Sprintf("%d", unsafe.Pointer(strAddress))
    			intPointer, _ := strconv.ParseInt(strPointer, 10, 0)
    			var pointer *Callback
    			pointer = *(**Callback)(unsafe.Pointer(&intPointer))
    			(Callback)(*pointer)(id64,msgTrainLog.TrainLog)
    
    			log.Printf("路由队列接收到消息======== [x] %s", d.Body)
    		}
    	}()
    	<-forever
    }
    
    func failOnError(err error, msg string) {
    	if err != nil {
    		log.Fatalf("%s: %s", msg, err)
    	}
    }
    

    三、websocket代码

    websocket代码参考了
    https://github.com/516134941/websocket-gin-demo/tree/master/message-chat

    wsclient.go

    package ws
    
    import (
    	_const "aisvc/common/const"
    	mq "aisvc/msgmq"
    	"bytes"
    	"fmt"
    	"log"
    	"net/http"
    	"sync"
    	"time"
    
    	"github.com/gin-gonic/gin"
    
    	"github.com/gorilla/websocket"
    )
    
    const (
    	// Time allowed to write a message to the peer.
    	writeWait = 10 * time.Second
    	// Time allowed to read the next pong message from the peer.
    	pongWait = 60 * time.Second
    	// Send pings to peer with this period. Must be less than pongWait.
    	pingPeriod = (pongWait * 9) / 10
    	// Maximum message size allowed from peer.
    	maxMessageSize = 512
    )
    
    var clientMaps map[string]*WSClient
    var once sync.Once
    
    func GetClientMaps() map[string]*WSClient {
    	once.Do(func() {
    		clientMaps = make(map[string]*WSClient)
    	})
    	return clientMaps
    }
    
    var (
    	newline = []byte{'\n'}
    	space   = []byte{' '}
    )
    
    var upgrader = websocket.Upgrader{
    	ReadBufferSize:  1024,
    	WriteBufferSize: 1024,
    }
    
    // Client is a middleman between the websocket connection and the hub.
    type WSClient struct {
    	hub *WSHub
    	// The websocket connection.
    	conn *websocket.Conn
    	// Buffered channel of outbound messages.
    	send chan []byte
    	trainId []byte
    }
    
    func SendLogsToWeb(trainId string,msg string) {
    	clientMaps := GetClientMaps()
    	c := clientMaps[trainId]
    
    	if c == nil{
    		return
    	}
    	c.conn.SetReadLimit(maxMessageSize)
    	c.conn.SetReadDeadline(time.Now().Add(pongWait))
    	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
    
    	message := bytes.TrimSpace(bytes.Replace([]byte(msg), newline, space, -1))
    	message = []byte(trainId + "&" + msg)
    	fmt.Println("websocket读取到的消息====="+string(message))
    	c.hub.broadcast <- []byte(message)
    
    }
    
    func (c *WSClient) readPump() {
    	defer func() {
    		c.hub.unregister <- c
    		c.conn.Close()
    	}()
    	c.conn.SetReadLimit(maxMessageSize)
    	c.conn.SetReadDeadline(time.Now().Add(pongWait))
    	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
    	for {
    		_, message, err := c.conn.ReadMessage()
    		if err != nil {
    			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
    				log.Printf("error: %v", err)
    			}
    			break
    		}
    		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
    		message = []byte(string(c.trainId) + "&" + string(message))
    		fmt.Println("websocket读取到的消息====="+string(message))
    		c.hub.broadcast <- []byte(message)
    	}
    }
    
    func (c *WSClient) writePump() {
    	ticker := time.NewTicker(pingPeriod)
    	defer func() {
    		ticker.Stop()
    		c.conn.Close()
    	}()
    	for {
    		select {
    		case message, ok := <-c.send:
    			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
    			if !ok {
    				// The hub closed the channel.
    				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
    				return
    			}
    
    			w, err := c.conn.NextWriter(websocket.TextMessage)
    			if err != nil {
    				return
    			}
    			w.Write(message)
    			if err := w.Close(); err != nil {
    				log.Printf("error: %v", err)
    				return
    			}
    		case <-ticker.C:
    			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
    			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
    				return
    			}
    		}
    	}
    }
    
    // ServeWs handles websocket requests from the peer.
    func ServeWs(hub *WSHub, c *gin.Context) {
    	GetClientMaps()
    	trainId := c.Param("trainId")
    	// 将网络请求变为websocket
    	var upgrader = websocket.Upgrader{
    		// 解决跨域问题
    		CheckOrigin: func(r *http.Request) bool {
    			return true
    		},
    	}
    	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    	if err != nil {
    		log.Println(err)
    		return
    	}
    	fmt.Println("websocket接收到前端的trainId======"+trainId)
    	client := &WSClient{hub: hub, conn: conn, send: make(chan []byte, 256), trainId: []byte(trainId)}
    	client.hub.register <- client
    
    	clientMaps[trainId] = client
    
    	// 监听日志消息
    	go mq.ConsumeMsgRout(_const.RABBITMQ_ROUT_EXCHANGE_NAME,_const.RABBITMQ_ROUT_QUEUE + trainId,_const.RABBITMQ_ROUT_ROUTING_KEY + trainId,SendLogsToWeb)
    
    	// Allow collection of memory referenced by the caller by doing all work in
    	// new goroutines.
    	go client.writePump()
    	//go client.readPump()
    }
    

    wshub.go

    package ws
    
    import "strings"
    
    // Hub maintains the set of active clients and broadcasts messages to the
    // clients.
    type WSHub struct {
    	// Registered clients.
    	clients map[*WSClient]bool
    	// Inbound messages from the clients.
    	broadcast chan []byte
    	// Register requests from the clients.
    	register chan *WSClient
    	// Unregister requests from clients.
    	unregister chan *WSClient
    	// 唯一id key:client value:唯一id
    	trainId map[*WSClient]string
    }
    
    // NewHub .
    func NewHub() *WSHub {
    	return &WSHub{
    		broadcast:  make(chan []byte),
    		register:   make(chan *WSClient),
    		unregister: make(chan *WSClient),
    		clients:    make(map[*WSClient]bool),
    		trainId:    make(map[*WSClient]string),
    	}
    }
    
    // Run .
    func (h *WSHub) Run() {
    	for {
    		select {
    		case client := <-h.register:
    			h.clients[client] = true                 // 注册client端
    			h.trainId[client] = string(client.trainId) // 给client端添加唯一id
    		case client := <-h.unregister:
    			if _, ok := h.clients[client]; ok {
    				delete(h.clients, client)
    				delete(h.trainId, client)
    				close(client.send)
    			}
    		case message := <-h.broadcast:
    			for client := range h.clients {
    				// 使用“&”对message进行message切割 获取唯一id
    				// 向信息所属的训练内的所有client 内添加send
    				// msg[0]为唯一id msg[1]为打印内容
    				msg := strings.Split(string(message), "&")
    				if string(client.trainId) == msg[0] {
    					select {
    					case client.send <- []byte(msg[1]):
    					default:
    						close(client.send)
    						delete(h.clients, client)
    						delete(h.trainId, client)
    					}
    				}
    			}
    		}
    	}
    }
    

    四、GIN路由

    // 设置跨域
    	router.Use(cors.New(cors.Config{
    		AllowAllOrigins:  true,  // 这是允许访问所有域
    		AllowMethods:     []string{"GET", "PUT", "POST", "DELETE", "OPTIONS"},   //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
    		AllowHeaders:     []string{"x-xq5-jwt", "Content-Type", "Origin", "Content-Length"},  // 允许跨域设置
    		ExposeHeaders:    []string{"x-xq5-jwt"},  // 跨域关键设置 让浏览器可以解析
    		AllowCredentials: true,  //  跨域请求是否需要带cookie信息 默认设置为true
    		MaxAge:           12 * time.Hour,
    	}))
    
    hub := ws.NewHub()
    	go hub.Run()
    	router.GET("/ws/logs/:trainId", func(c *gin.Context) { ws.ServeWs(hub, c) })
    

    五、HTML代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>logs</title>
    <script type="text/javascript">
    window.onload = function () {
        var conn;
        var msg = document.getElementById("msg");
        var log = document.getElementById("log");
        function appendLog(item) {
            var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
            log.appendChild(item);
            if (doScroll) {
                log.scrollTop = log.scrollHeight - log.clientHeight;
            }
        }
        document.getElementById("form").onsubmit = function () {
            if (!conn) {
                return false;
            }
            if (!msg.value) {
                return false;
            }
            conn.send(msg.value);
            msg.value = "";
            return false;
        };
        if (window["WebSocket"]) {
            // 123456为trainId
            conn = new WebSocket("ws://" + "localhost:9090" + "/ws/logs/123456");
            conn.onclose = function (evt) {
                var item = document.createElement("div");
                item.innerHTML = "<b>Connection closed.</b>";
                appendLog(item);
            };
            conn.onmessage = function (evt) {
                var messages = evt.data.split('\n');
                for (var i = 0; i < messages.length; i++) {
                    var item = document.createElement("div");
                    item.innerText = messages[i];
                    appendLog(item);
                }
            };
        } else {
            var item = document.createElement("div");
            item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
            appendLog(item);
        }
    };
    </script>
    <style type="text/css">
    html {
        overflow: hidden;
    }
    body {
        overflow: hidden;
        padding: 0;
        margin: 0;
        width: 100%;
        height: 100%;
        background: gray;
    }
    #log {
        background: white;
        margin: 0;
        padding: 0.5em 0.5em 0.5em 0.5em;
        position: absolute;
        top: 0.5em;
        left: 0.5em;
        right: 0.5em;
        bottom: 3em;
        overflow: auto;
    }
    #form {
        padding: 0 0.5em 0 0.5em;
        margin: 0;
        position: absolute;
        bottom: 1em;
        left: 0px;
        width: 100%;
        overflow: hidden;
    }
    </style>
    </head>
    <body>
    <div id="log"></div>
    <form id="form">
        <input type="submit" value="Send" />
        <input type="text" id="msg" size="64"/>
    </form>
    </body>
    </html>
    

    六、测试代码

    func main() {
    	// 不调用的话,glog会报错----ERROR: logging before flag.Parse:
    	flag.Parse()
    	initenv.InitEnv()
    
    	go httpserver.InitHttpServer()
    
    msg := map[string]interface{}{
    			"trainId":    123456,
    			"trainLog":   "测试测试测试测试",
    		}
    msgJson, _ := json.Marshal(msg)
    
    	forever := make(chan bool)
    	go func() {
    		for {
    
    	mq.PublishMsgRout(_const.RABBITMQ_ROUT_EXCHANGE_NAME,_const.RABBITMQ_ROUT_ROUTING_KEY+"123456",string(msgJson))
    		time.Sleep(1 * time.Second)
    		}
    	}()
    	<-forever
    
    }
    

    实时日志效果
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • WebSocket页面显示日志的代码

    千次阅读 2019-06-21 12:54:49
    WebSocket页面显示日志的代码在前端html页面显示后台的服务器日志信息页面效果前端页面代码后台代码WebSocketInterceptor拦截器WebSocketHandler握手动作WebSocketConfig配置类TailLogThread实时读取日志的线程...

    在前端html页面显示后台的服务器日志信息

    主要是为了方便,前端也不会ssh去连接服务器tail -f日志。为了方便前端和测试调试,将日志 染色 并显示在页面上。

    页面效果

    在这里插入图片描述

    前端页面代码

    前面引用的easyui库这些是为了页面布局和原系统支持。其实对读者没啥用,不需要的去掉就行。

    <!--showServiceLog.html-->
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>WebSocket查看服务日志页面</title>
        <!-- easyUI样式 -->
        <link rel="stylesheet" type="text/css" href="../../Font-Awesome-master/css/font-awesome.min.css" />
        <link rel="stylesheet" type="text/css" href="../../css/common/main.css" />
        <link rel="stylesheet" type="text/css" href="../../css/common/my.css" />
        <link rel="stylesheet" type="text/css" href="../../css/themes/default/easyui.css" />
        <link rel="stylesheet" type="text/css" href="../../css/themes/icon.css" />
        <script type="text/javascript" src="../../js/jquery.min.js"></script>
        <script type="text/javascript" src="../../js/jquery.easyui.min.js"></script>
        <script type="text/javascript" src="../../js/easyui-lang-zh_CN.js"></script>
        <script type="text/javascript" src="../../js/common.js"></script>
        <script type="text/javascript" src="../../js/common/Cookie.js"></script>
        <script type="text/javascript" src="../../js/sockjs.js"></script>
        <style type="text/css">
            .datagrid-cell, .datagrid-cell-group, .datagrid-header-rownumber, .datagrid-cell-rownumber
            {
                text-overflow: ellipsis;
            }
        </style>
    
    </head>
    <body>
    <!-- 标题和操作菜单 -->
    <table class="title" style="width:100%;padding:5px;">
        <tbody>
        <tr>
            <td style="width:25%;color: #407EAC">
                <img src="../../images/common/title.gif" style="vertical-align:middle;">查看服务日志</td>
            <td style="text-align: right;">
                <!--按钮区域-->
                <a class="easyui-linkbutton l-btn l-btn-small" data-options="iconCls:'icon-search'"  onclick="loadSocket2()">&nbsp;查看日志&nbsp;</a>
                <a class="easyui-linkbutton l-btn l-btn-small" data-options="iconCls:'icon-search'"  onclick="looknearly()">&nbsp;查看最近&nbsp;</a>
                <a class="easyui-linkbutton l-btn l-btn-small" data-options="iconCls:'icon-search'"  onclick="cleanText()">&nbsp;清屏&nbsp;</a>
            </td>
        </tr>
        </tbody>
    </table>
    <!-- 模糊查询 -->
    <table class="btbForm">
        <tbody>
        <tr>
            <!--<td>-->
                <!--小组发送<input id="groupId" class="easyui-combobox" data-options="editable:false,panelHeight:'auto',limitToList:'true'" style="width: 110px;">-->
                <!--&nbsp;&nbsp;&nbsp;组员&nbsp;<input id="userId" class="easyui-combobox" data-options="editable:false,panelHeight:'auto',limitToList:'true'" style="width: 110px;">-->
            <!--</td>-->
        </tr>
        </tbody>
    </table>
    <!-- 表格间距 -->
    <div style="margin-top:5px"></div>
    <!-- 数据表格 -->
    <div>
        <div id="text" style=" overflow-y:scroll; width:100%; height:400px;word-break:break-word;">初始化完毕...</div>
    </div>
    <!--隐藏的昵称信息-->
    <input id="nickName" style="display: none">
    <script type="text/javascript">
        //定义WebSocket接口
        var ws;
        /**
         * jQuery初始化完毕执行的代码
         */
        $(function(){
            loadSocket2();
            setInterval(function(){
                $('#text').css("height",$(document.body).height()*0.8);
            },300);
        });
    
        /**
         * 下滑到底部
         */
        function looknearly() {
            try {
                $("#text").scrollTop($("#text")[0].scrollHeight);
            } catch (e) {
                console.error(e);
            }
        }
    
        /**
         * 获取WebSocket根地址
         */
        function wsPath() {
            //定义ws接口开头协议
            var wsPath = "ws://"+basePath.split("://")[1];
            if(basePath.split("://")[0].trim().toLowerCase().indexOf("https")>-1){
                wsPath = "wss://"+basePath.split("://")[1];
            }
            return wsPath;
        }
    
        function testOhter() {
            $.messager.prompt('提示信息', '请输入ws地址:', function(r){
                if (r){
                    loadSocket2(r);
                }else{
                    loadSocket2();
                }
            });
        }
    
    
        /**
         * 加载websocket数据
         */
        function loadSocket2(file){
            //console.log(ws);
            if(ws!=null&&ws!=undefined&&ws.readyState == 1){
                $('#text').append("连接已存在..<br/>");
                return;
            }
            if(file!=null&&file!=undefined&&file!=''){
                if ("WebSocket" in window){
                    ws=new WebSocket(wsPath()+"/websocket/"+file+".json");
                }else{
                    ws=new SockJS(wsPath()+"/sockjs/"+file+".ws");
                }
            }else{
                if ("WebSocket" in window){
                    ws=new WebSocket(wsPath()+"/ws_showLog.json");
                }else{
                    ws=new SockJS(wsPath()+"/sockjs/ws_showLog.ws");
                }
            }
    
            $('#text').append("<br/>开始建立连接...<br/>");
            ws.onopen=function(event){
                $('#text').append("连接已建立..<br/>");
                $('#start').linkbutton('disable');
            }
            /**接收消息*/
            ws.onmessage=function(event){
                //console.log(event);
                var msg=event.data;
                $('#text').append(msg);
                // 滚动条滚动到最低部
                try {
                    $("#text").scrollTop($("#text")[0].scrollHeight);
                } catch (e) {
                    console.error(e);
                }
            }
            ws.onclose=function(){
                $('#text').append("连接已关闭..<br/>");
                $('#start').linkbutton('enable');
    
            }
            ws.onerror = function(){
                $('#text').append("发生了错误..<br/>");
                $('#start').linkbutton('enable');
            }
    
        }
    
        /**
         * 手动加载websocket数据
         */
        function loadSocket(file){
    
            if(file!=null&&file!=undefined&&file!=''){
                if ("WebSocket" in window){
                    ws=new WebSocket(wsPath()+"/websocket/"+file+".json");
                }else{
                    ws=new SockJS(wsPath()+"/sockjs/"+file+".ws");
                }
            }else{
                if ("WebSocket" in window){
                    ws=new WebSocket(wsPath()+"/ws_showLog.json");
                }else{
                    ws=new SockJS(wsPath()+"/sockjs/ws_showLog.ws");
                }
            }
    
            $('#text').append("<br/>开始建立连接...<br/>");
            ws.onopen=function(event){
                $('#text').append("连接已建立..<br/>");
                $('#start').linkbutton('disable');
            }
            /**接收消息*/
            ws.onmessage=function(event){
                //console.log(event);
                var msg=event.data;
                $('#text').append(msg);
                // 滚动条滚动到最低部
                try {
                    $("#text").scrollTop($("#text")[0].scrollHeight);
                } catch (e) {
                    console.error(e);
                }
            }
            ws.onclose=function(){
                $('#text').append("连接已关闭..<br/>");
                $('#start').linkbutton('enable');
    
            }
            ws.onerror = function(){
                $('#text').append("发生了错误..<br/>");
                $('#start').linkbutton('enable');
            }
    
        }
    
        /**
         * 清除屏幕
         */
        function cleanText() {
            $('#text').html("屏幕已清除...<br/>");
        }
    
    </script>
    
    </body>
    </html>
    

    后台代码

    WebSocketInterceptor拦截器

    package com.faker.filter;
    
    import com.faker.app.util.ToolsUtil;
    import org.apache.log4j.Logger;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.server.HandshakeInterceptor;
    
    import javax.servlet.http.HttpSession;
    import java.util.Map;
    
    /**
     * <p>Title: WebSocketInterceptor拦截器 - WebSocketInterceptor_ShowLog</p>
     *
     * <p>Description:WebSocket 适配器 拦截器 显示tomcat日志的拦截器</p>
     *
     * <p>Copyright: Copyright Faker(c) 2018</p>
     *
     * <p>Company: 无</p>
     *
     * @author Anlinxi
     *
     * @version 1.0
     */
    public class WebSocketInterceptor_ShowLog implements HandshakeInterceptor {
    
        /**
         * 日志
         */
        private final static Logger log= Logger.getLogger(WebSocketInterceptor_ShowLog.class);
    
        /**
         * 握手前
         * @param request 请求
         * @param response 响应
         * @param handler ws握手
         * @param attributes 属性
         * @return 是否握手成功
         * @throws Exception 异常
         */
        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler,
                                       Map<String, Object> attributes) throws Exception {
            log.info("执行握手: "+handler+"map: "+attributes.values());
            if(request != null){
                ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                HttpSession session = servletRequest.getServletRequest().getSession();
                if(session!=null){
                    String userId = (String) session.getAttribute("WEBSOCKET_SL_ID");
                    if(userId!=null){
                        session.setAttribute("SESSION_SL_ID", userId);
                        attributes.put("WEBSOCKET_SL_ID", userId);
                    }else{
                        session.setAttribute("SESSION_SL_ID", session.getId());
                        attributes.put("WEBSOCKET_SL_ID", session.getId());
                    }
                }
            }
            return true;
        }
    
        /**
         * 将属性设置到session里
         * @param session session
         * @param attributes Map
         * @param attributeName 待设置的属性名
         * @param attribute 待设置的属性
         */
        private void setAttributes(HttpSession session,Map<String, Object> attributes,String attributeName,String attribute){
            if (ToolsUtil.isNotNull(attribute)) {
                String attributeNameUp = attributeName.toUpperCase();
                session.setAttribute("SESSION_"+attributeNameUp, attribute);
                attributes.put("WEBSOCKET_"+attributeNameUp, attribute);
            }
        }
    
        /**
         * 握手后
         * @param request 请求
         * @param response 响应
         * @param handler ws握手
         * @param exceptions 错误
         */
        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Exception exceptions) {
            log.info("握手后: "+handler+"exceptions: "+exceptions);
        }
    
    }
    
    
    

    WebSocketHandler握手动作

    package com.faker.filter;
    import com.faker.app.util.CpdetectorUtils;
    import com.faker.app.util.TailLogThread;
    import com.faker.app.util.ToolsUtil;
    import net.sf.json.JSONObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    import org.springframework.web.util.HtmlUtils;
    
    import java.io.*;
    import java.util.*;
    
    import static java.lang.Thread.sleep;
    
    /**
     * <p>Title: Websocket处理器 - WebSocketHandler_ShowLog</p>
     *
     * <p>Description:统一发送信息等操作的</p>
     *
     * <p>Copyright: Copyright Faker(c) 2018</p>
     *
     * <p>Company: 无</p>
     *
     * @author Anlinxi
     *
     * @version 1.0
     */
    public class WebSocketHandler_ShowLog extends TextWebSocketHandler {
    
        private final static Logger logger = LoggerFactory.getLogger(WebSocketHandler_ShowLog.class);
    
        /**储存分组信息 分组名:用户分组list*/
        public static final Map<Object, WebSocketSession> userSocketSessionMap;
        static {
            userSocketSessionMap = new HashMap<Object, WebSocketSession>();
        }
    
        /**实例化的类*/
        public static WebSocketHandler_ShowLog webSocketHandler;
        /**运行的日志程序cmd*/
        private static Process process;
        /**输入流*/
        private static InputStream inputStream;
    
        /**
         * 实例化方法
         * @return webSocketHandler
         */
        public static WebSocketHandler_ShowLog getBeans(){
            if(webSocketHandler == null){
                webSocketHandler = new WebSocketHandler_ShowLog();
            }
            return webSocketHandler;
        }
    
        /**
         * 处理前端发送的文本信息
         * js调用websocket.send时候,会调用该方法
         * @param session session
         * @param message 发送信息
         * @throws Exception 异常
         */
        @Override
        protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
            if(message.getPayloadLength()==0){
                return;
            }
            // message.getPayload().toString() 获取消息具体内容
            Map<String, Object> msg = JSONObject.fromObject(message);
            logger.info("handleMessage......."+message.getPayload()+"..........."+msg);
            session.sendMessage(message);
            // 调用方法(发送消息给所有人)
            this.sendMessageToAllUsers(msg.get("msgContent").toString());
        }
    
        /**
         * 当新连接建立的时候,被调用
         * 连接成功时候,会触发页面上onOpen方法
         * @param session session
         * @throws Exception 异常
         */
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            String uid = (String) session.getAttributes().get("WEBSOCKET_SL_ID");
            if (userSocketSessionMap.get(uid) == null) {//获取用户id
                uid = session.getId();
                userSocketSessionMap.put(uid, session);
            }
            logger.info("======"+uid+" 建立连接完成======");
            //读取配置文件里日志文件地址
            String fileAddr = ToolsUtil.getInstence().getProperties("myConfig","log.tomcat.catalina");
            if(ToolsUtil.isNotNull(fileAddr)){
                //读取现有日志信息
                this.readCatalinaFile(fileAddr,session);
                //进程为空或者未激活时执行cmd命令
                // 执行tail -f命令
                String cmd = "tail -f "+fileAddr;
                try {
                    process = Runtime.getRuntime().exec(cmd);
                    inputStream = process.getInputStream();
                    if(process.isAlive()){
                        // 一定要启动新的线程,防止InputStream阻塞处理WebSocket的线程
                        TailLogThread thread = new TailLogThread(inputStream, session );
                        thread.start();
                    }
                } catch (IOException e) {
                    //logger.error(cmd);
                    System.err.println(cmd);
                    e.printStackTrace();
                    this.close();
                }
    
            }else{
                logger.error("======未配置myConfig文件中log.tomcat.catalina属性(tomcat日志路径)======");
            }
        }
    
        /**
         * 读取现有日志信息
         * @param fileAddr 文件地址
         * @param session WebSocketSession
         */
        private void readCatalinaFile(String fileAddr, WebSocketSession session) {
            Map<String,Object> result = new HashMap<>(4);
            FileInputStream inStream  = null;
            try {
                File file = new File(fileAddr);
                //如果文件夹不存在,先创建文件夹
                if(!file.exists()){
                    System.err.println("文件不存在");
                    return;
                }
                inStream = new FileInputStream(file);
                String bm =  CpdetectorUtils.getInstence().getFileOrIOEncode(fileAddr,"file");
                InputStreamReader isr = new InputStreamReader(inStream, bm);
                BufferedReader reader = new BufferedReader(isr);
                String pythonText;
                //读取的一行
                String readline;
                List<String> sbList = new ArrayList();
                while((readline = reader.readLine())!=null){
                    sbList.add(this.setColorToMsg(readline));
                }
                reader.close();
                int rowsNum  = sbList.size();
                int sendNum  = 0;
                StringBuffer sb = new StringBuffer();
                for(String l:sbList){
                    if(1<=rowsNum&&rowsNum<=400){
                        sb.append(l);
                    }
                    if(sb.length()>4096){
                        session.sendMessage(new TextMessage(sb.toString()));
                        sb.setLength(0);
                        sendNum++;
                        try {
                            //发送一次的时间间隔,给浏览器反映的时间
                            sleep(20);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    else if(rowsNum<=1&&sb.length()>0){
                        session.sendMessage(new TextMessage(sb.toString()));
                        sb.setLength(0);
                        sendNum++;
                    }
                    rowsNum--;
                }
    
                logger.info("原始日志共计"+sbList.size()+"行。发送信息"+sendNum+"次。");
                session.sendMessage(new TextMessage(System.lineSeparator()+System.lineSeparator()+"最近日志加载完毕......"+System.lineSeparator()+System.lineSeparator()));
                inStream.close();
    
            } catch (IllegalStateException | IOException e) {
                e.printStackTrace();
            }
            return;
        }
    
        /**
         * 给日志上色!
         * @param msg 日志消息
         * @return html
         */
        private String setColorToMsg(String msg){
            String color = "";
            if(msg.toLowerCase().contains("error")){
                color = "#ff0000";
            }else if(msg.toLowerCase().contains("warn")){
                color = "#ff9900";
            }else if(msg.toLowerCase().contains("debug")){
                color = "#336600";
            }else if(msg.toLowerCase().contains("info")){
                color = "#0033cc";
            }else if(msg.contains("定时清除游离文件中(时间间隔5分钟)")){
                color = "#C1CDCD";
            }else if(msg.contains("Exception")){
                color = "#FFD700";
            }
            //对文本中含有的html代码进行转义
            msg = HtmlUtils.htmlEscape(msg);
            String html = "<p class='log_msg' style='color:"+color+"'>"+msg+"</p>";
            return html;
        }
    
        /**
         * 当连接关闭时被调用
         * @param session session
         * @param status 连接状态
         * @throws Exception 异常
         */
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            logger.info("Websocket:" + session.getId() + "已经关闭");
            Iterator<Map.Entry<Object, WebSocketSession>> it = userSocketSessionMap
                    .entrySet().iterator();
            // 移除Socket会话
            logger.info("======关闭连接======");
            while (it.hasNext()) {
                Map.Entry<Object, WebSocketSession> entry = it.next();
                if (entry.getValue().getId().equals(session.getId())) {
                    userSocketSessionMap.remove(entry.getKey());
                    logger.warn("Socket会话已经移除:用户ID" + entry.getKey());
                    break;
                }
            }
            this.close();
        }
    
        /**
         * 关闭程序和流
         */
        private void close(){
    //        logger.info("关闭ws的程序和流!");
    //        try {
    //            if(inputStream != null)
    //                inputStream.close();
    //        } catch (Exception e) {
    //            e.printStackTrace();
    //        }
    //        if(process != null){
    //            process.destroy();
    //        }
        }
    
        /**
         * 传输错误时调用
         * @param session session
         * @param exception 异常
         * @throws Exception 异常
         */
        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            if (session.isOpen()) {
                session.close();
            }
            Iterator<Map.Entry<Object, WebSocketSession>> it = userSocketSessionMap
                    .entrySet().iterator();
            logger.info("======消息传输错误======");
            // 移除Socket会话
            while (it.hasNext()) {
                Map.Entry<Object, WebSocketSession> entry = it.next();
                if (entry.getValue().getId().equals(session.getId())) {
                    userSocketSessionMap.remove(entry.getKey());
                    logger.error("Socket会话已经移除:用户ID" + entry.getKey());
                    break;
                }
            }
            this.close();
        }
    
        /**
         * 给所有在线用户发送消息
         * @param message 发送消息
         */
        public void sendMessageToAllUsers(String message) {
            Iterator<Map.Entry<Object, WebSocketSession>> it = userSocketSessionMap
                    .entrySet().iterator();
            logger.info("======群发======");
            // 多线程群发
            while (it.hasNext()) {
                final Map.Entry<Object, WebSocketSession> entry = it.next();
                if (entry.getValue().isOpen()) {
                    new Thread(new Runnable() {
                        public void run() {
                            try {
                                if (entry.getValue().isOpen()) {
                                    entry.getValue().sendMessage(new TextMessage(message));
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                }
            }
        }
    
    
        /**
         * 给某个用户发送消息
         * @param userId 用户id
         * @param message 发送信息
         */
        public void sendMessageToUser(String userId,String message) throws IOException {
            WebSocketSession session = userSocketSessionMap.get(userId);
            logger.info("======给某个用户发送消息======");
            if (session != null && session.isOpen()) {
                session.sendMessage(new TextMessage(message));
            }
        }
    
    }
    

    WebSocketConfig配置类

    package com.faker.filter;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
    import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
    
    /**
     * <p>Title: websocket配置类 - WebSocketConfig_ShowLog</p>
     *
     * <p>Description:websocket日志显示的配置信息</p>
     *
     * <p>Copyright: Copyright Faker(c) 2018</p>
     *
     * <p>Company: 无</p>
     *
     * @author Anlinxi
     *
     * @version 1.0
     */
    @Configuration
    //这个标注可以不加,如果有加,要extends WebMvcConfigurerAdapter
    @EnableWebMvc
    @EnableWebSocket
    public class WebSocketConfig_ShowLog extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
    
        /**
         * 注册websocket的信息
         * @param registry registry
         */
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            //设置websocket的地址,注册Handler,注册Interceptor
            registry.addHandler(SocketHandler(), "/ws_showLog.json").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor_ShowLog());
            //注册SockJS,提供SockJS支持(主要是兼容ie8)
            registry.addHandler(SocketHandler(), "/sockjs/ws_showLog.ws")
                    //允许跨域
                    .setAllowedOrigins("*")
                    //注册Interceptor
                    .addInterceptors(new WebSocketInterceptor_ShowLog())
                    //支持sockjs协议
                    .withSockJS();
        }
    
        @Bean
        public org.springframework.web.socket.WebSocketHandler SocketHandler(){
            return new com.faker.filter.WebSocketHandler_ShowLog ();
        }
    
    
        /**
         * 配置webSocket引擎
         * 用于tomcat 可以不配置
         * @return Bean
         */
        @Bean
        public ServletServerContainerFactoryBean createWebSocketContainer() {
            ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
            container.setMaxTextMessageBufferSize(8192);
            container.setMaxBinaryMessageBufferSize(8192);
            return container;
        }
    
    }
    
    

    TailLogThread实时读取日志的线程

    package com.faker.app.util;
    import org.apache.commons.codec.StringEncoder;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.util.HtmlUtils;
    
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class TailLogThread extends Thread {
    
        /**读取缓冲*/
        private BufferedReader reader;
        /**WebSocketSession*/
        private WebSocketSession session;
        /**消息队列*/
        private List<String> msgs = new ArrayList<>();
        /**cmd的文本编码*/
        private String encoding;
    
        public TailLogThread(InputStream in, WebSocketSession session) {
            this.reader = new BufferedReader(new InputStreamReader(in));
            this.session = session;
    
        }
    
        /**
         * 读取日志的方法
         */
        @Override
        public void run() {
            this.method1();
        }
    
        /**
         * 原始读取日志的方法
         */
        private void method1(){
            String line;
            try {
                if(session.isOpen()){
                    //如果消息队列有数据,则先输出消息队列消息
                    if(msgs.size()>0){
                        List<String> errMsgs = new ArrayList<>();
                        for(String msg:msgs){
                            try {
                                //输出消息队列消息
                                this.sendMsg(msg,session);
                            } catch (IOException e) {
                                //未发送的信息储存在错误消息队列中
                                System.err.println(e.getMessage());
                                errMsgs.add(msg);
                            }
                        }
                        //清空消息队列
                        msgs.clear();
                        //如果有错误的消息队列,储存给消息队列中
                        if(errMsgs.size()>0){
                            msgs = errMsgs;
                        }
                    }
                }
                while((line = reader.readLine()) != null) {
                    if(encoding==null||encoding==""){
                        encoding = this.StringEncoding(line);
                    }
                    //String text = new String(line.getBytes(encoding),"UTF-8");
                    // 将实时日志通过WebSocket发送给客户端,给每一行添加一个HTML换行
                    if(session.isOpen()){
                        this.sendMsg(line,session);
                    }else{
                        msgs.add(line);
                    }
                }
            } catch (IOException e) {
                System.err.println(e.getMessage());
                e.printStackTrace();
            }
        }
    
        /**
         * 发送消息
         * @param msg
         * @param session
         * @throws IOException
         */
        private void sendMsg(String msg,WebSocketSession session) throws IOException {
            String str = msg;
            if(1==1){
                str = this.setColorToMsg(msg);
            }else{
                //文本域
                str = str + System.lineSeparator();
            }
            session.sendMessage(new TextMessage(str));
        }
    
        /**
         * 判断字符串编码
         * @param sb 字符串
         * @return 编码类型
         */
        private String StringEncoding(String sb){
            String backEncoding = "";
            String iso8859 = this.testEncoding(sb,"iso8859-1");
            String gbk = this.testEncoding(sb,"gbk");
            String utf8 = this.testEncoding(sb,"utf-8");
            if(iso8859.equals(sb.toString())){
                backEncoding = "iso8859";
            }else  if(gbk.equals(sb.toString())){
                backEncoding = "gbk";
            }else  if(utf8.equals(sb.toString())){
                backEncoding = "utf8";
            }
            return backEncoding;
        }
    
        /**
         * 给日志上色!
         * @param msg 日志消息
         * @return html
         */
        private String setColorToMsg(String msg){
            String color = "";
            if(msg.toLowerCase().contains("error")){
                color = "#ff0000";
            }else if(msg.toLowerCase().contains("warn")){
                color = "#ff9900";
            }else if(msg.toLowerCase().contains("debug")){
                color = "#336600";
            }else if(msg.toLowerCase().contains("info")){
                color = "#0033cc";
            }else if(msg.contains("定时清除游离文件中(时间间隔5分钟)")){
                color = "#C1CDCD";
            }else if(msg.contains("Exception")){
                color = "#FFD700";
            }
            //对文本中含有的html代码进行转义
            msg = HtmlUtils.htmlEscape(msg);
            String html = "<p class='log_msg' style='color:"+color+"'>"+msg+"</p>";
            return html;
        }
    
        /**
         * 测试编码
         * @param sb 文本
         * @param encoding 编码
         * @return
         */
        private String testEncoding(String sb,String encoding){
            String tmpStrin = null;
            try {
                tmpStrin = new String(sb.toString().getBytes(encoding));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return tmpStrin;
        }
    }
    
    

    ToolsUtil工具类

    package com.faker.app.util;
    
    import com.sun.istack.internal.NotNull;
    import net.sf.json.JSONArray;
    import net.sf.json.JSONObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.*;
    
    /**
     * <p>Title: 常用的工具类 - ToolsUtil</p>
     *
     * <p>Description:常用工具放这儿</p>
     *
     * <p>Copyright: Copyright Faker(c) 2018</p>
     *
     * <p>Company: 无</p>
     *
     * @author Anlinxi
     *
     * @version 1.0
     */
    public class ToolsUtil {
    
        /**
         * 工具类实例化对象
         */
        private static ToolsUtil toolsUtil = null;
    
        /**
         * 私有化构造函数防止new对象消耗内存
         */
        private ToolsUtil() {
    
        }
        /**
         * 静态实例化方法获取实例化对象
         * 使用同一个lCsinginUtil,减少内存占用
         * @return LCsinginUtil
         */
        public static ToolsUtil getInstence(){
            if(toolsUtil==null){
                toolsUtil = new ToolsUtil();
            }
            return toolsUtil;
        }
    
        /**
         * 日志
         */
        private final static Logger logger = LoggerFactory.getLogger(ToolsUtil.class);
    
        /**
         * 发送json数据
         * @param response 响应
         * @param object 发送的对象
         */
        public static void WriterJson(@NotNull HttpServletResponse response, Object object){
            try {
                String backJson = null;
                if(object instanceof List){
                    JSONArray jsonArray = JSONArray.fromObject(object);
                    backJson = jsonArray.toString();
                }else{
                    JSONObject jsonObject = JSONObject.fromObject(object);
                    backJson = jsonObject.toString();
                }
                response.setHeader("Cache-Control", "no-cache");
                response.setHeader("Access-Control-Allow-Origin", "*");
                response.setContentType("text/json;charset=utf-8");
                PrintWriter printWriter = response.getWriter();
                logger.info("发送json数据>>>>>>>>>>>>>>>>"+backJson);
                printWriter.write(backJson);
                printWriter.flush();
                printWriter.close();
            } catch (IOException e) {
                logger.error("发送json数据失败");
                e.printStackTrace();
            }
        }
    
    
        /**
         * 判断一个字符串是否为null或空字符,是则返回false,否为true
         * @param str 字符串对象
         * @return 是否为null或空字符
         */
        public static boolean isNotNull(Object str){
            return !isNullOrEmpty(str);
        }
    
        /**
         * 判断一个字符串是否为null或空字符,是则返回true,否为false
         * @param str 字符串对象
         * @return 是否为null或空字符
         */
        public static boolean isNullOrEmpty(Object str){
            String t = trim(str);
            if(t.equals("") || t==null){
                return true;
            } else {
                return false;
            }
        }
    
        /**
         * 字符串对象去空
         * @param str 字符串对象
         * @return 替换空
         */
        public static String trim(Object str){
            return trim(str, "");
        }
    
        /**
         * 将对象转换为字符串类型
         * @param obj 对象
         * @param nullToString 如果为空就返回该默认值
         * @return 字符串类型
         */
        public static String trim(Object obj, String nullToString){
            return obj == null ? nullToString : obj.toString().trim();
        }
    
        /**
         * 读取python文件代码
         * @param fileName 文件名(含后缀)
         * @param path 文件夹路径
         * @return 读取的字符串
         */
        public Boolean download( HttpServletResponse response,String fileName, String path){
            FileInputStream inStream  = null;
            try {
                inStream = new FileInputStream(path+fileName);
                //通过available方法取得流的最大字符数
                byte[] inOutb = new byte[inStream.available()];
                //读入流,保存在byte数组
                inStream.read(inOutb);
                //设置文件类型
                response.setContentType(Files.probeContentType(Paths.get(path+fileName)));
                //将response提取流
                OutputStream out = response.getOutputStream();
                //将byte数组写入response中
                out.write(inOutb);
                //刷新流
                out.flush();
                out.close();
                inStream.close();
            } catch (Exception e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
                return false;
            }
            return true;
        }
    
        /**
         * 获取系统信息
         */
        Properties prop = System.getProperties();
    
        /**
         * 获取当前服务器所在系统的路径
         * @return 服务器的路径
         */
        public String classRootPath() {
            //确定jython目录的路径
            String classRootPath = this.getClass().getResource("/").toString();
            String os = prop.getProperty("os.name");
            if (os != null && os.toLowerCase().indexOf("linux") > -1) {
                return classRootPath.replace("file:", "");
            } else {
                return classRootPath.replace("file:/", "");
            }
        }
    
        /**
         * 获取properties文件配置信息
         * @param fileName 文件名(不含后缀)
         * @param key 取值对应的键名
         * @return 值
         * @throws Exception 异常
         */
        public String getProperties (String fileName,String key) throws Exception {
            //文件名不为空
            if(fileName!=null&&!"".equals(fileName)){
                //不需要后缀
                if(fileName.contains(".properties")){
                    fileName.replace(".properties","");
                }
                if(key!=null&&!"".equals(key)){
                    //properties文件放置的位置
                    String fileAddr = "config/"+fileName;
                    ResourceBundle resource = ResourceBundle.getBundle(fileAddr);
                    String text = new String(resource.getString(key).getBytes("ISO-8859-1"), "UTF8");
                    return text;
                }else{
                    throw new Exception("取值键名为空!");
                }
            }else{
                throw new Exception("文件名为空!");
            }
        }
    }
    
    

    给日志上色的方法

    这个方法是去匹配字符串,不是原本的错误就是红色,所以可能有误差。配色也不会配,颜色区分这些还是前端要敏感一些。

    	/**
         * 给日志上色!
         * @param msg 日志消息
         * @return html
         */
        private String setColorToMsg(String msg){
            String color = "";
            if(msg.toLowerCase().contains("error")){
                color = "#ff0000";
            }else if(msg.toLowerCase().contains("warn")){
                color = "#ff9900";
            }else if(msg.toLowerCase().contains("debug")){
                color = "#336600";
            }else if(msg.toLowerCase().contains("info")){
                color = "#0033cc";
            }else if(msg.contains("定时清除游离文件中(时间间隔5分钟)")){
                color = "#C1CDCD";
            }else if(msg.contains("Exception")){
                color = "#FFD700";
            }
            //对文本中含有的html代码进行转义
            msg = HtmlUtils.htmlEscape(msg);
            String html = "<p class='log_msg' style='color:"+color+"'>"+msg+"</p>";
            return html;
        }
        ```
    
    展开全文
  • 提示:这里只是配置,没有对其实现...javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> <

    提示:这里只是配置,没有对其实现和功能进行说明

    主要是读取log.txt文件再通过websocket回显到前端
    大家用的话注意文件路径就行了

    依赖

    <dependency>
        <groupId>javax.websocket</groupId>
        <artifactId>javax.websocket-api</artifactId>
        <version>1.1</version>
        <scope>provided</scope>
    </dependency>
    

    Log4j配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <!--这是打印在控制台的-->
        <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
            <param name="Encoding" value="UTF-8"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n"/>
            </layout>
        </appender>
        <!--这是输出到文件的-->
        <appender name="StreamOperateFile" class="org.apache.log4j.RollingFileAppender">
            <param name="File" value="G:/Log/log.txt" />
            <param name="Append" value="false"/>
            <param name="MaxFileSize" value="10MB"/>
            <param name="MaxBackupIndex" value="20"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n"/>
            </layout>
        </appender>
        <logger name="java.sql">
            <level value="info"/>
            <appender-ref ref="StreamOperateFile" />
            <appender-ref ref="STDOUT"/>
        </logger>
        <logger name="org.apache.ibatis">
            <level value="info"/>
            <appender-ref ref="StreamOperateFile" />
            <appender-ref ref="STDOUT"/>
        </logger>
        <root>
            <level value="debug"/>
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="StreamOperateFile" />
        </root>
        <logger name="StreamOperateFile" additivity="false">
            <level value="debug" />
            <appender-ref ref="StreamOperateFile" />
            <appender-ref ref="console" />
        </logger>
    </log4j:configuration>
    

    java类

    package com.live.component;
    
    import org.springframework.stereotype.Component;
    
    import javax.websocket.Session;
    import java.io.*;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    @Component
    public class ThreadLogComponent extends Thread {
    
        private Session session;
    //    private long lastTimeFileSize = 0;
    
    
        public void read(Session session) {
            this.session = session;
        }
    
        public void realtimeShowLog(File log) throws FileNotFoundException {
            //指定文件可读可写
    //        final RandomAccessFile randomAccessFile = new RandomAccessFile(log, "rw");
            //启动一个线程每10秒钟读取新增的日志信息
            InputStreamReader reader = new InputStreamReader(new FileInputStream(log));
            BufferedReader bufferedReader = new BufferedReader(reader);
            ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
            exec.scheduleWithFixedDelay(() -> {
                try {
                    //获得变化部分的
    //                randomAccessFile.seek(lastTimeFileSize);
                    String tmp = "";
                    while ( (tmp = bufferedReader.readLine()) != null) {
                        session.getBasicRemote().sendText(tmp+"<br>");
    //                    lastTimeFileSize = randomAccessFile.length();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }, 0, 1, TimeUnit.SECONDS);
    
        }
    
    }
    
    
    package com.live.component;
    import org.springframework.stereotype.Component;
    
    import javax.websocket.*;
    import javax.websocket.server.ServerEndpoint;
    import java.io.File;
    import java.io.IOException;
    
    @ServerEndpoint(value = "/log_websocket")
    @Component
    public class WebSocketController {
    
        @OnOpen
        public void openConnection(Session session) {
            try {
                String path = "G:/Log/log.txt";
                File file = new File(path);
                ThreadLogComponent thread = new ThreadLogComponent();
                thread.read(session);
                thread.realtimeShowLog(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @OnClose
        public void onClose() {
        }
    
        @OnError
        public void error(Session session, Throwable error) {
            error.printStackTrace();
        }
    
    
    
          /* @OnMessage
        public void onMessage(Session session, String message) throws IOException {
            System.out.println("收到的用户的消息:"+message);
            session.getBasicRemote().sendText("发送给用户的消息");
        }*/
    
    }
    
    

    HTML

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:th="http://www.thymeleaf.org" lang="en">
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link rel="stylesheet" type="text/css" th:href="@{/static/css/bootstrap.min.css}"/>
        <link rel="stylesheet" type="text/css" th:href="@{/static/css/self/demo.css}"/>
        <title>实时日志</title>
    </head>
    
    <body class="body-left-hidden">
    <div id="log-container" style="height: 100vh; overflow-y: scroll; background: #333; color: #aaa; padding: 10px;">
        <div>
        </div>
    </div>
    </body>
    <script th:src="@{/static/js/jquery-3.4.1.min.js}"></script>
    <script th:src="@{/static/js/bootstrap.min.js}"></script>
    <script th:src="@{/static/js/bootstrap.bundle.min.js}"></script>
    <script th:src="@{/static/js/self/demo.js}"></script>
    <script th:src="@{/static/js/log/log.js}"></script>
    </html>
    
    

    JavaScript

    console.log(window.location.pathname);
    let websocket = null;
    if (window.location.pathname === "/log_websocket") {
        console.log(1);
        websocket = new WebSocket('ws://localhost:8080/log_websocket');
        $(document).ready(function() {
            // 指定websocket路径
            websocket.onmessage = function(event) {
                // 接收服务端的实时日志并添加到HTML页面中
                $("#log-container div").append(event.data);
                // 滚动条滚动到最低部
                $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
            };
        });
    }
    window.onbeforeunload = function(){
        if (websocket!=null) {
            websocket.close();
        }
    }
    
    /*
    const message = $("#message");
    const ws = new WebSocket("ws://localhost:8080/websocket");
    window.onbeforeunload = function(){
        ws.close();
    }
    ws.onopen = function(e) {
        ws.send("客户端连接成功");
    }
    ws.onclose = function(e) {
        console.log("Connection close...")
    }
    ws.onmessage = function(e) {
        $("#result").text("收到消息:"+e.data)
    }
    $("#send").on("click",function() {
        ws.send($(message).val());
    })*/
    
    

    效果

    当有新的日志了,也会实时显示
    在这里插入图片描述

    展开全文
  • spring boot集成WebSocket实时输出日志到web页面

    万次阅读 热门讨论 2017-11-22 21:36:49
    今天来做个有趣的东西,就是实时将系统日志输出的前端web页面,因为是实时输出,所有第一时间就想到了使用webSocket,而且在spring boot中,使用websocket超级方便,阅读本文,你会接触到以下关键词相关技术,...

    前言碎语
    今天来做个有趣的东西,就是实时将系统日志输出的前端web页面,因为是实时输出,所有第一时间就想到了使用webSocket,而且在spring boot中,使用websocket超级方便,阅读本文,你会接触到以下关键词相关技术,WebSocket(stopmp服务端),stomp协议,sockjs.min.js,stomp.min.js(stomp客户端),本文使用到的其实就是使用spring boot自带的webSocket模块提供stomp的服务端,前端使用stomp.min.js做stomp的客户端,使用sockjs来链接,前端订阅后端日志端点的消息,后端实时推送,达到日志实时输出到web页面的目的,效果如下图


    首先了解下stomp?
    STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
    STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。
    STOMP是一个非常简单和容易实现的协议,其设计灵感源自于HTTP的简单性。尽管STOMP协议在服务器端的实现可能有一定的难度,但客户端的实现却很容易。例如,可以使用Telnet登录到任何的STOMP代理,并与STOMP代理进行交互。

    下面是具体的步骤,主要是日志信息的获取和日志信息的推送,不多说,上代码
    一.引入spring boot websocket依赖


    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-websocket</artifactId>
    		</dependency>


    二.新增日志消息实体
    /**
     * Created by kl on 2017/10/9.
     * Content :日志消息实体,注意,这里为了减少篇幅,省略了get,set代码
     */
    public class LoggerMessage{
    
        private String body;
        private String timestamp;
        private String threadName;
        private String className;
        private String level;
    
        public LoggerMessage(String body, String timestamp, String threadName, String className, String level) {
            this.body = body;
            this.timestamp = timestamp;
            this.threadName = threadName;
            this.className = className;
            this.level = level;
        }
    
        public LoggerMessage() {
        }
    }
    三. 创建一个阻塞队列,作为日志系统输出的日志的一个临时载体
    public class LoggerQueue {
        //队列大小
        public static final int QUEUE_MAX_SIZE = 10000;
        private static LoggerQueue alarmMessageQueue = new LoggerQueue();
        //阻塞队列
        private BlockingQueueblockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
    
        private LoggerQueue() {
        }
    
        public static LoggerQueue getInstance() {
            return alarmMessageQueue;
        }
        /**
         * 消息入队
         * @param log
         * @return
         */
        public boolean push(LoggerMessage log) {
            return this.blockingQueue.add(log);//队列满了就抛出异常,不阻塞
        }
        /**
         * 消息出队
         * @return
         */
        public LoggerMessage poll() {
            LoggerMessage result = null;
            try {
                result = this.blockingQueue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return result;
        }
    }
    四.获取logback的日志,塞入日志队列中
    1.定义Logfilter拦截输出日志
    public class LogFilter extends Filter{
        @Override
        public FilterReply decide(ILoggingEvent event) {
            LoggerMessage loggerMessage = new LoggerMessage(
                    event.getMessage()
                    , DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
                    event.getThreadName(),
                    event.getLoggerName(),
                    event.getLevel().levelStr
            );
            LoggerQueue.getInstance().push(loggerMessage);
            return FilterReply.ACCEPT;
        }
    }  

    2.配置logback.xml,添加我们自定义的filter


    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true">
        <include resource="org/springframework/boot/logging/logback/defaults.xml" />
        <property name="LOG_FILE"
                  value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}" />
        <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
                <charset>utf8</charset>
            </encoder>
            <filter class="com.example.websocket.LogFilter"></filter>
        </appender>
        <root level="INFO">
            <appender-ref ref="FILE" />
            <appender-ref ref="CONSOLE" />
        </root>
    </configuration>


    五.配置WebSocket消息代理端点,即stomp服务端
    @Configuration
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/websocket")
                    .setAllowedOrigins("http://localhost:8976")
                    .withSockJS();
        }
    }
    注意:为了连接安全,setAllowedOrigins设置的允许连接的源地址,如果在非这个配置的地址下发起连接会报403,进一步还可以使用addInterceptors设置拦截器,来做相关的鉴权操作
    六.启动类,开启webSocket消息代理功能,并推送日志信息
    @SpringBootApplication
    @EnableScheduling
    @EnableWebSocketMessageBroker
    public class WebsocketApplication {
    	private Logger logger = LoggerFactory.getLogger(WebsocketApplication.class);
    	public static void main(String[] args) {
    		SpringApplication.run(WebsocketApplication.class, args);
    	}
    	@Autowired
    	private SimpMessagingTemplate messagingTemplate;
        int info=1;
    	@Scheduled(fixedRate = 1000)
    	public void outputLogger(){
          logger.info("测试日志输出"+info++);
    	}
    	/**
    	 * 推送日志到/topic/pullLogger
    	 */
    	@PostConstruct
    	public void pushLogger(){
    		ExecutorService executorService=Executors.newFixedThreadPool(2);
    		Runnable runnable=new Runnable() {
    			@Override
    			public void run() {
    				while (true) {
    					try {
    						LoggerMessage log = LoggerQueue.getInstance().poll();
    						if(log!=null){
    							if(messagingTemplate!=null)
    							messagingTemplate.convertAndSend("/topic/pullLogger",log);
    						}
    					} catch (Exception e) {
    						e.printStackTrace();
    					}
    				}
    			}
    		};
    		executorService.submit(runnable);
    		executorService.submit(runnable);
    	}
    }

    七.html页面,连接stomp服务端,订阅/topic/pullLogger的消息,展示日志信息

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>WebSocket Logger</title>
        <script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.js"></script>
        <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
        <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    </head>
    <body>
    <button οnclick="openSocket()">开启日志</button><button οnclick="closeSocket()">关闭日志</button>
    <div id="log-container" style="height: 450px; overflow-y: scroll; background: #333; color: #aaa; padding: 10px;">
        <div></div>
    </div>
    </body>
    <script>
        var stompClient = null;
        $(document).ready(function() {openSocket();});
        function openSocket() {
            if(stompClient==null){
                var socket = new SockJS('http://localhost:8084/websocket?token=kl');
                stompClient = Stomp.over(socket);
                stompClient.connect({token:"kl"}, function(frame) {
                    stompClient.subscribe('/topic/pullLogger', function(event) {
                        var content=JSON.parse(event.body);
                        $("#log-container div").append(content.timestamp +" "+ content.level+" --- ["+ content.threadName+"] "+ content.className+"   :"+content.body).append("<br/>");
                        $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
                    },{
                        token:"kltoen"
                    });
                });
            }
        }
        function closeSocket() {
            if (stompClient != null) {
                stompClient.disconnect();
                stompClient=null;
            }
        }
    </script>
    </body>
    </html>

    参考地址:
    stomp.js客户端: http://jmesnil.net/stomp-websocket/doc/
    scok.js客户端: https://github.com/sockjs/sockjs-client
    spring webSocket: https://docs.spring.io/spring/docs/
    展开全文
  • websocket日志实时推送

    2018-12-24 17:23:19
    websocket日志实时推送,web网站,利用tail -f 和websocket实时推送到页面
  • 功能:用websocket技术,在运维工具的浏览器上实时显示远程服务器上的日志信息 一般我们在运维工具部署环境的时候,需要实时展现部署过程中的信息,或者在浏览器中实时显示程序日志给开发人员看。你还在用ajax每隔...
  • 功能:用websocket技术,在运维工具的浏览器上实时显示远程服务器上的日志信息 一般我们在运维工具部署环境的时候,需要实时展现部署过程中的信息,或者在浏览器中实时显示程序日志给开发人员看。你还在用ajax每隔段...
  • 本文介绍一个基于websocket实现的远程实时日志系统,可以通过浏览器查看远程移动设备的实时运行... 浏览器日志查看页面:与服务器建立websocket连接,通过websocket接收指定设备的实时运行日志显示3. 移动设备:...
  • 根据运维提出的需求,服务发生异常时,每次都登录服务器查看日志有些费时费力 websocket 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息 引入依赖 <dependency> <groupId>org....
  • 前言最近需要做一个新功能,要求在浏览器可以看到服务器上的日志文件的内容,并且实时显示,也就是相当于要在浏览器实现Linux下的tail -f 的功能。 最开始的思路是使用Ajax定时向后端请求数据并进行展示,但是这样...
  • websocket 配置 package com.sitech.cmap.comp.wsg.cntr.oracle.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org....
  • 在我们线上项目中,版本迭代很多操作需要重新、关闭、开启应用程序,这样我们...如果要实时反馈的话,这里提供解决方案是使用websocket + sh 命令tail 这样来实时获取日志文件里面信息,java 获取到对应流信息并输出到
  • 目录 项目介绍和源码; 拿来即用的bootstrap模板; 服务器SSH服务配置与python中paramiko的使用;...基于websocket实时日志实现; 查看服务器中的日志与前端的datatable的利用; 重启服务...
  • webSocket 读取linux日志,并显示到前端页面 刚才在帖子上看到这篇文章觉得很简单,写websocket的,在这记录一下。 直接线程代码 后端 package cn.worboy.websocket; import javax.websocket.Session; import java....
  • webSocket实现Web实时服务器日志输出

    千次阅读 2019-04-12 10:03:23
    由于项目要求,需要在web页面上实时显示服务器上某日志文件的内容,针对该需求,经查找一些大神的实现方式,主要可由两种方式实现: 前台定时向后台发送请求,后台连接linux获取日志信息 使用webSocket建立通信,...
  • } // 通过WebSocket发送给实时日志给客户端 String line; while ((line = bufferedReader.readLine()) != null) { session.getBasicRemote().sendText(line + " "); } } catch (IOException e) { System.out....
  • 基于Websocket,的java web远程日志查看系统

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,810
精华内容 3,924
关键字:

websocket实时显示日志