SpringBoot Admin (三)在线滚动日志和实现原理

SpringBoot Admin
placeholder image
admin 发布于:2023-01-28 22:40:44
阅读:loading

背景介绍

你信吗,就因为多看了一眼,就有了本文以及本文背后的那么多时间和实践。有一次在运维同事那边分析问题时,起初期望拿到日志文件的log,不经意的发现别的组的同事同样查看日志时是在SpringBoot Admin提供的功能页面上看到的,当时就觉得真酷炫,牛X了。要搁我看来单独的写一个日志文件下载的或者在线展示列表的功能还是不错的,对于大日志文件的实时滚动输出我还没想过呢,不过这个亮点在我心里扎根了,这不在我学习SpringBoot Admin时并未发现这个功能,于是找啊找,改啊改,终于在结合我的使用经验后得到了我期望的预期。

对于日志配置这块个人比较倾向于在application.yml中配置logback.xml的路径,所有的日志相关的配置全在logback.xml中进行设置,不推荐在直接在application.yml中进行设置,本时这个主文件就是个大杂烩,何不把日志的配置给剥离出去呢,所以相关的集成配置见下文。

日志配置

logging:
  config: classpath:logback-spring.xml
  file:
    path: logs/gateway
    name: ${logging.file.path}/gateway.root.log
  level:
    root: debug

主要是通过logging.file.path和name指定要监控日志文件的位置即可,就这么点配置即可。

效果预览

Spring Boot Admin日志监控.gif

特别说明

(1)项目工程源码见:源码下载.zip,监控的是gateway模块的日志滚动;

实现分析

(1)关于SpringBoot Admin的日志文件读取的功能可见spring-boot-actuator-2.4.2.jar文件中的org.springframework.boot.actuate.logging.LogFileWebEndpoint类,但是源码中写的比较简单,根据文件构造FileSystemResource对象后就完活了,后续的实现着实让我费解了,难道SpringMvc的Controller对于返回Resource的资源文件类型自带可以做解析处理,不能够吧???

(2)关于前端代码部分可在spring-boot-admin-server-ui-2.4.2.jar文件中的sba-core.b6f99094.js文件中(可能版本号不一样,相关的文件序号也不一样吧)找到了关键字“/logfile”共计4处,由于是压缩的JS文件非常难于阅读,于是拷贝出来将其格式化后仍然非常的难以阅读,在这个文件中找到了一秒钟一触发的定时后,果断放弃阅读,基本确信就是此处文件代码了,文件路径及文件参考如下:

image.pngimage.png

(3)其实通过浏览器的F12基本上可以看到是前端发起了1秒钟一个轮询的请求,对于这个轮询的间隔来说,还是倔强的通过代码来找到它的时间间隔,确实是1秒钟,在此之前个人琢磨着难道会使用WebSocket这种服务器适时推送的技术实现的文件适时输出?或者说是使用的更高端的文件流的输出?实际上前者并不是,后者没找到。(这原理分析的确实水啊)

(4)又经过分析得到的实现原理为,通过Ajax轮询向服务器发送请求,通过Response Headers的Content-Range属性进行交互,详细如下:

A.当客户端开始发起日志文件的请求时,先获取日志文件最新的大小,并返回至客户端;

B.客户端发起第一次请求时,在Request Header中增加Range参数将文件大小提交至服务器,服务器获取日志文件的最新大小 减去 客户端发送Request Header中的服务端获取文件大小,得到这个轮询区间内文件的结果大小,即告知服务端在响应日志文件内容时先跳过多少字节,并且读取多少字节并响应给客户端,参考如下图所示:

image.png

参考实现

package cn.chendd;

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpRange;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;

/**
 * 文件跳过指定大小读取示例
 * @author chendd
 * @date 2022/12/26 20:08
 */
public class Test {

    public static void main(String[] args) throws Exception {
        {
            List<HttpRange> ranges = HttpRange.parseRanges("bytes=37465308-");
            System.out.println(ranges);
            File file = new File("E:\\chendd\\Idea-Workspaces\\chendd-SpringCloud\\logs\\gateway\\gateway.root.log");
            System.out.println(file.length());
            List<ResourceRegion> regionList = HttpRange.toResourceRegions(ranges, new FileSystemResource(file));
            System.out.println(regionList);
            System.out.println(38553505 - 37465308);

            InputStream inputStream = new FileInputStream(file);
            inputStream.skip(38553505);
            byte[] bytes = new byte[1088197 - 3963 - 15 - 26 - 1 - 3 - 3];
            inputStream.read(bytes);
            System.out.println(new String(bytes));
        }
        {
            File file = new File("d:/utf8.txt");
            InputStream u8InputStream = new FileInputStream(file);
            byte[] u8bytes = new byte[5 + 3 + 3];
            u8InputStream.read(u8bytes);
            System.out.println(new String(u8bytes));
            System.out.println("utf8文件总字节:" + file.length());
        }

        {
            File file = new File("d:/gbk.txt");
            InputStream gbkInputStream = new FileInputStream(file);
            byte[] gbkbytes = new byte[5 + 2 + 2];
            gbkInputStream.read(gbkbytes);
            System.out.println(new String(gbkbytes , "GBK"));
            System.out.println("GBK文件总字节:" + file.length());
        }
    }

}

关键点说明

(1)HttpRange是一个工具类,可专门用于计算Range或传输的Range参数值;

(2)InputStream的skip函数可以跳过指定的文件大小字节;

(3)InputStream的read函数可以指定读取的字节数

(4)你是否觉得在读取字符文件时可能出现的两个问题,一个是中文乱码,另一个是读取的字节数正好是读取到中文的一半字节,将中文给截断了?事实上不存在乱码问题,大家都用UTF-8编码吧。至于将读取到的中文一半给截断了应该也不会出现,因为每次跳过的大小和读取的文件大小均是获取的当前文件的大小,并不是自己设置的,我猜测每次获取文件的大小时是一个完整的流结构的大小。



 点赞


 发表评论

当前回复:作者

 评论列表


留言区