本次方法的核心概念是通过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;
import 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();
}
});
tailer.start();
} catch (exception e) {
e.printstacktrace();
}
}
/**
* websocket请求关闭
*/
@onclose
public void onclose(session session) {
// 建立连接梳理 减 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));
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论