一、簡介
有些時候webapp有需求將日志輸出到前台頁面,便於開發者查看日志,本篇將介紹如何將日志輸出到前台顯示;
二、准備
WebSocket技術、log4j、miniui(本項目所用前端,其他也一樣);
三、參考文檔
受到此博主的啟發,特感謝此博主:https://blog.csdn.net/xiao__gui/article/details/50041673
四、過程
1、log4j配置
log4j.rootLogger=debug,stdout,WA
# 選用WriterAppender作為Appender,表示以流的形式輸出,這個Appender一般很少用,是常用Appender的父類 log4j.appender.WA=org.apache.log4j.WriterAppender log4j.appender.WA.Threshold=debug log4j.appender.WA.layout=org.apache.log4j.PatternLayout
# %X{ip} 輸出本服務器的ip,通過MDC輸入(見后面描述) log4j.appender.WA.layout.ConversionPattern=%d{ISO8601} - [%X{ip}] -%5p %c{1}:%L - %m%n
2、WebSocket后台編寫
import java.io.PipedReader; import java.io.PipedWriter; import java.io.Writer; import java.net.InetAddress; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import org.apache.log4j.Appender; import org.apache.log4j.Logger; import org.apache.log4j.MDC; import org.apache.log4j.WriterAppender; /** * @Author : ycj * @Description : 日志文件處理websocket服務 * @CreateDate : 2018/6/12 9:42 */ @ServerEndpoint("/log") public class LogWebsocketHandle { private PipedReader reader; private Writer writer; /** * WebSocket請求開啟 */ @OnOpen public void onOpen(Session session) { Logger root = Logger.getRootLogger(); try { // 獲取本服務器id String hostAddress = InetAddress.getLocalHost().getHostAddress();
// MDC是key-value結構,有興趣的可以去了解下,在log4j的配置中設置 %X{ip},在日志中輸出 MDC.put("ip",hostAddress); Appender appender = root.getAppender("WA");
// 通過管道流進行線程間的通訊 reader = new PipedReader(); writer = new PipedWriter( reader) ; ((WriterAppender) appender).setWriter(writer); // 啟動新的線程 LogThread thread = new LogThread(reader, session); thread.start(); } catch (Exception ex) { ex.printStackTrace(); } } /** * WebSocket請求關閉,關閉請求時調用此方法,關閉流 */ @OnClose public void onClose() { try { if(reader != null) { reader.close(); } } catch (Exception ex) { ex.printStackTrace(); } try { if(writer != null) { writer.close(); } } catch (Exception ex) { ex.printStackTrace(); } } @OnError public void onError(Throwable thr) { thr.printStackTrace(); } }
LogThread 線程
import java.io.BufferedReader; import java.io.IOException; import java.io.PipedReader; import javax.websocket.Session; /** * 新線程 */ public class LogThread extends Thread { private BufferedReader reader; private Session session; public LogThread(PipedReader pipedReader, Session session) { this.reader = new BufferedReader(pipedReader); this.session = session; } @Override public void run() { String line; try { while((line = reader.readLine()) != null) { // 將實時日志通過WebSocket發送給客戶端 session.getBasicRemote().sendText(line + "<br>"); } } catch (IOException e) { e.printStackTrace(); } } }
2、前端編寫
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>tail log</title>
<!-- 此boot.js不需要--> <script src="../../boot.js"></script> <script src="../../js/jquery-1.6.2.min.js"></script> </head> <body> <div id="log-container" style="height: 480px; overflow-y: scroll; background: #333;padding: 10px;"> <div> </div> </div> <div>
<!-- 下面div用的是miniui的樣式,其他的稍微改變一下即可--> <div id="ck1" name="product" class="mini-checkbox" checked = "true" readOnly="false" text="是否滾動" onvaluechanged="onValueChanged" style="left: 20px"></div> <a style="left: 30px;top: 1px" class="mini-button mini-button-info" onclick="clearContext()">清屏</a> </div> </body> <script> // 控制是否滾動顯示日志 var checked = true; function onValueChanged(e) { checked = this.getChecked(); } $(document).ready(function() { // 指定websocket路徑,此地址建議根據用js動態獲取 var websocket = new WebSocket('ws://localhost:8080/log'); websocket.onmessage = function(event) { // 接收服務端的實時日志並添加到HTML頁面中(error顯示紅色) if (event.data.search("ERROR") != -1) { $("#log-container div").append(event.data).css("color", "#AA0000"); } else { $("#log-container div").append(event.data).css("color", "#aaa"); } // 是否滾動 if (checked) { // 滾動條滾動到最低部 $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height()); } }; }); // 清屏日志 function clearContext() { $("#log-container div").empty(); } </script> </body> </html>
3、js獲取項目路徑代碼(供參考)
// 此方式獲取的bootPath項目根路徑放入WebSocket路徑需要進行截取
JsPath = function (js) { var scripts = document.getElementsByTagName("script"); var path = ""; for (var i = 0, l = scripts.length; i < l; i++) { var src = scripts[i].src; if (src.indexOf(js) != -1) { var ss = src.split(js); path = ss[0]; break; } } var href = location.href; href = href.split("#")[0]; href = href.split("?")[0]; var ss = href.split("/"); ss.length = ss.length - 1; href = ss.join("/"); if (path.indexOf("https:") == -1 && path.indexOf("http:") == -1 && path.indexOf("file:") == -1 && path.indexOf("\/") != 0) { path = href + "/" + path; } return path; } var bootPATH = JsPath("boot.js");
4、成果展示

