通过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>
最终的效果
核心的代码,配置就上面的俩。其他的都是相当简单的东西。