websocket實(shí)現(xiàn)方法日志實(shí)時查詢過程
本次方法的核心概念是通過redis生成唯一key值(沒有放出來),然后通過前端獲取這個唯一的key帶入到方法請求中,然后服務(wù)器通過這個key生成此次方法生成唯一的日志文件,websocket接口通過線程實(shí)時讀取key文件返回內(nèi)容。
1、通過logback生成日志工具類
package utils;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.OptionHelper;
import cn.sunline.dds.common.framework.constants.SocketLog;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* 日志構(gòu)建器
* @author Mr.Ye
* @Description: 該方法不會在控制臺打印日志,只用于生成指定文件的日志
*/
public class LoggerBuilder {
private static final Map<String,Logger> container = new HashMap<>();
private static SocketLog socketLog;
public static Logger getLogger(String key) {
Logger logger = container.get(key);
if(logger != null) {
return logger;
}
synchronized (LoggerBuilder.class) {
logger = container.get(key);
if(logger != null) {
return logger;
}
logger = build(key);
container.put(key,logger);
}
return logger;
}
//刪除container中的log
public static void close(String key) {
if (container.containsKey(key)) {
container.remove(key);
}
}
private static Logger build(String key) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger logger = context.getLogger("FILELOGGER");
logger.setAdditive(false);
RollingFileAppender appender = new RollingFileAppender();
appender.setContext(context);
appender.setName("FILELOGGER");
appender.setFile(OptionHelper.substVars("/logs/"+ key + ".log",context));
appender.setAppend(true);
appender.setPrudent(false);
//重命名日志文件
SizeAndTimeBasedRollingPolicy policy = new SizeAndTimeBasedRollingPolicy();
String fp = OptionHelper.substVars("/logs/" + key + ".log.%d{yyyy-MM-dd}.%i",context);
policy.setMaxFileSize(FileSize.valueOf("128MB"));
policy.setFileNamePattern(fp);
policy.setMaxHistory(7);
policy.setTotalSizeCap(FileSize.valueOf("32GB"));
policy.setParent(appender);
policy.setContext(context);
policy.start();
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(context);
encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n");
encoder.start();
appender.setRollingPolicy(policy);
appender.setEncoder(encoder);
appender.start();
logger.addAppender(appender);
return logger;
}
}2、構(gòu)建websocket方法
package socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Data: 2022/04/22
* @author Mr.Ye
* @deprecated 由于讀寫文件占用資源較大,所以暫時不使用
*/
@ServerEndpoint("/log/{key}")
@Component
public class LogWebSocket {
private final Logger logger = LoggerFactory.getLogger(LogWebSocket.class);
/**
* 記錄當(dāng)前總共有多少個連接
*/
private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0);
/**
* 項(xiàng)目路徑
*/
private static final String PROPERTY = System.getProperty("user.dir");
/**
* 存放當(dāng)前正在做靈活開發(fā)的session對象
*/
private static final Map<String, Session> CLIENTS = new ConcurrentHashMap<>();
/**
* 新的WebSocket請求開啟
* @describe 當(dāng)前方法各系統(tǒng)通用,通過file獲取日志文件,linux下可以使用tail -f持續(xù)獲取日志文件更方便
*/
@OnOpen
public void onOpen(Session session,@PathParam("key")String key) {
try {
System.out.println("------------------key------------------:"+key);
// 建立連接梳理 加 1
ONLINE_COUNT.incrementAndGet();
// 將當(dāng)前創(chuàng)建的session 存儲起來
CLIENTS.put(session.getId(), session);
logger.info("有新窗口打開連接加入:{},當(dāng)前正在查詢總數(shù)為:{}", session.getId(), ONLINE_COUNT.get());
String url = PROPERTY+ "/logs/"+key+".log";
LogFileTailer tailer = new LogFileTailer(url);
tailer.addListener(log -> {
try {
// session.getBasicRemote().sendText(log + "<br />");
session.getBasicRemote().sendText(log);
} catch (IOException e) {
e.printStackTrace();
}
});
tailer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* WebSocket請求關(guān)閉
*/
@OnClose
public void onClose(Session session) {
// 建立連接梳理 減 1
ONLINE_COUNT.decrementAndGet();
// 刪除緩存起來的session
CLIENTS.remove(session.getId());
logger.info("有一窗口連接關(guān)閉:{},當(dāng)前正在開發(fā)總數(shù)為:{}", session.getId(), ONLINE_COUNT.get());
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
// 建立連接梳理 減 1
ONLINE_COUNT.decrementAndGet();
// 刪除緩存起來的session
CLIENTS.remove(session.getId());
logger.error("發(fā)生錯誤,刪除當(dāng)前session:{},剩余正在總數(shù)為:{}", session.getId(), ONLINE_COUNT.get());
}
}3、日志監(jiān)聽獲取
package socket;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.function.Consumer;
/**
* @Data: 2022/04/24
* @author Mr.Ye
* @deprecated 由于讀寫文件占用資源較大,所以暫時不使用
*/
public class LogFileTailer extends Thread {
private File logfile;
private Consumer<String> callback;
/**
* 監(jiān)視開關(guān),true = 打開監(jiān)視
*/
private boolean tailing = true;
/**
*
* @param file 要監(jiān)視的文本文件
*/
public LogFileTailer(String file) {
logfile = new File(file);
}
/**
*Tailing 開關(guān)
* @param tailing
*/
public void Tailing(boolean tailing) {
this.tailing = tailing;
}
/**
* 設(shè)置回調(diào)事件
*
* @param callback 回調(diào)事件
*/
public void addListener(Consumer<String> callback) {
this.callback = callback;
}
@Override
public void run() {
/*上一次讀取文件位置*/
long filePointer =0;
try {
RandomAccessFile file = new RandomAccessFile(logfile, "r");
while (tailing) {
long fileLength = logfile.length();
if (fileLength < filePointer) {
file = new RandomAccessFile(logfile, "r");
filePointer = 0;
}
if (fileLength > filePointer) {
file.seek(filePointer);
String line = file.readLine();
while (line != null) {
line = new String(line.getBytes("ISO-8859-1"), "utf-8");
if (callback != null){
callback.accept(line);
}
line = file.readLine();
}
filePointer = file.getFilePointer();
}
// sleep(sampleInterval);
}
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、問題補(bǔ)充
SpringBoot中需要注入bean,通過注入一個ServerEndpointExporter,該Bean會自動注冊使用@ServerEndpoint注解申明的websocket endpoint。
要注意:如果使用獨(dú)立的servlet容器,而不是直接使用springboot的內(nèi)置容器,
就不要注入ServerEndpointExporter,因?yàn)樗鼘⒂扇萜髯约禾峁┖凸芾?,所以在注?/p>
ServerEndpointExporter時需要增加一些配置
package conf;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* 注入一個ServerEndpointExporter,
* 該Bean會自動注冊使用@ServerEndpoint注解申明的websocket endpoint
* @author Mr.Ye
*
*/
@Configuration
public class WebSocketConfig implements ServletContextInitializer {
/**
* 要注意,如果使用獨(dú)立的servlet容器,而不是直接使用springboot的內(nèi)置容器,
* 就不要注入ServerEndpointExporter,因?yàn)樗鼘⒂扇萜髯约禾峁┖凸芾?
* 所以增加以下配置:
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize", String.valueOf(10 * 1024 * 1024));
servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize", String.valueOf(10 * 1024 * 1024));
}
}
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
利用EasyExcel導(dǎo)出帶有選擇校驗(yàn)框的excel
EasyExcel是一個輕量級的Excel處理工具,支持Excel?2003(xls)和Excel?2007及以上版本(xlsx)的文件格式,本文將利用EasyExcel導(dǎo)出帶有選擇校驗(yàn)框的excel,需要的可以參考下2024-12-12
解決SpringMVC Controller 接收頁面?zhèn)鬟f的中文參數(shù)出現(xiàn)亂碼的問題
下面小編就為大家分享一篇解決SpringMVC Controller 接收頁面?zhèn)鬟f的中文參數(shù)出現(xiàn)亂碼的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03
Spring Boot中的WebSocketMessageBrokerConfigurer接口使用
在SpringBoot中,我們可以使用 WebSocketMessageBrokerConfigurer接口來配置WebSocket消息代理,以實(shí)現(xiàn)實(shí)時通信,具有一定的參考價值,感興趣的可以了解一下2023-11-11
基于Feign實(shí)現(xiàn)異步調(diào)用
近期,需要對之前的接口進(jìn)行優(yōu)化,縮短接口的響應(yīng)時間,但是springcloud中的feign是不支持傳遞異步化的回調(diào)結(jié)果的,因此有了以下的解決方案,記錄一下,需要的朋友可以參考下2021-05-05
兩種Eclipse部署動態(tài)web項(xiàng)目方法
這篇文章主要介紹了兩種Eclipse部署動態(tài)web項(xiàng)目方法,需要的朋友可以參考下2015-11-11
Java線程池并發(fā)執(zhí)行多個任務(wù)方式
這篇文章主要介紹了Java線程池并發(fā)執(zhí)行多個任務(wù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
Spring 事件監(jiān)聽機(jī)制實(shí)現(xiàn)跨模塊調(diào)用的思路詳解
之前一個項(xiàng)目,有兩個模塊,A 模塊需要依賴 B 模塊,但現(xiàn)在 B 模塊有地方需要調(diào)用 A 模塊的方法,如果直接依賴,又會產(chǎn)生循環(huán)依賴問題,最終選擇使用 spring 的事件監(jiān)聽來解決該問題,下面給大家介紹Spring 事件監(jiān)聽機(jī)制實(shí)現(xiàn)跨模塊調(diào)用的思路,感興趣的朋友一起看看吧2024-05-05

