diff --git a/pom.xml b/pom.xml index 842ec763..11956e44 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,12 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + org.springframework.boot spring-boot-configuration-processor @@ -353,6 +359,18 @@ 1.18.30 provided + + + + + + + + + io.github.sevdokimov.logviewer + log-viewer-spring-boot + 1.0.10 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java b/src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java new file mode 100644 index 00000000..6729a142 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/webLog/LogChannel.java @@ -0,0 +1,66 @@ +package com.genersoft.iot.vmp.conf.webLog; + +import lombok.extern.slf4j.Slf4j; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@ServerEndpoint(value = "/channel/log") +@Slf4j +public class LogChannel { + + public static final ConcurrentMap CHANNELS = new ConcurrentHashMap<>(); + + private Session session; + + @OnMessage(maxMessageSize = 1) // MaxMessage 1 byte + public void onMessage(String message) { + + log.debug("Recv Message: {}", message); + + try { + this.session.close(new CloseReason(CloseReason.CloseCodes.TOO_BIG, "此节点不接收任何客户端信息")); + } catch (IOException e) { + log.error("[Web-Log] 连接关闭失败: id={}, err={}", this.session.getId(), e.getMessage()); + } + } + + @OnOpen + public void onOpen(Session session, EndpointConfig endpointConfig) { + this.session = session; + this.session.setMaxIdleTimeout(0); + CHANNELS.put(this.session.getId(), this); + + log.info("[Web-Log] 连接已建立: id={}", this.session.getId()); + } + + @OnClose + public void onClose(CloseReason closeReason) { + + log.info("[Web-Log] 连接已断开: id={}, err={}", this.session.getId(), closeReason); + + CHANNELS.remove(this.session.getId()); + } + + @OnError + public void onError(Throwable throwable) throws IOException { + log.info("[Web-Log] 连接错误: id={}, err= ", this.session.getId(), throwable); + this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage())); + } + + /** + * Push messages to all clients + * + * @param message + */ + public static void push(String message) { + CHANNELS.values().stream().forEach(endpoint -> { + if (endpoint.session.isOpen()) { + endpoint.session.getAsyncRemote().sendText(message); + } + }); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/webLog/WebSocketAppender.java b/src/main/java/com/genersoft/iot/vmp/conf/webLog/WebSocketAppender.java new file mode 100644 index 00000000..78e4a66d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/webLog/WebSocketAppender.java @@ -0,0 +1,22 @@ +package com.genersoft.iot.vmp.conf.webLog; + +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import com.genersoft.iot.vmp.utils.DateUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WebSocketAppender extends AppenderBase { + + private PatternLayoutEncoder encoder; + + @Override + protected void append(ILoggingEvent loggingEvent) { + byte[] data = this.encoder.encode(loggingEvent); + // Push to client. + LogChannel.push(DateUtil.timestampMsTo_yyyy_MM_dd_HH_mm_ss(loggingEvent.getTimeStamp()) + " " + loggingEvent.getFormattedMessage()); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/websocket/WebSocketConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/websocket/WebSocketConfig.java new file mode 100644 index 00000000..0ef52675 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/websocket/WebSocketConfig.java @@ -0,0 +1,19 @@ +package com.genersoft.iot.vmp.conf.websocket; + +import com.genersoft.iot.vmp.conf.webLog.LogChannel; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter(){ + ServerEndpointExporter endpointExporter = new ServerEndpointExporter(); + + endpointExporter.setAnnotatedEndpointClasses(LogChannel.class); + + return endpointExporter; + } +} diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index e2b03ee0..6f72cb6d 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -24,6 +24,14 @@ + + + + ${FILE_LOG_PATTERN} + UTF-8 + + + @@ -88,6 +96,7 @@ + diff --git a/web_src/src/components/log.vue b/web_src/src/components/log.vue new file mode 100755 index 00000000..924b448b --- /dev/null +++ b/web_src/src/components/log.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/web_src/src/router/index.js b/web_src/src/router/index.js index ba41868e..012afba5 100755 --- a/web_src/src/router/index.js +++ b/web_src/src/router/index.js @@ -25,6 +25,7 @@ import wasmPlayer from '../components/common/jessibuca.vue' import rtcPlayer from '../components/dialog/rtcPlayer.vue' import region from '../components/region.vue' import group from '../components/group.vue' +import log from '../components/log.vue' const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { @@ -142,8 +143,11 @@ export default new VueRouter({ path: '/channel/group', name: 'group', component: group, - } - , + }, + { + path: '/log', + component: log, + }, ] }, {