Demo(Scenario 1 only)

Plan 1

  1. When creating a WebSocket connection, add a custom Appender dynamically to logback to send logs to the WebSocket connection through the custom Appender

    • The custom appenders
    public class MyAppender extends AppenderBase<ILoggingEvent> {
    
    	private WebSocketServer webSocketServer;
    
    	public MyAppender(WebSocketServer webSocketServer) {
    		this.webSocketServer = webSocketServer;
    	}
    
    	/** * Add log *@param iLoggingEvent
    	 */
    	@Override
    	protected void append(ILoggingEvent iLoggingEvent) {
    		try {
    			webSocketServer.sendMessage(doLayout(iLoggingEvent));
    		} catch(IOException e) { e.printStackTrace(); }}/** * Format log *@param event
    	 * @return* /
    	public String doLayout(ILoggingEvent event) {
    		StringBuilder sbuf = new StringBuilder();
    		if (null! = event &&null! = event.getMDCPropertyMap()) { SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
    
    			sbuf.append(simpleDateFormat.format(new Date(event.getTimeStamp())));
    			sbuf.append("\t");
    
    			sbuf.append(event.getLevel());
    			sbuf.append("\t");
    
    			sbuf.append(event.getThreadName());
    			sbuf.append("\t");
    
    			sbuf.append(event.getLoggerName());
    			sbuf.append("\t");
    
    			sbuf.append(event.getFormattedMessage().replace("\" "."\ \ \" "));
    			sbuf.append("\t");
    		}
    
    		returnsbuf.toString(); }}Copy the code
    • The back end responds to the WebSocket
    @ServerEndpoint("/webSocket")
    @Component
    public class WebSocketServer {
    
    	// A connection session with a client through which to send data to the client
    	private Session session;
    
    	private Integer sessionId;
    
    	/** * Connection established successfully called method */
    	@OnOpen
    	public void onOpen(Session session) {
    		this.session = session;
    		this.sessionId = (new Random()).nextInt(100000);
    		LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
    		// Step 2: Get the log object (log is inherited, close the upper layer, and close the lower layer unless otherwise specified)
    		ch.qos.logback.classic.Logger rootLogger = lc.getLogger("root");
    		MyAppender myAppender = new MyAppender(this);
    		myAppender.setContext(lc);
            	// Custom Appender set name
    		myAppender.setName("myAppender" + sessionId);
    		myAppender.start();
    		rootLogger.addAppender(myAppender);
    		System.out.println("Injection successful");
    	}
    
    	/** * the connection closes the called method */
    	@OnClose
    	public void onClose(a) {
    		LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
    		ch.qos.logback.classic.Logger rootLogger = lc.getLogger("root");
            	// Remove the Appender by name
    		rootLogger.detachAppender("myAppender" + sessionId);
    		System.out.println("-------- Removed successfully --------");
    	}
    
    	/** * The server actively sends messages */
    	public void sendMessage(String message) throws IOException {
    		this.session.getBasicRemote().sendText(message); }}Copy the code
    • The front end
    <! DOCTYPEhtml>
    <html>
    <head>
        <meta charset="UTF-8" />
        <title>Real time log</title>
        <script src="./js/sockjs.min.js"></script>
        <script src="./js/stomp.js"></script>
        <script src=". / js/jquery - 3.1.1. Js. ""></script>
    </head>
    <body>
    <noscript><h2 style="color:#ff0000">Sorry, your browser does not support this feature!</h2></noscript>
    <div>
        <div>
            <button id="connect" onclick="connect();">The connection</button>
            <button id="disconnect" disabled="disabled" onclick="disconnect();">disconnect</button>
        </div>
        <div id="conversationDiv">
            <textarea id="response"></textarea>
        </div>
    </div>
    </body>
    <script type="text/javascript">
        var ws;
    
        function setConnected(connected){
            document.getElementById('connect').disabled = connected;
            document.getElementById('disconnect').disabled = ! connected;// document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
            $("#response").html();
        }
    
        function connect(){
            ws = new WebSocket('ws://localhost:8081/webSocket');
            ws.onopen = WSonOpen;
            ws.onmessage = WSonMessage;
            ws.onclose = WSonClose;
            ws.onerror = WSonError;
        }
    
        function WSonOpen() {
            var message = {
                Content:'Connection successful'
            }
            setConnected(true);
            showResponse(message)
        };
    
        function WSonMessage(event) {
            var message = {
                Content:event.data
            }
            showResponse(message)
        };
    
        function WSonClose() {
            var message = {
                Content:'Disconnected'
            }
            showResponse(message)
        };
    
        function WSonError() {
            var message = {
                Content:'Connection error! '
            }
            showResponse(message)
        };
    
        function disconnect(){
            ws.close()
            setConnected(false);
        }
    
        function sendMessage(){
            ws.send(JSON.stringify({'Content':Content}))
        }
    
        function showResponse(message){
             var response = $("#response").val();
             $("#response").val(response+message.Content+'\n');
             // Rolling all the way to the bottom will cause a display delay
            var textarea = document.getElementById("response");
            textarea.scrollTop = textarea.scrollHeight;
        }
    </script>
    </html>
    Copy the code
  2. When webSocket is closed, the custom Appender is removed from logback

  • The effect

