代码提交
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -29,11 +29,17 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-json</artifactId>
|
<artifactId>hutool-json</artifactId>
|
||||||
|
|||||||
@@ -2,9 +2,48 @@ package cn.somkit.fmt.entity;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
public record LoggerMessage(
|
public class LoggerMessage {
|
||||||
String level,
|
String level;
|
||||||
String loggerName,
|
String loggerName;
|
||||||
String message,
|
String message;
|
||||||
LocalDateTime timestamp
|
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.classic.spi.ILoggingEvent;
|
||||||
import ch.qos.logback.core.filter.Filter;
|
import ch.qos.logback.core.filter.Filter;
|
||||||
import ch.qos.logback.core.spi.FilterReply;
|
import ch.qos.logback.core.spi.FilterReply;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.somkit.fmt.entity.LoggerMessage;
|
import cn.somkit.fmt.entity.LoggerMessage;
|
||||||
import cn.somkit.fmt.utils.LoggerQueue;
|
import cn.somkit.fmt.utils.LoggerQueue;
|
||||||
|
|
||||||
@@ -19,18 +20,15 @@ public class LogStashFilter extends Filter<ILoggingEvent> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FilterReply decide(ILoggingEvent e) {
|
public FilterReply decide(ILoggingEvent e) {
|
||||||
if (!this.isStarted()) {
|
LoggerMessage msg = new LoggerMessage(
|
||||||
return FilterReply.NEUTRAL;
|
e.getLevel().toString(),
|
||||||
} else {
|
e.getLoggerName(),
|
||||||
LoggerMessage msg = new LoggerMessage(
|
e.getFormattedMessage(),
|
||||||
e.getLevel().toString(),
|
DateUtil.format(Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime(),
|
||||||
e.getLoggerName(),
|
"yyyy-MM-dd HH:mm:ss.SSS")
|
||||||
e.getFormattedMessage(),
|
);
|
||||||
Instant.ofEpochMilli(e.getTimeStamp()).atZone(ZoneId.systemDefault()).toLocalDateTime()
|
LoggerQueue.getInstance().push(msg); // 单例阻塞队列
|
||||||
);
|
return FilterReply.NEUTRAL;
|
||||||
LoggerQueue.getInstance().push(msg); // 单例阻塞队列
|
|
||||||
return e.getLevel().isGreaterOrEqual(this.level) ? FilterReply.NEUTRAL : FilterReply.DENY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLevel(String level) {
|
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;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class ApplicationContextProvider implements ApplicationContextAware {
|
public class AppContext implements ApplicationContextAware {
|
||||||
|
|
||||||
private static ApplicationContext context;
|
private static ApplicationContext context;
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||||||
|
|
||||||
public final class LoggerQueue {
|
public final class LoggerQueue {
|
||||||
private static final LoggerQueue INSTANCE = new 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() {
|
public static LoggerQueue getInstance() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<!-- 日志输出到控制台 -->
|
<!-- 日志输出到控制台 -->
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<filter class="cn.somkit.fmt.filter.LogStashFilter">
|
<filter class="cn.somkit.fmt.filter.LogStashFilter">
|
||||||
<level>INFO</level>
|
<level>DEBUG</level>
|
||||||
</filter>
|
</filter>
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<!-- 指定日志输出级别,以及启动的Appender -->
|
<!-- 指定日志输出级别,以及启动的Appender -->
|
||||||
<root level="INFO">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -19,9 +19,12 @@
|
|||||||
<script th:src="@{/common/js/LogMonitorAdaptive.js}" type="text/javascript" charset="utf-8"></script>
|
<script th:src="@{/common/js/LogMonitorAdaptive.js}" type="text/javascript" charset="utf-8"></script>
|
||||||
<script type="text/javascript" th:inline="javascript" charset="utf-8">
|
<script type="text/javascript" th:inline="javascript" charset="utf-8">
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
|
|
||||||
|
let ws = null;
|
||||||
|
|
||||||
const logger = new LogMonitorAdaptive('#logContainer', {
|
const logger = new LogMonitorAdaptive('#logContainer', {
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
maxLines: 5000,
|
maxLines: 10000,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
enableFilter: true,
|
enableFilter: true,
|
||||||
enableSearch: true,
|
enableSearch: true,
|
||||||
@@ -34,19 +37,44 @@
|
|||||||
enableWordWrap: true,
|
enableWordWrap: true,
|
||||||
showTimestamp: true, // 是否显示时间戳
|
showTimestamp: true, // 是否显示时间戳
|
||||||
showLevel: true, // 是否显示日志级别标签
|
showLevel: true, // 是否显示日志级别标签
|
||||||
|
wordWrap: true, // 日志内容是否自动换行(true=换行,false=横向滚动)
|
||||||
//暂停/继续 回调函数
|
//暂停/继续 回调函数
|
||||||
onTogglePause: (isPaused) => {
|
onTogglePause: async (isPaused) => {
|
||||||
const options = {
|
const options = {
|
||||||
url: Fmt.ctx() + '/logging/close',
|
url: Fmt.ctx() + '/logging/close',
|
||||||
data: {closed: isPaused},
|
data: {closed: isPaused},
|
||||||
method: 'post'
|
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: () => {
|
onCreated: () => {
|
||||||
console.log('日志容器已创建');
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user