代码提交
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -29,11 +29,17 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
||||
@@ -2,9 +2,48 @@ package cn.somkit.fmt.entity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record LoggerMessage(
|
||||
String level,
|
||||
String loggerName,
|
||||
String message,
|
||||
LocalDateTime timestamp
|
||||
) {}
|
||||
public class LoggerMessage {
|
||||
String level;
|
||||
String loggerName;
|
||||
String message;
|
||||
String timestamp;
|
||||
|
||||
public LoggerMessage(String level, String loggerName, String message, String timestamp) {
|
||||
this.level = level;
|
||||
this.loggerName = loggerName;
|
||||
this.message = message;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(String level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
public void setLoggerName(String loggerName) {
|
||||
this.loggerName = loggerName;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.filter.Filter;
|
||||
import ch.qos.logback.core.spi.FilterReply;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.somkit.fmt.entity.LoggerMessage;
|
||||
import cn.somkit.fmt.utils.LoggerQueue;
|
||||
|
||||
@@ -19,18 +20,15 @@ public class LogStashFilter extends Filter<ILoggingEvent> {
|
||||
|
||||
@Override
|
||||
public FilterReply decide(ILoggingEvent e) {
|
||||
if (!this.isStarted()) {
|
||||
return FilterReply.NEUTRAL;
|
||||
} else {
|
||||
LoggerMessage msg = new LoggerMessage(
|
||||
e.getLevel().toString(),
|
||||
e.getLoggerName(),
|
||||
e.getFormattedMessage(),
|
||||
Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime()
|
||||
DateUtil.format(Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime(),
|
||||
"yyyy-MM-dd HH:mm:ss.SSS")
|
||||
);
|
||||
LoggerQueue.getInstance().push(msg); // 单例阻塞队列
|
||||
return e.getLevel().isGreaterOrEqual(this.level) ? FilterReply.NEUTRAL : FilterReply.DENY;
|
||||
}
|
||||
return FilterReply.NEUTRAL;
|
||||
}
|
||||
|
||||
public void setLevel(String level) {
|
||||
|
||||
23
src/main/java/cn/somkit/fmt/socket/WebSocketAutoConfig.java
Normal file
23
src/main/java/cn/somkit/fmt/socket/WebSocketAutoConfig.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package cn.somkit.fmt.socket;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketAutoConfig implements WebSocketConfigurer {
|
||||
|
||||
@Bean
|
||||
public WebSocketServerHandler webSocketServerHandler() {
|
||||
return new WebSocketServerHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(webSocketServerHandler(), "/socket/ws")//设置连接路径和处理
|
||||
.setAllowedOrigins("*");//允许跨域访问
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.somkit.fmt.socket;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.metona.cache.Cache;
|
||||
import cn.somkit.fmt.entity.LoggerMessage;
|
||||
import cn.somkit.fmt.utils.LoggerQueue;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class WebSocketServerHandler implements WebSocketHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private Cache<String, Object> cache;
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
push(session);
|
||||
}
|
||||
|
||||
private void push(WebSocketSession session) throws IOException {
|
||||
while (StrUtil.isBlankIfStr(cache.get("closed")) || !Boolean.parseBoolean(String.valueOf(cache.get("closed")))) {
|
||||
LoggerMessage log = LoggerQueue.getInstance().poll();
|
||||
if(log != null){
|
||||
session.sendMessage(new TextMessage(JSONUtil.toJsonStr(log)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
|
||||
push(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPartialMessages() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ApplicationContextProvider implements ApplicationContextAware {
|
||||
public class AppContext implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext context;
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public final class LoggerQueue {
|
||||
private static final LoggerQueue INSTANCE = new LoggerQueue();
|
||||
private final BlockingQueue<LoggerMessage> queue = new LinkedBlockingQueue<>();
|
||||
private final BlockingQueue<LoggerMessage> queue = new LinkedBlockingQueue<>(100000);
|
||||
|
||||
public static LoggerQueue getInstance() {
|
||||
return INSTANCE;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<!-- 日志输出到控制台 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="cn.somkit.fmt.filter.LogStashFilter">
|
||||
<level>INFO</level>
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
@@ -15,7 +15,7 @@
|
||||
</appender>
|
||||
|
||||
<!-- 指定日志输出级别,以及启动的Appender -->
|
||||
<root level="INFO">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
<script th:src="@{/common/js/LogMonitorAdaptive.js}" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" th:inline="javascript" charset="utf-8">
|
||||
window.onload = () => {
|
||||
|
||||
let ws = null;
|
||||
|
||||
const logger = new LogMonitorAdaptive('#logContainer', {
|
||||
theme: 'dark',
|
||||
maxLines: 5000,
|
||||
maxLines: 10000,
|
||||
fontSize: 14,
|
||||
enableFilter: true,
|
||||
enableSearch: true,
|
||||
@@ -34,19 +37,44 @@
|
||||
enableWordWrap: true,
|
||||
showTimestamp: true, // 是否显示时间戳
|
||||
showLevel: true, // 是否显示日志级别标签
|
||||
wordWrap: true, // 日志内容是否自动换行(true=换行,false=横向滚动)
|
||||
//暂停/继续 回调函数
|
||||
onTogglePause: (isPaused) => {
|
||||
onTogglePause: async (isPaused) => {
|
||||
const options = {
|
||||
url: Fmt.ctx() + '/logging/close',
|
||||
data: {closed: isPaused},
|
||||
method: 'post'
|
||||
};
|
||||
Fmt.axios(options).then((result) => console.log(result)).catch((err) => console.error(err));
|
||||
await Fmt.axios(options).then((result) => console.log(result)).catch((err) => console.error(err));
|
||||
ws.send('发送日志');
|
||||
},
|
||||
onCreated: () => {
|
||||
console.log('日志容器已创建');
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
if ('WebSocket' in window) {
|
||||
ws = new WebSocket((Fmt.ctx() + '/socket/ws').replace("http", "ws").replace("https", "wss"));
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket通讯');
|
||||
return false;
|
||||
}
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
if(event.data){
|
||||
let data = JSON.parse(event.data);
|
||||
logger.log(data.loggerName + ' : ' + data.message, data.level, data.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
|
||||
window.onbeforeunload = ()=> {
|
||||
ws.close();
|
||||
};
|
||||
} catch (e) {
|
||||
alert('WebSocket:链接失败');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user