通过websocket即时预览springboot服务端的日志信息

通过websocket即时预览springboot服务端的日志信息

通过读取日志文件获取日志消息

这种方式,需要知道日志文本文件的地址。通过随机IO,读取到最新的日志文本数据。

// 不能打开写权限("w"),不然其他程序没法写入数据到该文件
try(RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\log.log", "r")){
	// 最后一次指针的位置,默认从头开始
	long lastPointer = 0;
	// 每一行读取到的数据
	String line = null;
	// 持续监听文件
	while(true) {
		// 从最后一次读取到的数据开始读取
		randomAccessFile.seek(lastPointer);
		// 读取一行,遇到换行符停止,不包含换行符
		while((line = randomAccessFile.readLine()) != null) {
			System.out.print(line + "\n");  // 打印日志消息,或者响应给客户端
		}
		// 读取完毕后,记录最后一次读取的指针位置
		lastPointer = randomAccessFile.getFilePointer();
	}
}

通过自定义 Appender 获取到最新的消息

这种方式使用的日志框架为 logback,其实我对logback也不熟悉,只是看ch.qos.logback.core.ConsoleAppender 获得灵感,想着通过修改 outputStream 熟悉,把日志输出到自定义的管道流,再从管道流中读取到最新的日志消息,推送给客户端。

自定义 Appender 的实现

覆写 start 方法,在开始之前,通过 setOutputStream 设置管道流,并且启动一个线程,不停的从管道读取流中获取到最新的数据

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;

import javax.websocket.Session;


import ch.qos.logback.core.OutputStreamAppender;
import io.springboot.log.web.socket.channel.LogChannel;

public class SocketOutputStreamAppender<E> extends OutputStreamAppender<E> {
	
	@Override
	public void start() {

		// 管道读取流
		PipedInputStream pipedInputStream = new PipedInputStream();
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(pipedInputStream, StandardCharsets.UTF_8));
		
		// 管道写入流
		PipedOutputStream pipedOutputStream = new PipedOutputStream();
		
		try {
			 // 写入读取管道链接
			pipedOutputStream.connect(pipedInputStream); 
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 设置输出流到Appender
		super.setOutputStream(pipedOutputStream);
		
		// 子线程开始从管道流读取数据
		Thread thread = new Thread(() -> {
			String line = null;
			while (true) {
				try {
					line = bufferedReader.readLine();
				} catch (IOException e) {
					// e.printStackTrace();
				}
				if (line != null) {
					for (Session channel : LogChannel.CHANNELS.values()) {
						if (channel.isOpen()) { 
							// 异步传输数据到客户端
							channel.getAsyncRemote().sendText(line);
						}
					}
				}
			}
		});
		
		thread.setDaemon(Boolean.TRUE);
		thread.setName("socket-log");
		thread.start();
		
		super.start();
	}
}

logback的配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
		
	<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
	<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
	<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
	
	<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
	
	<!-- 输出到控制台 -->
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
		</encoder>
	</appender>
	
	<!-- 输出到socket -->
	<appender name="socket" class="io.springboot.log.logback.SocketOutputStreamAppender">
		<encoder>
			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
		</encoder>
	</appender>
	
	<root level="DEBUG">
		<appender-ref ref="console" />
		<appender-ref ref="socket"/>
	</root>
</configuration>

最终的效果

核心的代码,配置就上面的俩。其他的都是相当简单的东西。

完整的代码工程