之前看网站日志什么的都得连接服务器输入命令才能查看,非常麻烦,今天利用websocket来进行日志实时查看。

maven依赖:

<!-- springboot整合websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

后端代码:

1、注入serverEndpointExporter

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2、TailLogThread类

import javax.websocket.Session;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * @className: TailLogThread
 * @description: TailLogThread
 * @author: xiaofei
 * @create: 2020年01月04日
 */
public class TailLogThread extends Thread {

    private BufferedReader reader;

    private Session session;

    public TailLogThread(InputStream in, Session session) {
        this.reader = new BufferedReader(new InputStreamReader(in));
        this.session = session;
    }

    @Override
    public void run() {
        String line;
        try {
            while ((line = reader.readLine()) != null) {
                if (session.isOpen()) {
                    // 将实时日志通过WebSocket发送给客户端,给每一行添加一个Html换行
                    session.getBasicRemote().sendText(line + "<br>");
                    try {
                        // 每0.5秒执行一次
                        Thread.sleep(500);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3、注入serverEndpointExporter

@Component
@ServerEndpoint("xxxxxx")
public class TailLogWebSocket {

    private final static Logger logger = LoggerFactory.getLogger(TailLogWebSocket.class);

    private Process process;
    private InputStream inputStream;

    @OnOpen
    public void onOpen(Session session) {
        logger.info("连接建立成功");
        logger.debug("debug日志测试");
        logger.warn("warn日志测试");
        logger.error("warn日志测试");
        try {
            // 这个是日志存放路径,我写在了yml里面
            String logPath = "";
            logPath = logPath.split("&")[0];
            String temp = "local";
            // 获取项目运行yml的后缀,如 pro test..
            String activeProfile = "";
            // 我这里只有两个环境,一个测试环境一个生产环境
            if (temp.equals(activeProfile)) {
                // 这里是windows读取方法
                File file = new File(logPath);
                FileInputStream fileInputStream = new FileInputStream(file);
                TailLogThread thread = new TailLogThread(fileInputStream, session);
                thread.start();
            } else {
                // 这里是Linux读取方法
                process = Runtime.getRuntime().exec(logPath);
                inputStream = process.getInputStream();
                TailLogThread thread = new TailLogThread(inputStream, session);
                thread.start();
            }
        } catch (IOException e) {
            logger.error("WebSocket读取日志出错:", e);
            e.printStackTrace();
        }
    }

    @OnMessage
    public void OnMessage(String msg) {
        logger.info("发送消息:{}", msg);
    }

    @OnClose
    public void onClose() {
        logger.info("连接关闭");
        try {
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (process != null) {
            process.destroy();
        }
    }

    @OnError
    public void onError(Throwable thr) {
        logger.error("WebSocket发生错误:", thr);
    }
}

这里说一下logPath,由于我的生产环境是在linux所以需要弄两份。&后面的我下面会讲怎么填写。
如果你是本地运行只需要填写 127.0.0.1:项目端口号 即可。

linux查看日志: tail -90f /java/xxxx.out&xxxxx/wss
windows查看日志:E:\IdeaProjects\aliyun\logs\xxxxx.log&127.0.0.1

前端html

前端页面,根据自己需求进行设置样式。里面我用了一个th标签用来回显WebSocket地址用的。

<div class="container-div">
    <div class="col-sm-12 search-collapse">
        <div class="select-list">
            <ul>
                <li>
                    监控地址:<input type="text" style="width: 350px;" id="wsServerUrl" name="wsServerUrl"
                                th:value="${wsServerUrl}"/>
                </li>
                <li>
                    <a class="btn btn-primary btn-rounded btn-sm" onclick="addWebSocket();" id="openLog"><i
                            class="fa fa-refresh"></i> 开始监控</a>
                </li>
                <li>
                    <a class="btn btn-warning btn-rounded btn-sm" onclick="closeWebSocket();" id="stopLog"><i
                            class="fa fa-remove"></i> 停止监控</a>
                </li>
                <li>
                    <a class="btn btn-danger btn-rounded btn-sm" onclick="emptyWebScoket();" id="cleanLog"><i
                            class="fa fa-trash"></i> 清空日志</a>
                </li>
            </ul>
        </div>
    </div>

    <div class="container-div ui-layout-center">
        <div class="row">
            <div class="col-sm-12 select-table table-striped">
                <div id="log-container">
                    <div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

前端js

<script>
    var websocket;
    var wsaddr;

    /**
     * 开启
     */
    function addWebSocket() {
        var wsaddr = $("#wsServerUrl").val();
        if (wsaddr == '') {
            $.modal.msgWarning("请填写WebSocket的地址");
            return false;
        }
        StartWebSocket(wsaddr);
    }

    /**
     * 关闭
     */
    function closeWebSocket() {
        if (websocket) {
            if (websocket.readyState === 3) {
                $.modal.msgWarning("已断开");
                return
            }
            if (websocket.readyState === 0) {
                $.modal.msgWarning("连接中...");
                return
            }
            websocket.close();
        } else {
            $.modal.msgWarning("还未建立连接");
        }

    }

    /**
     * 缓存
     */
    $(function () {
        var ws_adr = window.localStorage.getItem("bejson_ws_adr");
        if (ws_adr != null && ws_adr != "") {
            wsaddr.value = ws_adr;
        }
    })

    /**
     * 清空
     */
    function emptyWebScoket() {
        $("#log-container div").empty();
        window.localStorage.removeItem('bejson_ws_adr')
    }

    function StartWebSocket(wsUri) {
        if (websocket && websocket.readyState === 2) {
            $.modal.msgWarning("断开中...");
            return
        }
        try {
            websocket = new WebSocket(wsUri);
            websocket.onopen = function (evt) {
                onOpen(evt);
            };
            websocket.onclose = function (evt) {
                onClose(evt);
            };
            websocket.onmessage = function (evt) {
                onMessage(evt)
            };
            websocket.onerror = function (evt) {
                onError(evt)
            };
        } catch (e) {
            $.modal.msgWarning("错误的WebSocket链接");
        }
    }

    function onOpen(evt) {
        writeToScreen("<span style='color:#14ff00;'>连接成功建立!!!</span></br>");
    }

    function onClose(evt) {
        writeToScreen("<span style='color:red;'>WebSocket连接已断开!!!</span></br>");
        websocket.close();
    }

    function onMessage(evt) {
        let msg = evt.data;
        if (msg.indexOf('INFO') > 0) {
            msg = '<p style="color: #aaa;">' + msg + '</p>';
        } else if (msg.indexOf('WARN') > 0) {
            msg = '<p style="color: #FFB800;">' + msg + '</p>';
        } else if (msg.indexOf('ERROR') > 0) {
            msg = '<p style="color: #ff3a00;">' + msg + '</p>';
        } else if (msg.indexOf('DEBUG') > 0) {
            msg = '<p style="color: #09ff02;">' + msg + '</p>';
        }
        writeToScreen(msg);
    }

    function onError(evt) {
        writeToScreen('<span style="color: red;">发生错误:</span> ' + evt.data);
    }

    /**
     * 打印输输出
     */
    function writeToScreen(message) {
        $("#log-container div").append(message);
        // 滚动条滚动到最低部
        $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
    }

</script>

Nginx配置

如果你网站的域名是Https,WebSocket也必须配置证书

linux查看日志: tail -90f /java/xxxx.out&xxxxx/wss

说一下上面&后面配置:

项目不是https协议,直接用服务器ip+项目端口号即可。
xxxx.out&127.0.0.1:8080/wss

项目是https协议,WebSocket域名 + wss。域名不要加前缀 https://
xxxx.out&域名/wss

server{
    listen 80;
    listen 443 ssl http2;
    server_name 自己的域名;
    # 强制跳转https
    if ($server_port !~ 443){
        rewrite ^(/.*)$ https://$host$1 permanent;
    }
    #HTTP_TO_HTTPS_END
    # 证书pem
    ssl_certificate    /1.pem;
    # 证书key
    ssl_certificate_key    /1.key;
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=31536000";
    error_page 497  https://$host$request_uri;

    # 访问就是 xxx.com/wss
    location /wss/  {
      proxy_pass http://127.0.0.1:8080/;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
      proxy_set_header X-real-ip $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
    }
    # 禁止访问的文件或目录
    location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
    {
        return 404;
    }
    #一键申请SSL证书验证目录相关设置
    location ~ \.well-known{
        allow all;
    }
}

效果

上面页面监控地址填写配置:

后面的wss就是下面nginx里面的配置wss,可以自行更改,只要保持一致即可。

不是https:ws://ip地址:项目端口号/wss
是https:wss://域名/wss

如果你的项目域名用了CDN,则就不能作为WebSocket的连接地址。

如果项目用了CDN解决办法: 在开一个二级域名。