由于本demo是在原有服务进行拓展, 为减少耦合故新建模块
依赖及配置文件
pom文件
<artifactId>publish-plat-log</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- springboot websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- thymeleaf模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
注意:
由于此模块没有启动类, 故本依赖及父依赖不能不能有打包插件
spring-boot-maven-plugin
application文件
此处取名为application-wslog.yml
spring:
thymeleaf:
prefix: classpath:/static/view/
check-template: true
config:
activate:
on-profile: wslog
主模块添加配置引用改模块
spring:
profiles:
active: dev
include: wslog
资源包
resources
下新建 static
包
jquery js工具包
放在static.js
, 用于页面引用
HTML页面
static.view
下新建 logging.html
<!DOCTYPE>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>实时日志</title>
<!-- jquery -->
<script th:src="@{/js/jquery-1.9.1.min.js}"></script>
</head>
<body>
<!-- 标题 -->
<h1 style="text-align: center;">实时日志</h1>
<!-- 显示区 -->
<div id="loggingText" contenteditable="true"
style="width:100%;height: 600px;background-color: ghostwhite; overflow: auto;"></div>
<!-- 操作栏 -->
<div style="text-align: center;">
<button onclick="$('#loggingText').text('')" style="color: green; height: 35px;">清屏</button>
<button onclick="$('#loggingText').animate({scrollTop:$('#loggingText')[0].scrollHeight});"
style="color: green; height: 35px;">滚动至底部
</button>
<button onclick="if(window.loggingAutoBottom){$(this).text('开启自动滚动');}else{$(this).text('关闭自动滚动');};window.loggingAutoBottom = !window.loggingAutoBottom"
style="color: green; height: 35px; ">开启自动滚动
</button>
</div>
</body>
<script th:inline="javascript">
//websocket对象
let websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:28822/websocket/logging");
} else {
console.error("不支持WebSocket");
}
//连接发生错误的回调方法
websocket.onerror = function (e) {
console.error("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
console.log("WebSocket连接成功")
};
//接收到消息的回调方法
websocket.onmessage = function (event) {
//追加
if (event.data) {
//日志内容
let $loggingText = $("#loggingText");
$loggingText.append(event.data + '<br/>');
// //是否开启自动底部
if (window.loggingAutoBottom) {
//滚动条自动到最底部
$loggingText.scrollTop($loggingText[0].scrollHeight);
}
}
}
//连接关闭的回调方法
websocket.onclose = function () {
console.log("WebSocket连接关闭")
};
</script>
</html>
实现代码
Endpoint
WebSocket获取实时日志并输出到Web页面
import ch.qos.logback.classic.LoggerContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Random;
/**
* WebSocket获取实时日志并输出到Web页面
*/
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/logging")
public class LoggingWSServer {
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
private Integer sessionId;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
this.sessionId = (new Random()).nextInt(100000);
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
// 第二步:获取日志对象 (日志是有继承关系的,关闭上层,下层如果没有特殊说明也会关闭)
ch.qos.logback.classic.Logger rootLogger = lc.getLogger("root");
MyAppender myAppender = new MyAppender(this);
myAppender.setContext(lc);
// 自定义Appender设置name
myAppender.setName("myAppender" + sessionId);
myAppender.start();
rootLogger.addAppender(myAppender);
System.out.println("====注入成功====");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger rootLogger = lc.getLogger("root");
// 通过name移除Appender
rootLogger.detachAppender("myAppender" + sessionId);
System.out.println("====移除成功====");
}
/**
* 服务器主动发送消息
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
}
WebSocketConfig
此处为了偷懒将测试controller也放在此处
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* WebSocket配置
*/
@Slf4j
@RestController
@Configuration
public class WebSocketConfig {
/**
* 用途:扫描并注册所有携带@ServerEndpoint注解的实例。 @ServerEndpoint("/websocket")
* PS:如果使用外部容器 则无需提供ServerEndpointExporter。
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* 端口
*/
@Value("${server.port}")
private String port;
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
try {
InetAddress ia = InetAddress.getLocalHost();
//获取本机内网IP
log.info("启动成功:" + "http://" + ia.getHostAddress() + ":" + port + "/");
} catch (UnknownHostException ex) {
ex.printStackTrace();
}
};
}
/**
* 跳转实时日志
*/
@GetMapping("/logging")
public ModelAndView logging() {
return new ModelAndView("logging.html");
}
/**
* 测试日志输出
*/
@GetMapping("/testLog")
public String testLog() throws Exception {
log.info("测试日志输出");
if (true) {
throw new Exception("异常测试");
}
return "testLog";
}
}
日志格式拓展
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyAppender extends AppenderBase<ILoggingEvent> {
private final LoggingWSServer webSocketServer;
public MyAppender(LoggingWSServer webSocketServer) {
this.webSocketServer = webSocketServer;
}
/**
* 添加日志
* @param iLoggingEvent
*/
@Override
protected void append(ILoggingEvent iLoggingEvent) {
try {
webSocketServer.sendMessage(doLayout(iLoggingEvent));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 格式化日志
* @param event
*/
public String doLayout(ILoggingEvent event) {
StringBuilder sbuf = new StringBuilder();
if (null != event && null != event.getMDCPropertyMap()) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
sbuf.append(simpleDateFormat.format(new Date(event.getTimeStamp())));
sbuf.append("\t");
sbuf.append(event.getLevel());
sbuf.append("\t");
sbuf.append(event.getThreadName());
sbuf.append("\t");
sbuf.append(event.getLoggerName());
sbuf.append("\t");
sbuf.append(event.getFormattedMessage().replace("\"", "\\\""));
sbuf.append("\t");
}
return sbuf.toString();
}
}