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 --- [%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住,后面会更新。