SpringBoot+Vue实现了一个日志监控可视化平台

科技   2024-11-25 14:24   安徽  

来源:juejin.cn/post/7214068367607070781

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

截止目前,累计输出 69w+ 字,讲解图 2776+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2400+小伙伴加入

  • 前言
  • 开工
  • 代码部分
  • 结束
  • 代码仓库

日志服务是作为软件开发架构的必备服务之一,一直都是我们所关注,所考虑的服务关键点,一个优秀的日志服务可以为项目的维护提供有力的支持,提高了系统的可靠性。

前言

如何设计一个优秀而又可靠的日志服务,是一直以来都是一个很难的课题,写本文的目的起源于大学时期做的一个项目,那时候后台部署到阿里云上,每次遇到Error的问题的时候都需要登陆服务器查看,非常的麻烦,于是我就突发奇想为什么不对Spring的日志进行拦截,展示到前端界面呢?于是就产生了这个项目。

开工

在开始之前,我们需要捋清楚,我们的需求是什么?通过思考,我们得知,我们的需求是能够实时获取系统输出日志,并展示到我们的前端,用于运维和问题排查。

在明确了目的之后,我们可以开始了开发工作,首先是技术选型,我们所选择的是SpringBoot + Vue来搭配。鉴于写文本的时候Vue3已经发布了,所以才用的是最新的Vue3来进行开发。

代码部分

前端

前端所选用的通信技术为:Vue3 + Websocket,日志通过Websocket实时同步到前端,具体实现如下。

mounted() {
    this.res = document.getElementById('terminal-console');
    this.websocket = new WebSocket("ws://127.0.0.1:8080/websocket/logger");
    this.renderConsole()
    this.connect()
  },

1.渲染代码架子。

2.建立Websocket链接。

3.渲染控制台

4.建立链接,开始接收日志,这里由于前端篇幅问题,我就直接放核心代码了,后续如果需要进一步的可以前往github进行下载。

connect() {
      this.res.appendChild(this.createDivElement('通道连接成功,静默等待....'))
      const _this = this;
      if (this.websocket != null) {
        this.websocket.open = function () {
          this.websocket.send("数据发送");
        };
        this.websocket.onmessage = function (event) {
   // 处理日志
          const content = JSON.parse(event.data);
          let levelHtml = _this.createSpanElement(content.level, '#90ad2b');
          const className =  _this.createSpanElement(content.className, '#229379')
          switch (content.level) {
            case 'DEBUG':
              levelHtml = _this.createSpanElement(content.level, '#A8C023');
              break;
            case 'WARN':
              levelHtml = _this.createSpanElement(content.level, '#fffa1c');
              break;
            case 'ERROR':
              levelHtml = _this.createSpanElement(content.level, '#e3270e');
              break;
          }
          const logger = _this.createDivElement(content.timestamp)
          logger.appendChild(levelHtml)
          logger.innerHTML = logger.innerHTML + " --- [" + content.threadName + "] "
          logger.appendChild(className)
          logger.innerHTML = logger.innerHTML + " :" + content.body
          _this.res.appendChild(logger)
        }
      }
    }

后台

后台部分实现思路如下:

1.在logger.xml配置文件中配置日志拦截器,同时实现拦截器类,下方的filter就是我们的实现的过滤器。

%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

2.紧接着就是实现过滤的能力,这块就不用说了,就是实现了logger的过滤能力,其思路就是继承logback的Filter,实现过滤方法,在过滤过程中提取关键字,并写入队列。

@Service
public class LogFilter extends Filter {
    @Override
    public FilterReply decide(ILoggingEvent event) {
        String exception = "";
        IThrowableProxy iThrowableProxy1 = event.getThrowableProxy();
        if (iThrowableProxy1 != null) {
            exception = "" + iThrowableProxy1.getClassName() + " " + iThrowableProxy1.getMessage() + "
"
;
            for (int i = 0; i < iThrowableProxy1.getStackTraceElementProxyArray().length; i++) {
                exception += "" + iThrowableProxy1.getStackTraceElementProxyArray()[i].toString() + "
"
;
            }
        }
 // 创建日志消息
        LoggerMessage loggerMessage = new LoggerMessage(
                event.getMessage()
                , DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
                event.getThreadName(),
                event.getLoggerName(),
                event.getLevel().levelStr,
                exception,
                ""
        );
 // 写入非阻塞队列
        LoggerQueue.getInstance().push(loggerMessage);
        return FilterReply.ACCEPT;
    }
}

3.写入过后呢,就由websocket类,来实时提取返回给前端,核心代码如下,其实就是建立一个线程池,不停的去获取队列的内容,如果有的话就返回给前端,如果没有就跳过(当然这样实现很挫,最好呢是吧消息丢进消息队列,然后有一个服务端去异步消费个前端)。

public void send() {
    ExecutorService executorService= Executors.newFixedThreadPool(2);
    Runnable runnable= () -> {
        while (true) {
            try {
                LoggerMessage log = LoggerQueue.getInstance().poll();
                if(log!=null){
                    sendInfo(JSON.toJSONString(log), "logger");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    executorService.submit(runnable);
}

结束

通过上面一顿操作,我们基本完成了,一个日志服务的搭建过程,不过这里还是有不足的,比如利用消息队列来中转日志消息,这样可以避免开线程给服务器造成无关的压力。

代码仓库

https://github.com/bertguan96/zeus

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

截止目前,累计输出 69w+ 字,讲解图 2776+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2400+小伙伴加入


1. 我的私密学习小圈子,从0到1手撸企业实战项目!

2. 提高系统吞吐量的一把利器:DeferredResult 到底有多强?

3. 如何保证mongodb和数据库双写数据一致性?

4. 面试官:如果要存ip地址,用什么数据类型比较好

最近面试BAT,整理一份面试资料Java面试BATJ通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下在看,加个星标,这样每次新文章推送才会第一时间出现在你的订阅列表里。

“在看”支持小哈呀,谢谢啦

小哈学Java
码龄9年,前某厂中台研发。专注于Java领域干货分享,不限于BAT面试, 算法,数据库,Spring Boot, 微服务,高并发, JVM, Docker容器,ELK相关知识,期待与您一同进步。
 最新文章