描述
该方法是通过websocket连接方式,将filter过滤器收集到的日志信息保存到队列中。连接成功之后,将会获取到队列中的日志信息,发送给客户端,回显到页面上。
maven依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
工具类
阻塞队列工具类
/**
* 阻塞队列工具类
*
* @author: 苦瓜不苦
* @date: 2022/6/26 1:24
**/
public class LogQueueUtil {
// 队列大小
public final static int queue_max_size = 10000;
// 阻塞队列
private final static BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(queue_max_size);
/**
* 消息入队
*
* @param message
* @return
*/
public static void push(String message) {
// 队列满了就抛出异常,不阻塞
blockingQueue.add(message);
}
/**
* 消息出队
*
* @return
*/
public static String poll() {
try {
return blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
储存websocket实例工具类
/**
* 储存websocket对象工具类
*
* @author: 苦瓜不苦
* @date: 2022/6/26 1:31
**/
@Slf4j
public class SessionUtil {
// 储存Session实例对象
private final static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
// 当前在线人数统计
private final static AtomicInteger counter = new AtomicInteger();
/**
* 添加Session
*
* @param key
* @param session
*/
public synchronized static void put(String key, Session session) {
sessionMap.put(key, session);
log.info("ID {} 连接成功,当前在线人数 >>>>>> {}", key, counter.incrementAndGet());
}
/**
* 移除并关闭Session
*
* @param key
*/
public synchronized static void remove(String key) {
try {
Session session = sessionMap.remove(key);
session.close();
log.info("ID {} 关闭成功,当前在线人数 >>>>>> {}", key, counter.decrementAndGet());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 指定key获取Session
*
* @param key
* @return
*/
public static Session get(String key) {
return sessionMap.get(key);
}
/**
* 获取所有Session
*
* @return
*/
public static Map<String, Session> getAll() {
return sessionMap;
}
}
日志filter过滤器
继承Filter<ILoggingEvent>类,重写decide方法。获取到日志信息,储存到队列中。
注意:将定义好的日志过滤类设置到logback.xml配置中。将
<filter class="org.example.config.LogFilter"/>添加到<appender>标签里面。才能使定义好的类生效
/**
* 日志收集过滤器
*
* @author: 苦瓜不苦
* @date: 2022/6/26 1:49
**/
public class LogFilter extends Filter<ILoggingEvent> {
/**
* 重新decide方法,获取到日志信息
*
* @param iLoggingEvent
* @return
*/
@Override
public FilterReply decide(ILoggingEvent iLoggingEvent) {
String message = StrUtil.strBuilder()
.append("<pre>")
.append(DateUtil.date(iLoggingEvent.getTimeStamp()))
.append("\t")
.append(iLoggingEvent.getLevel().levelStr)
.append("\t")
.append(iLoggingEvent.getLoggerName())
.append("\t")
.append(iLoggingEvent.getThreadName())
.append("\t")
.append(iLoggingEvent.getFormattedMessage())
.append("<pre>")
.toString();
// 日志信息发送到队列中
LogQueueUtil.push(message);
return FilterReply.ACCEPT;
}
}
websocket配置类
/**
* websocket配置类
*
* @author: 苦瓜不苦
* @date: 2022/6/26 1:39
**/
@Configuration
public class WebsocketConfig {
/**
* 开启websocket注解模式
* 会扫描带有@ServerEndpoint注解的类
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* 发送日志信息
* 启动程序,该方法会自动执行
*/
@PostConstruct
public void sendLog() {
// 开启多线程,避免阻塞程序启动
ThreadUtil.execute(() -> {
while (true) {
// 获取阻塞队列中的消息,无消息时会进行阻塞
String message = LogQueueUtil.poll();
// 获取当前连接的websocket实例
Map<String, Session> sessionMap = SessionUtil.getAll();
sessionMap.forEach((key, session) -> {
// 发送消息
if (session.isOpen()) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
});
}
}
连接websocket类
/**
* @author: 苦瓜不苦
* @date: 2022/6/25 0:26
**/
@Slf4j
@Component
@ServerEndpoint("/websocket")
public class WebsocketController {
/**
* 建立连接
*
* @param session
*/
@OnOpen
public void onOpen(Session session) {
SessionUtil.put(session.getId(), session);
}
/**
* 收到客户端消息后调用的方法
*
* @param session
* @param message
*/
@OnMessage
public void onMessage(Session session, String message) {
log.info("客户端发送的消息是 >>>>>> {}", message);
}
/**
* 关闭连接
*
* @param session
*/
@OnClose
public void onClose(Session session) {
SessionUtil.remove(session.getId());
}
/**
* 异常方法
*
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
log.error("错误原因 >>>>>> {}", throwable.getMessage());
}
}
html页面
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket日志打印</title>
</head>
<body>
<input id="text" type="text" />
<button onclick="send()">Send</button>
<button onclick="closeWebSocket()">Close</button>
<div id="message"></div>
</body>
<script type="text/javascript">
let ws = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
ws = new WebSocket("ws://localhost:29527/api/websocket");
}
else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
ws.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
ws.onopen = function(event) {
console.log("ws调用连接成功回调方法")
}
//接收到消息的回调方法
ws.onmessage = function(message) {
if (typeof(message.data) == 'string') {
setMessageInnerHTML(message.data);
}
}
//ws连接断开的回调方法
ws.onclose = function(e) {
console.log("ws连接断开")
//console.log(e)
setMessageInnerHTML("ws close");
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML;
}
//关闭连接
function closeWebSocket() {
ws.close();
}
//发送消息
function send(msg) {
if(!msg){
msg = document.getElementById('text').value;
document.getElementById('message').innerHTML += "发送的消息:" + msg + '<br/>';
ws.send(msg);
}
}
</script>
</html>