Springboot + Websocket + Sockjs + Stomp + Vue + Iview 实现java后端日志显示在前端web页面上
话不多说,看代码。
一、pom.xml 引入spring boot websocket依赖
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-websocket</artifactId> 4 </dependency>
二、WebSocketConfig 配置WebSocket消息代理端点,即stomp服务端
1 @Slf4j 2 @Configuration 3 @EnableWebSocketMessageBroker // 注解开启STOMP协议来传输基于代理的消息,此时控制器支持使用 4 @MessageMapping 5 public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer 6 { 7 @Autowired 8 private SimpMessagingTemplate messagingTemplate; 9 10 // 测试 11 // int info = 1; 12 // 13 // @Scheduled(fixedRate = 4000) 14 // public void outputLogger() 15 // { 16 // log.info("测试日志输出" + info++); 17 // } 18 19 @Override 20 public void registerStompEndpoints(StompEndpointRegistry registry) 21 { 22 // 将clientMessage注册为STOMP的一个端点 23 // 客户端在订阅或发布消息到目的路径前,要连接该端点 24 // setAllowedOrigins允许所有域连接,否则浏览器可能报403错误 25 registry.addEndpoint("/websocket").setAllowedOrigins("*").addInterceptors().withSockJS(); // 26 } 27 28 29 30 /** 31 * 推送日志到/topic/pullLogger 32 */ 33 @PostConstruct 34 public void pushLogger() 35 { 36 ExecutorService executorService = Executors.newFixedThreadPool(4); 37 Runnable fileLog = new Runnable() 38 { 39 @Override 40 public void run() 41 { 42 while (true) 43 { 44 try 45 { 46 String log = LoggerQueue.getInstance().poll().toString(); 47 if (log != null) 48 { 49 if (messagingTemplate != null) 50 messagingTemplate.convertAndSend("/topic/pullFileLogger", log); 51 } 52 } 53 catch (Exception e) 54 { 55 e.printStackTrace(); 56 } 57 } 58 } 59 }; 60 executorService.submit(fileLog); 61 executorService.submit(fileLog); 62 } 63 }
三、新增日志消息实体
1 /* 2 * 日志消息实体类,这里用了lombok插件 3 * 没有安装该插件的话,就手动添加get、set方法、toString方法 4 */ 5 @Getter 6 @Setter 7 @ToString 8 @AllArgsConstructor 9 public class LoggerMessage 10 { 11 private String body; 12 private String timestamp; 13 private String threadName; 14 private String className; 15 private String level; 16 private String exception; 17 private String cause; 18 }
四、创建一个阻塞队列,作为日志系统输出的日志的一个临时载体
1 /* 2 * 创建一个阻塞队列,作为日志系统输出的日志的一个临时载体 3 */ 4 public class LoggerQueue 5 { 6 // 队列大小 7 public static final int QUEUE_MAX_SIZE = 10000; 8 private static LoggerQueue alarmMessageQueue = new LoggerQueue(); 9 // 阻塞队列 10 private BlockingQueue blockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE); 11 12 private LoggerQueue() 13 { 14 } 15 16 public static LoggerQueue getInstance() 17 { 18 return alarmMessageQueue; 19 } 20 21 /** 22 * 消息入队 23 * 24 * @param log 25 * @return 26 */ 27 public boolean push(LoggerMessage log) 28 { 29 return this.blockingQueue.add(log);// 队列满了就抛出异常,不阻塞 30 } 31 32 /** 33 * 消息出队 34 * 35 * @return 36 */ 37 public LoggerMessage poll() 38 { 39 LoggerMessage result = null; 40 try 41 { 42 result = (LoggerMessage) this.blockingQueue.take(); 43 } 44 catch (InterruptedException e) 45 { 46 e.printStackTrace(); 47 } 48 return result; 49 } 50 }
五、获取logback的日志,塞入日志队列中
1.定义Logfilter拦截输出日志
1 @Service 2 public class LogFilter extends Filter<ILoggingEvent> 3 { 4 @Override 5 public FilterReply decide(ILoggingEvent event) 6 { 7 String exception = ""; 8 IThrowableProxy iThrowableProxy1 = event.getThrowableProxy(); 9 if (iThrowableProxy1 != null) 10 { 11 exception = "<span class='excehtext'>" + iThrowableProxy1.getClassName() + " " + iThrowableProxy1.getMessage() + "</span></br>"; 12 for (int i = 0; i < iThrowableProxy1.getStackTraceElementProxyArray().length; i++) 13 { 14 exception += "<span class='excetext'>" + iThrowableProxy1.getStackTraceElementProxyArray()[i].toString() + "</span></br>"; 15 } 16 } 17 LoggerMessage loggerMessage = new LoggerMessage(event.getMessage(), DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())), event.getThreadName(), event.getLoggerName(), 18 event.getLevel().levelStr, exception, ""); 19 LoggerQueue.getInstance().push(loggerMessage); 20 return FilterReply.ACCEPT; 21 } 22 }
2.配置logback-spring.xml,添加我们自定义的filter
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> 3 <!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true --> 4 <!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。 5 当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> 6 <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> 7 <configuration scan="true" scanPeriod="10 seconds"> 8 <contextName>logback</contextName> 9 <include resource="org/springframework/boot/logging/logback/defaults.xml" /> 10 <include resource="org/springframework/boot/logging/logback/file-appender.xml" /> 11 <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 --> 12 <property name="log.path" value="/var/log/" /> 13 14 <!--0. 日志格式和颜色渲染 --> 15 <!-- 彩色日志依赖的渲染类 --> 16 <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> 17 <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /> 18 <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /> 19 <!-- 彩色日志格式 --> 20 <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{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}}"/> 21 22 <!--1. 输出到控制台--> 23 <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 24 <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> 25 <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> 26 <level>info</level> 27 </filter> --> 28 <encoder> 29 <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> 30 <!-- 设置字符集 --> 31 <charset>UTF-8</charset> 32 </encoder> 33 <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> 34 <filter class="com.hmkj.ddos.config.LogFilter"> 35 </filter> 36 </appender> 37 38 <!-- 2.2 level为 INFO 日志,时间滚动输出 --> 39 <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 40 <!-- 正在记录的日志文档的路径及文档名 --> 41 <file>${log.path}/hmddos.log</file> 42 <!--日志文档输出格式--> 43 <encoder> 44 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> 45 <charset>UTF-8</charset> 46 </encoder> 47 <!-- 日志记录器的滚动策略,按日期,按大小记录 --> 48 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 49 <!-- 每天日志归档路径以及格式 --> 50 <fileNamePattern>${log.path}/web-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> 51 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 52 <maxFileSize>10MB</maxFileSize> 53 </timeBasedFileNamingAndTriggeringPolicy> 54 <!--日志文档保留天数--> 55 <maxHistory>15</maxHistory> 56 </rollingPolicy> 57 <!-- 此日志文档只记录info级别的 --> 58 <filter class="ch.qos.logback.classic.filter.LevelFilter"> 59 <level>info</level> 60 <onMatch>ACCEPT</onMatch> 61 <onMismatch>DENY</onMismatch> 62 </filter> 63 </appender> 64 65 <root level="info"> 66 <appender-ref ref="CONSOLE" /> 67 <appender-ref ref="INFO_FILE" /> 68 </root> 69 </configuration>
六、Vue中添加基于webSocket通信的库
先安装 sockjs-client 和 stompjs
注意: 在安装stompjs出现一个问题,net 模块不存在,则应该在项目根目录再执行npm install net 即可。
1 npm install sockjs-client 2 npm install stompjs
.vue页面
1 <template> 2 <div> 3 <Card dis-hover> 4 <p slot="title" style="padding-bottom: 20px; padding-top: 5px;"> 5 监听程序日志 6 </p> 7 <Button type="primary" @click="openSocket">开启日志</Button> 8 <Button type="error" @click="closeSocket">关闭日志</Button><br><br> 9 <div id="filelog-container" style="height: 400px; overflow-y: scroll; background: #333; color: #aaa; padding: 10px;"> 10 <div id="aa">{{pullFileLogger}}</div> 11 </div> 12 </Card> 13 </div> 14 </template> 15 16 <script> 17 // 安装并引入相关模块 18 import SockJS from 'sockjs-client'; 19 import Stomp from 'stompjs'; 20 export default { 21 data(){ 22 return { 23 pullLogger: '', 24 pullFileLogger: '正在连接websocket,请稍后~', 25 stompClient: null 26 } 27 }, 28 methods:{ 29 openSocket () { 30 if (this.stompClient === null) { 31 console.log('打开websocket连接') 32 this.pullFileLogger = '正在连接websocket,请稍后~' 33 let _that = this 34 var socket = new SockJS('http://localhost:7788/websocket?token=kl'); 35 _that.stompClient = Stomp.over(socket); 36 _that.stompClient.connect({token:"kl"}, function(frame) { 37 _that.stompClient.subscribe('/topic/pullFileLogger', function(event) { 38 _that.pullFileLogger = '' 39 var content = event.body 40 var aa = document.getElementById('aa') 41 var p = document.createElement('p') 42 p.style.wordWrap = 'break-word' 43 p.style.fontSize = 18 + 'px' 44 p.appendChild(document.createTextNode(event.body)) 45 aa.appendChild(p) 46 var div1 = document.getElementById('filelog-container') 47 div1.scrollTop = div1.scrollHeight 48 }, { 49 token:"kltoen" 50 }) 51 }) 52 } 53 }, 54 closeSocket () { 55 if (this.stompClient != null) { 56 this.stompClient.disconnect() 57 this.stompClient = null 58 console.log("关闭websocket连接") 59 this.pullFileLogger = 'websocket连接已关闭' 60 } 61 } 62 }, 63 //销毁页面之前,断开连接 64 beforeDestroy: function () { 65 //页面离开时断开连接,清除定时器 66 this.closeSocket() 67 }, 68 mounted(){ 69 //调用初始化websocket方法 70 this.openSocket() 71 } 72 } 73 </script> 74 <style> 75 </style>
七、效果图,字体颜色背景颜色可以自行修改样式。
有什么不对的地方欢迎大家评论指点。