簡單的spring-boot工程這裏就不做過多的講解了,只敘述核心部分
首先導入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
首先在項目的日誌配置文件中新增/修改此節點配置
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--encoder 默認配置爲PatternLayoutEncoder-->
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%thread] %logger Line:%-3L - %msg%n</pattern>-->
<charset>UTF-8</charset>
</encoder>
<!--此日誌appender是爲開發使用,只配置最底級別,控制檯輸出的日誌級別是大於或等於此級別的日誌信息-->
<filter class="com.xcloud.api.system.core.LogFilter"></filter>
</appender>
此節點配置主要是爲了把控制檯輸出的日誌交給我們自己的日誌過濾器進行處理(此節點不要指定日誌等級,記得<appender-ref ref="STDOUT" />)
接下來實現自己的日誌過濾器
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
import com.xcloud.api.system.entity.LoggerMessage;
import org.springframework.stereotype.Service;
import java.text.DateFormat;
import java.util.Date;
/**
* Xcloud-Api By IDEA
* Created by LaoWang on 2018/8/25.
*/
@Service
public class LogFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
String exception = "";
IThrowableProxy iThrowableProxy1 = event.getThrowableProxy();
if(iThrowableProxy1!=null){
exception = "<span class='excehtext'>"+iThrowableProxy1.getClassName()+" "+iThrowableProxy1.getMessage()+"</span></br>";
for(int i=0; i<iThrowableProxy1.getStackTraceElementProxyArray().length;i++){
exception += "<span class='excetext'>"+iThrowableProxy1.getStackTraceElementProxyArray()[i].toString()+"</span></br>";
}
}
LoggerMessage loggerMessage = new LoggerMessage(
event.getMessage()
, DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
event.getThreadName(),
event.getLoggerName(),
event.getLevel().levelStr,
exception,
""
);
LoggerQueue.getInstance().push(loggerMessage);
return FilterReply.ACCEPT;
}
}
這部分代碼網上大部分都是一樣的,但是並沒有對異常具體信息進行處理,所以這裏我把具體異常信息日誌也添加進去了
創建一個阻塞隊列,作爲日誌系統輸出的日誌的一個臨時載體
import com.xcloud.api.system.entity.LoggerMessage;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class LoggerQueue {
//隊列大小
public static final int QUEUE_MAX_SIZE = 10000;
private static LoggerQueue alarmMessageQueue = new LoggerQueue();
//阻塞隊列
private BlockingQueue blockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
private LoggerQueue() {
}
public static LoggerQueue getInstance() {
return alarmMessageQueue;
}
/**
* 消息入隊
*
* @param log
* @return
*/
public boolean push(LoggerMessage log) {
return this.blockingQueue.add(log);//隊列滿了就拋出異常,不阻塞
}
/**
* 消息出隊
*
* @return
*/
public LoggerMessage poll() {
LoggerMessage result = null;
try {
result = (LoggerMessage) this.blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
創建一個日誌實體(這裏我使用了lombok,如果沒用的話,請自行生成get,set和全參數構造方法)
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* 日誌消息實體
*/
@Getter
@Setter
@ToString
@AllArgsConstructor
public class LoggerMessage {
private String body;
private String timestamp;
private String threadName;
private String className;
private String level;
private String exception;
private String cause;
}
接下來配置WebSocket
import com.xcloud.api.system.core.LoggerQueue;
import com.xcloud.api.system.entity.LoggerMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Xcloud-Api By IDEA
* 配置WebSocket消息代理端點,即stomp服務端
* 爲了連接安全,setAllowedOrigins設置的允許連接的源地址
* 如果在非這個配置的地址下發起連接會報403
* 進一步還可以使用addInterceptors設置攔截器,來做相關的鑑權操作
* Created by LaoWang on 2018/8/25.
*/
@Slf4j
@Configuration
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket")
.setAllowedOrigins("*")
.withSockJS();
}
/**
* 推送日誌到/topic/pullLogger
*/
@PostConstruct
public void pushLogger(){
ExecutorService executorService= Executors.newFixedThreadPool(2);
Runnable runnable=new Runnable() {
@Override
public void run() {
while (true) {
try {
LoggerMessage log = LoggerQueue.getInstance().poll();
if(log!=null){
if(messagingTemplate!=null)
messagingTemplate.convertAndSend("/topic/pullLogger",log);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
executorService.submit(runnable);
executorService.submit(runnable);
}
}
如果項目配置有攔截器獲取其他權限控制的,請開放/websocket
前端頁面:這裏我使用了原生swagger+layer,請自行根據自己頁面進行配置
配置一個按鈕點擊彈出layer彈窗,日誌顏色,具體的一些樣式可根據自己喜好選擇
$(".log").click(function () {
//iframe層
layer.open({
type: 1,
title: '<span class="laytit">接口實時日誌</span>',
shadeClose: false,
shade: 0.7,
maxmin: true,
area: ['80%', '70%'],
content: $("#logdiv").html(), //iframe的url
cancel: function(index){
closeSocket();
}
});
});
<!-- 日誌實時推送業務處理 -->
var stompClient = null;
function openSocket() {
if (stompClient == null) {
if($("#log-container").find("span").length==0){
$("#log-container div").after("<span>通道連接成功,靜默等待.....</span><img src='images/loading.gif'>");
}
var socket = new SockJS('websocket?token=kl');
stompClient = Stomp.over(socket);
stompClient.connect({token: "kl"}, function (frame) {
stompClient.subscribe('/topic/pullLogger', function (event) {
var content = JSON.parse(event.body);
var leverhtml = '';
var className = '<span class="classnametext">' + content.className + '</span>';
switch (content.level) {
case 'INFO':
leverhtml = '<span class="infotext">' + content.level + '</span>';
break;
case 'DEBUG':
leverhtml = '<span class="debugtext">' + content.level + '</span>';
break;
case 'WARN':
leverhtml = '<span class="warntext">' + content.level + '</span>';
break;
case 'ERROR':
leverhtml = '<span class="errortext">' + content.level + '</span>';
break;
}
$("#log-container div").append("<p class='logp'>" + content.timestamp + " " + leverhtml + " --- [" + content.threadName + "] " + className + " :" + content.body + "</p>");
if (content.exception != "") {
$("#log-container div").append("<p class='logp'>" + content.exception + "</p>");
}
if (content.cause != "") {
$("#log-container div").append("<p class='logp'>" + content.cause + "</p>");
}
$("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
}, {
token: "kltoen"
});
});
}
}
function closeSocket() {
if (stompClient != null) {
stompClient.disconnect();
stompClient = null;
}
}
最後來一個效果圖(圖中日誌等級動態配置將在下篇博客說明)有什麼不對的地方歡迎大牛們評論!!!