如何采集springboot日志至web页面查看
实现方案
基于Filter方式,在日志输出至控制台前,LoggerFitler 拦截日志通过websocket推送至前台页面
实现逻辑:
LoggerFilter采集日志添加至LoggerQueue队列,
LoggerConsumer 从LoggerQueue中采集推送至前台页面
1. 配置拦截器
- logback 在appender中指定filter 拦截
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%magenta(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %green([%thread]) %logger{40}: %m %n
</pattern>
<charset>utf8</charset>
</encoder>
<filter class="com.weblog.logger.LoggerFilter"></filter>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
2. 代码逻辑
2.1 LoggerFilter 拦截日志来源
从ILoggingEvent 获取日志信息,封装为LoggerMessage ,添加至日志队列
public class LoggerFilter extends Filter<ILoggingEvent> {
private String formatTime(long timestamp) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
return simpleDateFormat.format(new Date(timestamp));
}
/**
* 解析日志事件信息内容 加入队列
*
* @param event 日志事件
*/
@Override
public FilterReply decide(ILoggingEvent event) {
LoggerMessage log = new LoggerMessage();
log.setClassInfo(event.getLoggerName());
log.setLogTime(formatTime(event.getTimeStamp()));
log.setLogInfo(event.getFormattedMessage());
log.setLogLevel(event.getLevel().levelStr);
String exception = "";
IThrowableProxy throwableProxy = event.getThrowableProxy();
if (throwableProxy != null) {
exception = ThrowableProxyUtil.asString(throwableProxy);
}
log.setErrorInfo(exception);
LoggerQueue.getInstance().push(log);
return FilterReply.ACCEPT;
}
}
2.2 LoggerQueue 提供日志的存放与获取
内置队列管理器, 提供pull 和push方法
代码未优化,队列应该选择先进先出
public class LoggerQueue {
/**
* 最大队列大小
*/
public static final int QUEUE_MAX_SIZE = 10000;
/**
* 日志队列
*/
private static LoggerQueue loggerQueue = new LoggerQueue();
/**
* 阻塞队列
*/
private BlockingQueue<LoggerMessage> blockingQueue = new LinkedBlockingQueue<LoggerMessage>(QUEUE_MAX_SIZE);
private LoggerQueue() {
}
public static LoggerQueue getInstance() {
return loggerQueue;
}
/**
* 推送最新日志消息
*
* @param loggerMessage 日志消息
* @return 是否成功
*/
public boolean push(LoggerMessage loggerMessage) {
return this.blockingQueue.add(loggerMessage);//队列满了就抛出异常,不阻塞
}
/**
* 拉取最新日志消息
*
* @return 日志消息
*/
public LoggerMessage pull() {
LoggerMessage loggerMessage = null;
try {
loggerMessage = this.blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return loggerMessage;
}
}
2.4 WebSocketLoggerConsumer 实现消息推送至websocket
开启固定线程,获取日志队列信息,推送至websocket
@Component
public class WebSocketLoggerConsumer implements LoggerConsumer {
@Resource
private SimpMessagingTemplate messagingTemplate;
/**
* 创建日志消费线程池
*/
@PostConstruct
public void createLogExecutor() {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
LoggerQueue loggerQueue = LoggerQueue.getInstance();
if (loggerQueue != null) {
consumer(loggerQueue.pull());
}
}
}
};
executorService.submit(runnable);
}
@Override
public void consumer(LoggerMessage loggerMessage) {
messagingTemplate.convertAndSend("/topic/log", loggerMessage.toString());
}
}
3. 测试
3.1 测试示例
1s 写入1条info日志,2s 写入1条warn 日志
@Scheduled(fixedDelay = 1000)
private void mockLogInfoData() {
logger.info("logger info data!");
}
@Scheduled(fixedDelay = 2000)
private void mockLogWarnData() {
logger.warn("logger warn data!");
}