一款簡單易用的遠端紀錄檔檢視器,可實時檢視雲伺服器上的紀錄檔資料

2022-12-28 21:05:25

本文主要內容

前置知識

  • SpringBoot基礎知識
  • SSH:Secure Shell
  • Web前端基礎:HTML、CSS、JavaScript、jQuery
  • WebSocket

如果你對以上基礎技術很陌生,本篇文章內容可能不適合你!

專案背景

  • 場景1:在企業級開發中,公司的測試環境一般部署在某個遠端的內網伺服器上,我們想要檢視該個測試環境的紀錄檔,就需要手動建立SSH,再執行紀錄檔檢視命令,在終端檢視紀錄檔
  • 場景2:我們自己寫的小專案部署到雲伺服器上後,想要檢視紀錄檔,也需要通過SSH連線到雲伺服器,通過執行檔案檢視命令,來看到紀錄檔資訊

在這個過程中:

  1. 需要開啟SSH使用者端工具,例如MobaXterm、putty
  2. 連線到遠端伺服器:輸入密碼、使用者名稱
  3. 手工鍵入紀錄檔檔案檢視命令:tail -f 紀錄檔檔案路徑
  4. 在Shell Terminal檢視紀錄檔
  5. 存在問題:在Terminal上看得眼睛痛,不要根據關鍵字搜尋紀錄檔,不好檢視紀錄檔資訊

在我我司的內網測試環境,檢視紀錄檔

那麼,有沒有辦法把這一過程自動化呢?答案是肯定的。這便是本專案的設計初衷與目的!

功能特性

功能特性列表

  • 支援開啟多個的前端頁面,分別抓取紀錄檔資料渲染到頁面,但只能抓取一個紀錄檔檔案的資料
  • 可檢視該紀錄檔檔案的歷史資料
  • 可實時抓取紀錄檔檔案中新產生的紀錄檔資料
  • 對當前頁面上的紀錄檔資料進行關鍵字查詢

後續將會支援的功能

  • 使用者可以在前端頁面上自定義SSH服務地址和紀錄檔檔案的位置,並支援儲存,一次設定,以後可以多次使用
  • 現階段只支援獲取文字檔案中的紀錄檔資料,後續將可支援其他格式(例如壓縮檔案)的紀錄檔資料
  • ……

支援開啟多個的前端頁面,分別抓取紀錄檔資料渲染到頁面,但只能抓取一個紀錄檔檔案的資料

可檢視該紀錄檔檔案的歷史資料

可實時抓取紀錄檔檔案中新產生的紀錄檔資料

Start:開始抓取紀錄檔檔案中的歷史記錄,然後實時獲取新產生的紀錄檔

Stop:停止抓取

Clean:清除當前頁面上的所有紀錄檔資料,但不會斷開連線,還是會實時地呈現後端推播過來的紀錄檔資訊

對當前頁面上的紀錄檔資料進行關鍵字查詢

  • 單擊搜尋方塊,將貼上板上的資料複製到此個搜尋方塊內
  • 雙擊搜尋方塊,清除此個搜尋方塊內的資料

技術棧

後端技術

  • Spring Boot
  • SSH使用者端的Java實現工具:jsch
  • Spring封裝的WebSocket Server API:將SSH中執行命令後返回的資料,推播給前端

前端技術

  • jQuery
  • JavaScript封裝的WebSocket Client API:接收後端發來的資料,將其渲染到HTML頁面

本地執行

