前言
最近看了一个项目,有一个实时打印服务器日志的功能,但是他这个实现方法是不断的轮训服务器,请求日志然后显示到页面上。我觉得这种方法不是那么好,觉得还是用WebSocket
推送的方式比较不错,所以实现一个服务器端推送日志的功能。
准备
因为后端使用Logback
打印日志,所以看了一下Logback
的文档,他有一些自己实现的推送日志的功能,但是都是通过Socket
实时推送到一个专门的日志服务器上,和我的想法有点区别。如果有需要可以看看文档中的Receivers
章节或者Appenders
章节。
实现
1、 配置WebSocket(自行配置)
2、 实现自己的Appender
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.Layout; import ch.qos.logback.core.encoder.Encoder; import ch.qos.logback.core.encoder.LayoutWrappingEncoder; import org.springframework.context.ApplicationContext; import org.springframework.messaging.simp.SimpMessagingTemplate;
import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset;
public class WebSocketAppender extends AppenderBase<ILoggingEvent> {
public final static String SEND_LOG_FLAG = "SEND_TO"; public final static String ON = "ON"; public final static String OFF = "OFF";
private ApplicationContext applicationContext = null; protected Encoder<ILoggingEvent> encoder;
@Override protected void append(ILoggingEvent eventObject) { getContext().getProperty(SEND_LOG_FLAG); if (ON.equals(getContext().getProperty(SEND_LOG_FLAG)) && applicationContext == null) { this.applicationContext = (ApplicationContext) getContext().getObject("SpringApplicationContext"); } Level level = eventObject.getLevel(); byte[] bytes = this.encoder.encode(eventObject); String message = new String(bytes); LocalDateTime localDateTime = Instant.ofEpochMilli(eventObject.getTimeStamp()).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
SystemLogVo systemLogVo = new SystemLogVo(localDateTime, level.toInt(), level.levelStr, message);
SimpMessagingTemplate simpMessagingTemplate = applicationContext.getBean(SimpMessagingTemplate.class); simpMessagingTemplate.convertAndSend("/topic/log", systemLogVo); }
public void setLayout(Layout<ILoggingEvent> layout) { LayoutWrappingEncoder<ILoggingEvent> lwe = new LayoutWrappingEncoder<>(); lwe.setLayout(layout); lwe.setContext(context); this.encoder = lwe; } }
|
这里有个需要注意的地方,因为日志启动是在Spring相关配置之前,所以在服务器启动之前是获取不到Spring管理的对象的。所以需要加一个配置,标识服务器是否已经启动完成,SEND_LOG_FLAG
如果为ON
则说明服务器已经启动,并且因为Appender
对象并不是Spring
管理的对象并不能直接获取Spring
管理的对象,需要通过ApplicationContext
来间接获取。所以这个时候需要使用ApplicationListener
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import ch.qos.logback.classic.LoggerContext; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component;
@Component public class LogListener implements ApplicationListener<ContextRefreshedEvent> {
@Override public void onApplicationEvent(ContextRefreshedEvent event) { LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); lc.putProperty(WebSocketAppender.SEND_LOG_FLAG, WebSocketAppender.ON); lc.putObject("SpringApplicationContext", event.getApplicationContext()); } }
|
3、前端页面接收日志。