websocket实现方法日志实时查询过程
目录
- 1、通过logback生成日志工具类
- 2、构建websocket方法
- 3、日志监听获取
- 4、问题补充
- 总结
本次方法的核心概念是通过Redis生成唯一key值(没有放出来),然后通过前端获取这个唯一的key带入到方法请求中,然后服务器通过这个key生成此次方法生成唯一的日志文件,websocket接口通过线程实时读取key文件返回内容。
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; iphpmport org.slf4j.LoggerFactory; import Java.util.HashMap; import java.util.Map; /** * 日志构建器 * @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、构建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); /** * 记录当前总共有多少个连接 */ private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0); /** * 项目路径 */ private static final String PROPERTY = System.getProperty("user.dir"); /** * 存放当前正在做灵活开发的session对象 */ private static final Map<String, Session> CLIENTS = new ConcurrentHashMap<>(); /** * 新的WebSocket请求开启 * @describe 当前方法各系统通用,通过file获取日志文件,linux下可以使用tail -f持续获取日志文件更方便 */ @OnOpen public void onOpen(Session session,@PathParam("key")String key) { try { System.out.println("------------------key------------------:"+key); // 建立连接梳理 加 1 ONLINE_COUNT.incrementAndGet(); // 将当前创建的session 存储起来 CLIENTS.put(session.getId(), session); logger.info("有新窗口打开连接加入:{},当前正在查询总数为:{}", 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(); } }python); tailer.start(); } catch (Exception e) { e.printStackTrace(); } } /** * WebSocket请求关闭 */ @OnClose public void onClose(Session session) { //python 建立连接梳理 减 1 ONLINE_COUNT.decrementAndGet(); // 删除缓存起来的session CLIENTS.remove(session.getId()); logger.info("有一窗口连接关闭:{},当前正在开发总数为:{}", 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("发生错误,删除当前session:{},剩余正在总数为:{}", session.getId(), ONLINE_COUNT.get()); } }
3、日志监听获取
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; /** * 监视开关,true = 打开监视 */ private boolean tailing = true; /** * * @param file 要监视的文本文件 */ public LogFileTailer(String file) { logfile = new File(file); } /** *Tailing 开关 * @param tailing */ public void Tailing(boolean tailing) { this.tailing = tailing; } /** * 设置回调事件 * * @param callback 回调事件 */ 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、问题补充
SpringBoot中需要注入bean,通过注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint。
要注意:如果使用独立的servlet容器,而不是直接使用springboot的内置容器,
就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理,所以在注入
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 { /** * 要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器, * 就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理 * 所以增加以下配置: * @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)); } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论