Step1:環境準備或檢查

  • Java:11
  • SpringBoot:2.3.12.RELEASE
  • Apache Maven:3.6.3
  • Chrome Version:108.0.5359.94,在位址列輸入(chrome://version/)可獲取

Step2:克隆專案到本地,從IDEA中開啟,等待Maven自動設定完畢

Step3:填寫專案的組態檔(application.yml)

  • 指定SSH的連線引數:jsch開頭的一系列設定引數
  • 遠端伺服器上的紀錄檔所在位置:log-path

Step4:執行啟動類:src/main/java/com/hackyle/log/viewer/RemoteLogViewerApp.java

Step5:進入Chrome,在位址列輸入:http://localhost:8989/,進入紀錄檔檢視首頁

設計說明

主要流程

  1. 前端發起一個WebSocket連線到後端
  2. 連線建立成功後,後端通過SSH連線到遠端伺服器
  3. 執行紀錄檔檔案檢視命令:tail -1f 紀錄檔檔案的絕對路徑,例如:tail -1f /data/blog.hackyle.com/blog-business-logs/blog-business.log
  4. 獲取到該個命令的執行結果,通過WebSocket推播到前端頁面上

後端

整合SSH

主要步驟

  1. 匯入jsch的POM依賴
  2. 在組態檔(yml)中定義SSH的連線引數
  3. 寫一個業務類,定義建立SSH對談、關閉對談的方法
    1. [使用@Value(「${jsch.host}」)](mailto:%E4%BD%BF%E7%94%A8@Value()註解從組態檔中載入引數
    2. 建立對談方法:Session buildConnect()
    3. 關閉對談方法:void destroyConnect(Session sshSession)

application.yml

JschService

@Override
public Session buildConnect() {
    Session sshSession = null;
    try {
        JSch jSch = new JSch(); 
        sshSession = jSch.getSession(username, host, port); 

        Properties config = new Properties();
        config.put("StrictHostKeyChecking","no"); 
        sshSession.setPassword(password); 
        sshSession.setConfig(config);

        sshSession.connect(); 

        if(sshSession.isConnected()) {
            System.out.println("SSH連線成功:" + sshSession.getHost() + ":" + sshSession.getPort() +"  "+ sshSession);
        } else {
            throw new RuntimeException("SSH連線失敗");
        }
    } catch (Exception e) {
        System.out.println("SSH連線出現異常:" + e);
    }

    return sshSession;
}

@Override
public void destroyConnect(Session sshSession) {
    if(sshSession != null) {
        sshSession.disconnect();
        if(!sshSession.isConnected()) {
            System.out.println("SSH已斷開連線:" + sshSession.getHost()+":"+ sshSession.getPort() +"  "+ sshSession);
        }
    }
}

紀錄檔資料獲取與推播邏輯

com/hackyle/log/viewer/service/impl/LogServiceImpl.java

主要邏輯

  1. 準備要執行的Shell命令:tail -1f 紀錄檔檔案的絕對路徑,例如:tail -1f /data/blog.hackyle.com/blog-business-logs/blog-business.log
  2. 獲取sshSession,建立一個執行Shell命令的Channel
  3. 從Channel中讀取流,包裝為字元流,一次讀取一行紀錄檔資料
  4. 獲取WebSocket Session,只要它沒有被關閉,就將紀錄檔資料通過該Session推播出去

整合WebSocket Server

主要步驟

  1. 匯入WebSocket的starter依賴
  2. 事件處理器:通過繼承 TextWebSocketHandler 類並覆蓋相應方法,可以對 websocket 的事件進行處理
  3. WS握手(連線)攔截器
  • 通過實現 HandshakeInterceptor 介面來定義握手攔截器,完全等價於SpringMVC中的攔截器
  • 最佳應用場景是:通過攔截器可以對ws請求進行認證
  1. 定義ws對前端暴露的API介面
  • 通過實現 WebSocketConfigurer 類並覆蓋相應的方法進行 websocket 的設定。
  • 我們主要覆蓋 registerWebSocketHandlers 這個方法。
  • 通過向 WebSocketHandlerRegistry 設定不同引數來進行設定。其中 addHandler方法新增我們上面的寫的 ws 的 handler 處理類,第二個引數是你暴露出的 ws 路徑。
  • addInterceptors 新增我們寫的握手過濾器。
  • setAllowedOrigins("*") 這個是關閉跨域校驗,方便本地偵錯,線上推薦開啟。

事件處理器

com/hackyle/log/viewer/handler/LogWebSocketHandler.java

  • 定義WebSocket的一系列回撥函數
  • 使用一個靜態Map快取當前所有已經建立了連線的對談

afterConnectionEstablished方法:連線建立成功時呼叫

  • 快取當前已經建立WebSocket的連線對談
  • 建立一個SSH對談,也放入快取
  • 把WebSocket對談ID先發給前端,便於前端通過該對談ID關閉WebSocket連線
  • 執行紀錄檔檢視命令,向前端推播紀錄檔資料

afterConnectionClosed方法:關閉連線後呼叫

  • 從快取中移除該個已經建立了的WebSocket連線對談

握手攔截器

com/hackyle/log/viewer/interceptor/WebSocketInterceptor.java

  • beforeHandshake:在握手前觸發;afterHandshake:在握手後觸發。
  • 功能與SpringMVC攔截器類似
  • 這裡獲取前端傳遞來的檢視多少條歷史紀錄檔的引數

對外暴露ws介面

com/hackyle/log/viewer/config/WebSocketConfig.java

  • 定義ws對外的存取介面
  • 將時間處理器、握手攔截器注入到WebSocketHandlerRegistry

前端

整合WebSocket Client

WebSocket使用者端

  • 初始化範例物件,開啟WebSocket:var ws = new WebSocket('ws://localhost:8989/ws/hello');
  • readyState中列舉了不同的狀態,可根據狀態指定狀態(ws的建立連線、傳送訊息、接收訊息、關閉連線)的處理邏輯
  • 關閉WebSocket:close();

src/main/resources/static/js/index.js

顯示歷史紀錄檔的條數

抓取控制

Start:開始抓取紀錄檔檔案中的歷史記錄,然後實時獲取新產生的紀錄檔

Stop:停止抓取

Clean:清除當前頁面上的所有紀錄檔資料,但不會斷開連線,還是會實時地呈現後端推播過來的紀錄檔資訊

為三個按鈕分別新增一個Click事件,定義動作函數

Start:建立WebSocket範例,將後端發來的資料,不斷追加到某個標籤下

Stop:前端手動關閉WebSocket,請求後端介面,關閉WebSocket Server

src/main/resources/static/js/index.js

頁內關鍵字搜尋

在本個頁面內,進行關鍵字搜尋。本質是模擬瀏覽器的Ctrl+F,進行HTML內容搜尋

呼叫window.find()方法

  • 官方檔案:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/find
  • API:find(aString, aCaseSensitive, aBackwards, aWrapAround, aWholeWord, aSearchInFrames, aShowDialog);
  • 引數釋義
    • aString:將要搜尋的字串
    • aCaseSensitive:布林值,如果為true,表示搜尋是區分大小寫的。
    • aBackwards:布林值。如果為true, 表示搜尋方向為向上搜尋。
    • aWrapAround:布林值。如果為true, 表示為迴圈搜尋。

快速貼上

單擊搜尋方塊,將貼上板上的資料複製到此個搜尋方塊內

  • 獲取到該個搜尋方塊
  • 呼叫execCommand(「copy」),把貼上板上的資料寫入

使用第三方庫clipboard接管貼上板


function copyHandle(content){
    let copy = (e)=>{
        e.preventDefault()
        e.clipboardData.setData('text/plain',content)
        
        document.removeEventListener('copy',copy)
    }
    document.addEventListener('copy',copy)
    document.execCommand("Copy");
}

雙擊搜尋方塊,清除此個搜尋方塊內的資料

  • 新增一個雙擊事件
  • 清除元素內的值

手動關閉WS連線

背景

  • 如果直接在Client端直接關閉,在Server端會拋異常(Caused by: java.io.IOException: 你的主機中的軟體中止了一個已建立的連線。)
  • 所以,後端設計一個介面,當要關閉某個WebSocket連線時,請求該個介面,並攜帶上WebSocket的SessionId

設計思想

  1. 在前後端建立連線時,後端就把sessionId放入快取,並響應給前端
  2. 前端得到sessionId,將其放在sessionStorage中,目的是使得該個id僅在本頁面內有效
  3. 前端在請求關閉介面時,攜帶上該個id
  4. 後端移除該個id的快取,並關閉所有對談資訊

後端

接收前端請求:com/hackyle/log/viewer/controller/LogController.java

業務:com/hackyle/log/viewer/service/impl/LogServiceImpl.java#closeWebSocketServer

實現:com/hackyle/log/viewer/handler/LogWebSocketHandler.java#closeWebSocketServer

前端

存入sessionStorage:src/main/resources/static/js/index.js

關閉WebSocket連線時,攜帶sessionId:src/main/resources/static/js/index.js

打成Jar執行

背景

  1. 每次需要檢視紀錄檔時,都需要開啟IDE環境,也挺麻煩的
  2. 解決辦法是將本專案打成Jar,一鍵啟動

Step1:在POM.xml中新增打包外掛

Step2:執行打包命令

Step3:將Jar放在合適的位置

Step4:寫個啟動指令碼。本專案基於JDK11,建議手動設定臨時的JDK環境變數,再啟動Jar


set JAVA_HOME=D:\ProgramFilesKS\Java\JDK11
set path=%JAVA_HOME%\bin;%path%

java -jar D:\D-Project\DevelopTools\remote-log-viewer-0.6.0.jar

pause