vue+springboot实现控制台日志实时推送前台

前言

​ 因为最近在做课程设计,然后采用了前后端分离技术进行开发,后端完成之后被我放到了阿里云服务器上,代码肯定有出问题的时候,刚开始每一次出问题我都跑去连接服务器,然后看问题,(一方面因为我懒没在上面挂专门的日志监控的,另一方面是部署后端的机器是学生机跑太多会影响机器的效率)非常麻烦,所以我就突发奇想做一个类似这个的日志监控程序,放在管理后台里面,在出错的时候用鲜明的颜色标注出来,便于查找问题。于是我就跑去研究折腾这个东西,还好网上有可以参考的地方。经过了2天的折腾已经初步完成了日志监控。

1.准备工作

1.1 环境准备

后端框架:springboot

前端框架:vue

通信方式:websocket

1.2 依赖准备

前端依赖:

npm install stompjs

运行npm run dev可能会报错,提示安装net,执行命令

npm install --save net

后端依赖:

这里两种方式都可以,具体你的框架是采用哪种方式搭建的。

maven:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

gradle:

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '2.1.5.RELEASE'

:因为我搭建的springboot是基于2.1.5的所以我后面标明了版本,如果你是其他版本的,也可以修改。

2.原理解释

实现原理十分简单,一个过滤器(LogFilter),一个socket配置(WebSocketConfig),一个消息实体(LoggerMessage),一个任务队列(LoggerQueue),一个日志配置文件(logback.xml)即可完成该功能。

具体原理如图所示:

在这里插入图片描述
这是简单的原理图,仅供参考。

3.代码实现

3.1 前端部分代码

openSocket() {
  if (this.stompClient == null) {
    this.res = "<div style='color: #18d035;font-size: 14px'>通道连接成功,静默等待....</div>"
    // this.$refs['logContainerDiv'].append();
    // 建立连接对象
    let socket = new SockJS('http://127.0.0.1:8080/websocket?token=kl');
    // 获取STOMP子协议的客户端对象
    this.stompClient = Stomp.over(socket);
    this.stompClient.connect({token: 'kl'}, () => {
      this.stompClient.subscribe('/topic/pullLogger',(event) =>  {
          let content = JSON.parse(event.body);
          let leverhtml = '';
          let className = "<span style='color: #229379'>"+content.className + "</span>";
          switch (content.level) {
            case 'INFO':
              leverhtml = "<span style='color: #90ad2b'>" +content.level + "</span>";
              break;
            case 'DEBUG':
              leverhtml = "<span style='color: #A8C023'>" +content.level + "</span>";
              break;
            case 'WARN':
              leverhtml = "<span style='color: #fffa1c'>" +content.level + "</span>";
              break;
            case 'ERROR':
              leverhtml = "<span style='color: #e3270e'>" +content.level + "</span>";
              break;
          }
          this.res+= "<div style='color: #18d035;font-size: 14px'>" + content.timestamp + " " +leverhtml + " --- [" + content.threadName + "] " + className + " :" + content.body + "</div>"
          // this.$refs['logContainerDiv'].append(content.timestamp + " " + leverhtml + " --- [" + content.threadName + "] " + className + " :" + content.body + "<br/>");
          if (content.exception != "") {
            this.res+= "<div>" + content.exception + "</div>"
              // this.$refs['logContainerDiv'].append();
          }
          if (content.cause != "") {
            this.res+= "<div>" + content.cause + "</div>"
            // this.$refs['logContainerDiv'].append(content.cause);
          }
          // this.$refs['logContainer'].scrollTo(this.$refs['logContainerDiv'].height() - this.$refs['logContainer'].height());
        },
        {
          token: "kltoen"
        });
    });
  }
},
closeSocket() {
  if (this.stompClient != null) {
    this.stompClient.disconnect();
    this.stompClient = null;
  }
},

3.2 后端部分代码

后端部分在websocket模块,需要在对应的类上面加入@EnableWebSocketMessageBroker注解,如果不加该注解在运行程序的时候SimpMessagingTemplate类将抛出空指针异常,无法注入。

/**
 * @author gjt
 * @version 1.0
 * @date 2019/6/24 21:13
 * @Description 这里写描述内容
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket")
                .setAllowedOrigins("*")
                .withSockJS();
    }

    /**
     * 推送日志到/topic/pullLogger
     */
    @PostConstruct
    public void pushLogger(){
        ExecutorService executorService= Executors.newFixedThreadPool(2);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        LoggerMessage log = LoggerQueue.getInstance().poll();
                        if(log!=null){
                            if(messagingTemplate!=null)
                            {
                                messagingTemplate.convertAndSend("/topic/pullLogger",log);
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        executorService.submit(runnable);
    }
}
/**
 * @author gjt
 * @version 1.0
 * @date 2019/6/24 21:12
 * @Description 这里写描述内容
 */
@Service
public class LogFilter extends Filter<ILoggingEvent> {

    @Override
    public FilterReply decide(ILoggingEvent event) {
        String exception = "";
        IThrowableProxy iThrowableProxy1 = event.getThrowableProxy();
        if(iThrowableProxy1!=null){
            exception = "<span class='excehtext'>"+iThrowableProxy1.getClassName()+" "+iThrowableProxy1.getMessage()+"</span></br>";
            for(int i=0; i<iThrowableProxy1.getStackTraceElementProxyArray().length;i++){
                exception += "<span class='excetext'>"+iThrowableProxy1.getStackTraceElementProxyArray()[i].toString()+"</span></br>";
            }
        }
        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.3 配置部分

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--encoder 默认配置为PatternLayoutEncoder-->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level -&#45;&#45; [%thread] %logger Line:%-3L - %msg%n</pattern>-->
            <charset>UTF-8</charset>
        </encoder>
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="org.rc.base.filter.LogFilter"></filter>
    </appender>

4.运行结果

在这里插入图片描述
这是我已经整合进入后台管理之后的效果图。

5.参考文档

spring-boot推送实时日志到前端页面显示
spring boot集成WebSocket
vue中使用stompjs实现mqtt消息推送通知

6.最后

代码我没有放全,如果有需要的可以留言,我鼓励大家更多的是主动去尝试写完代码。

以上代码存在一个问题就是实时性,就是日志只能实时反馈不能看几分钟以内的,这一点需要改进,也是我后期打算优化的地方。

最近准备做一下升级,有兴趣的可以M住,后面会更新。

Logo

前往低代码交流专区

更多推荐