spring?boot集成WebSocket日志實(shí)時(shí)輸出到web頁面

前言
今天來做個(gè)有趣的東西,就是實(shí)時(shí)將系統(tǒng)日志輸出的前端web頁面,因?yàn)槭菍?shí)時(shí)輸出,所有第一時(shí)間就想到了使用webSocket,而且在spring boot中,使用websocket超級(jí)方便,閱讀本文,你會(huì)接觸到以下關(guān)鍵詞相關(guān)技術(shù),WebSocket(stopmp服務(wù)端),stomp協(xié)議,sockjs.min.js,stomp.min.js(stomp客戶端),本文使用到的其實(shí)就是使用spring boot自帶的webSocket模塊提供stomp的服務(wù)端,前端使用stomp.min.js做stomp的客戶端,使用sockjs來鏈接,前端訂閱后端日志端點(diǎn)的消息,后端實(shí)時(shí)推送,達(dá)到日志實(shí)時(shí)輸出到web頁面的目的,效果如下圖

首先了解下stomp
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,簡單(流)文本定向消息協(xié)議,它提供了一個(gè)可互操作的連接格式,允許STOMP客戶端與任意STOMP消息代理(Broker)進(jìn)行交互。STOMP協(xié)議由于設(shè)計(jì)簡單,易于開發(fā)客戶端,因此在多種語言和多種平臺(tái)上得到廣泛地應(yīng)用。
STOMP協(xié)議的前身是TTMP協(xié)議(一個(gè)簡單的基于文本的協(xié)議),專為消息中間件設(shè)計(jì)。
STOMP是一個(gè)非常簡單和容易實(shí)現(xiàn)的協(xié)議,其設(shè)計(jì)靈感源自于HTTP的簡單性。盡管STOMP協(xié)議在服務(wù)器端的實(shí)現(xiàn)可能有一定的難度,但客戶端的實(shí)現(xiàn)卻很容易。例如,可以使用Telnet登錄到任何的STOMP代理,并與STOMP代理進(jìn)行交互。
下面是具體的步驟,主要是日志信息的獲取和日志信息的推送,不多說,上代碼
一.引入spring boot websocket依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
二.新增日志消息實(shí)體
/**
* Created by kl on 2017/10/9.
* Content :日志消息實(shí)體,注意,這里為了減少篇幅,省略了get,set代碼
*/
public class LoggerMessage{
private String body;
private String timestamp;
private String threadName;
private String className;
private String level;
public LoggerMessage(String body, String timestamp, String threadName, String className, String level) {
this.body = body;
this.timestamp = timestamp;
this.threadName = threadName;
this.className = className;
this.level = level;
}
public LoggerMessage() {
}
}三. 創(chuàng)建一個(gè)阻塞隊(duì)列
作為日志系統(tǒng)輸出的日志的一個(gè)臨時(shí)載體
public class LoggerQueue {
//隊(duì)列大小
public static final int QUEUE_MAX_SIZE = 10000;
private static LoggerQueue alarmMessageQueue = new LoggerQueue();
//阻塞隊(duì)列
private BlockingQueueblockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
private LoggerQueue() {
}
public static LoggerQueue getInstance() {
return alarmMessageQueue;
}
/**
* 消息入隊(duì)
* @param log
* @return
*/
public boolean push(LoggerMessage log) {
return this.blockingQueue.add(log);//隊(duì)列滿了就拋出異常,不阻塞
}
/**
* 消息出隊(duì)
* @return
*/
public LoggerMessage poll() {
LoggerMessage result = null;
try {
result = this.blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}四.獲取logback的日志,塞入日志隊(duì)列中
1.定義Logfilter攔截輸出日志
public class LogFilter extends Filter{
@Override
public FilterReply decide(ILoggingEvent event) {
LoggerMessage loggerMessage = new LoggerMessage(
event.getMessage()
, DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
event.getThreadName(),
event.getLoggerName(),
event.getLevel().levelStr
);
LoggerQueue.getInstance().push(loggerMessage);
return FilterReply.ACCEPT;
}
}2.配置logback.xml,添加我們自定義的filter
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE"
value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}" />
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<filter class="com.example.websocket.LogFilter"></filter>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="CONSOLE" />
</root>
</configuration>五.配置WebSocket消息代理端點(diǎn),即stomp服務(wù)端
@Configuration
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket")
.setAllowedOrigins("http://localhost:8976")
.withSockJS();
}
}注意:為了連接安全,setAllowedOrigins設(shè)置的允許連接的源地址,如果在非這個(gè)配置的地址下發(fā)起連接會(huì)報(bào)403,進(jìn)一步還可以使用addInterceptors設(shè)置攔截器,來做相關(guān)的鑒權(quán)操作
六.啟動(dòng)類,開啟webSocket消息代理功能,并推送日志信息
@SpringBootApplication
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebsocketApplication {
private Logger logger = LoggerFactory.getLogger(WebsocketApplication.class);
public static void main(String[] args) {
SpringApplication.run(WebsocketApplication.class, args);
}
@Autowired
private SimpMessagingTemplate messagingTemplate;
int info=1;
@Scheduled(fixedRate = 1000)
public void outputLogger(){
logger.info("測試日志輸出"+info++);
}
/**
* 推送日志到/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);
executorService.submit(runnable);
}
}七.html頁面,連接stomp服務(wù)端,訂閱/topic/pullLogger的消息,展示日志信息
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket Logger</title>
<script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
</head>
<body>
<button onclick="openSocket()">開啟日志</button><button onclick="closeSocket()">關(guān)閉日志</button>
<div id="log-container" style="height: 450px; overflow-y: scroll; background: #333; color: #aaa; padding: 10px;">
<div></div>
</div>
</body>
<script>
var stompClient = null;
$(document).ready(function() {openSocket();});
function openSocket() {
if(stompClient==null){
var socket = new SockJS('http://localhost:8084/websocket?token=kl');
stompClient = Stomp.over(socket);
stompClient.connect({token:"kl"}, function(frame) {
stompClient.subscribe('/topic/pullLogger', function(event) {
var content=JSON.parse(event.body);
$("#log-container div").append(content.timestamp +" "+ content.level+" --- ["+ content.threadName+"] "+ content.className+" :"+content.body).append("<br/>");
$("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
},{
token:"kltoen"
});
});
}
}
function closeSocket() {
if (stompClient != null) {
stompClient.disconnect();
stompClient=null;
}
}
</script>
</body>
</html>參考地址:
stomp.js客戶端:http://jmesnil.net/stomp-websocket/doc/
scok.js客戶端:https://github.com/sockjs/sockjs-client
spring webSocket:https://docs.spring.io/spring/docs/
以上就是spring boot集成WebSocket到web頁面實(shí)時(shí)輸出的詳細(xì)內(nèi)容,更多關(guān)于spring boot集成WebSocket實(shí)時(shí)輸出到web的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
spring boot使用WebClient調(diào)用HTTP服務(wù)代碼示例
這篇文章主要介紹了spring boot使用WebClient調(diào)用HTTP服務(wù)代碼示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
@JsonFormat處理LocalDateTime失效的問題
這篇文章主要介紹了關(guān)于@JsonFormat處理LocalDateTime失效的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
Java8新特性之泛型的目標(biāo)類型推斷_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
泛型是Java SE 1.5的新特性,泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)。下面通過本文給分享Java8新特性之泛型的目標(biāo)類型推斷,感興趣的朋友參考下吧2017-06-06
SpringCloud?Nacos?+?Ribbon?調(diào)用服務(wù)的實(shí)現(xiàn)方式(兩種)
這篇文章主要介紹了SpringCloud?Nacos?+?Ribbon?調(diào)用服務(wù)的兩種方法,分別是通過代碼的方式調(diào)用服務(wù)和通過注解方式調(diào)用服務(wù),每種方式給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
Springboot整合Mybatis傳值的常用方式總結(jié)
今天給大家?guī)淼氖顷P(guān)于Springboot的相關(guān)知識(shí),文章圍繞著Springboot整合Mybatis傳值的常用方式展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Spring事務(wù)管理下synchronized鎖失效問題的解決方法
這篇文章主要給大家介紹了關(guān)于Spring事務(wù)管理下synchronized鎖失效問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03
Springboot如何讀取resources下的json配置文件
這篇文章主要介紹了Springboot如何讀取resources下的json配置文件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Java實(shí)現(xiàn)隨機(jī)出題,10道10以內(nèi)加減法計(jì)算代碼實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)隨機(jī)出題,10道10以內(nèi)加減法計(jì)算,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04