Disadvantages: Unable to display the exception stack information (can be captured by global exception, and then print the exception stack information through logback, more troublesome, but also has obvious defects, not good)

Scheme 2

  1. When creating a WebSocket connection, the Runtime is used to create a child Process that executesTail -f Log file, gets the child process output stream and sends the log to the WebSocket connection
    @ServerEndpoint("/realtimeLog/{level}")
    @Component
    public class RealTimeLog {
    
    	private static Logger logger = LoggerFactory.getLogger(RealTimeLog.class);
    
    	private static String logPath;
    
    	private static String webSocketSecret;
    
    	private Runnable runnable;
    
    	private Process process;
    	
    	@Autowired
    	public void setLogPath(@Value("${log.path}") String logPath) {
    		this.logPath = logPath;
    	}
    
    	ThreadPoolExecutor executor = new ThreadPoolExecutor(0.20.10, TimeUnit.SECONDS,
    			new LinkedBlockingQueue<>(20),
    			new CustomizableThreadFactory("realtime-log-thread-"),
    			new ThreadPoolExecutor.AbortPolicy()
    	);
    
    
    	/** * Connection established successfully called method */
    	@OnOpen
    	public void onOpen(Session session, @PathParam("level") String level) throws Exception {
    		if (Strings.isNullOrEmpty(level)) {
    			level = "INFO";
    		}
    		process = Runtime.getRuntime().exec("tail -f " + logPath + "api.log");
            	// Start a new thread to send logs
    		runnable = new Runnable() {
    			@Override
    			public void run(a) {
    				try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
    					String line;
    					while((line = in.readLine()) ! =null) { session.getBasicRemote().sendText(line); }}catch(IOException e) { e.printStackTrace(); }}}; executor.execute(runnable); }/** * the connection closes the called method */
    	@OnClose
    	public void onClose(a) {
            // Remove the thread
    		executor.remove(runnable);
    		if(process ! =null) {
                // Close the child processprocess.destroy(); }}// Splice grep filter criteria
    	public static String appendLevel(String level) {
    		StringBuffer stringBuffer = new StringBuffer();
    		String[] arr = new String[]{"DEBUG"."INFO"."WARNING"."ERROR"};
    		for (int i = 0; i < arr.length; i++) {
    			if (arr[i].equals(level)) {
    				stringBuffer.append("|" + arr[i]);
    				for (int j = i + 1; j < arr.length; j++) {
    					stringBuffer.append("|" + arr[j]);
    				}
    				break; }}returnstringBuffer.toString(); }}Copy the code
  2. When the WebSocket is closed, the output stream is closed and the child process is closed
  • The effect