您现在的位置是:网站首页>博客详情博客详情
原springboot集成ws实时输出控制台日志到web页面
凡繁烦2020-04-24 15:23【JAVA】4243人已围观
简介spring boot项目中集成websocket,通过ws将控制台的日志输出到web前端页面
首先新建springboot项目,这里就不一一赘述了;
1.maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!--监控sql日志-->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>${log4jdbc.version}</version>
</dependency>
2.启动类添加ws注解
@SpringBootApplication
@MapperScan(basePackages = "com.lyf.blogback.mapper")
@EnableTransactionManagement
@EnableSwaggerBootstrapUI
@EnableWebSocketMessageBroker
public class BlogBackApplication {
public static void main(String[] args) {
SpringApplication.run(BlogBackApplication.class, args);
}
}
3.创建消息实体(未使用lombok)
public class LogMessage {
private String body;
private String timestamp;
private String threadName;
private String className;
private String level;
public LogMessage() {
}
public LogMessage(String body, String timestamp, String threadName, String className, String level) {
this.body = body;
this.timestamp = timestamp;
this.threadName = threadName;
this.className = className;
this.level = level;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
}
4.创建日志过滤器
public class LogFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
LogMessage loggerMessage = new LogMessage(
event.getFormattedMessage(),
DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
event.getThreadName(),
event.getLoggerName(),
event.getLevel().levelStr
);
LoggerQueue.getInstance().push(loggerMessage);
return FilterReply.ACCEPT;
}
}
并在logback.xml里面加上自定义的过滤器配置
<filter class="com.lyf.blogback.config.monitor.LogFilter"></filter>
5.创建一个阻塞队列,作为日志系统输出的日志的一个临时载体
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(LogMessage log) {
return this.blockingQueue.add(log);
}
/**
* 消息出队
*
* @return
*/
public LogMessage poll() {
LogMessage result = null;
try {
result = (LogMessage) this.blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
6.创建ws配置类
@Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@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 {
LogMessage log = LoggerQueue.getInstance().poll();
if(log!=null){
// 格式化异常堆栈信息
if("ERROR".equals(log.getLevel())){
log.setBody("<pre>"+log.getBody()+"</pre>");
}
if(log.getClassName().equals("jdbc.resultsettable")){
log.setBody("<br><pre>"+log.getBody()+"</pre>");
}
if(messagingTemplate!=null){
messagingTemplate.convertAndSend("/topic/logMsg",log);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
executorService.submit(runnable);
}
}
7.前端页面(使用的layui)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>web控制台</title>
<link rel="stylesheet" href="../../../assets/libs/layui/css/layui.css"/>
<link rel="stylesheet" href="../../../assets/module/admin.css?v=311"/>
<link type="text/style" rel="stylesheet" href="../../../assets/css/common.css?v=411"/>
<style>
.container {
width: 100%;
margin: 5px
}
.container .console {
font-family: "Interstate", "Hind", -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
overflow-y: scroll;
background: #494949;
color: #f7f7f7;
padding: 10px;
font-size: 14px;
border-radius: 3px 1px 3px 3px;
}
</style>
</head>
<body>
<!-- 加载动画,移除位置在common.js中 -->
<div class="page-loading">
<div class="ball-loader">
<span></span><span></span><span></span><span></span>
</div>
</div>
<!-- 正文开始 -->
<div class="container">
<div id="console" class="console">
</div>
<div class="layui-btn-group fr" style="margin-top:10px;">
<button class="layui-btn layui-btn-danger" data-level="error">ERROR</button>
<button class="layui-btn layui-btn-warm" data-level="warn">WARN</button>
<button class="layui-btn" data-level="info">INfO</button>
<button class="layui-btn layui-btn-normal" data-level="debug">DEBUG</button>
<button class="layui-btn refresh">刷新</button>
<button class="layui-btn layui-btn-primary screen_clear">清屏</button>
</div>
</div>
<script type="text/html" id="logItem">
<div>
{{# var color={INFO: '#0000ff', WARN: '#FFFF00', ERROR: '#FF0000', DEBUG: '#DEA000'} }}
<span>{{ d.name }}</span>
<span style="color:#CD0066 ">{{ d.timestamp }}</span>
<span style="color: #00CD00">{{ d.threadName+' ' }}</span>
<span style="color: {{ color[d.level] }}">
{{ d.level+' ' }}
</span>
<span style="color: #DE00CC">{{ d.className+' ' }}</span>
<span>{{d.body}}</span>
<div>
</script>
<!-- js部分 -->
<script type="text/javascript" src="../../../assets/libs/layui/layui.js"></script>
<script type="text/javascript" src="../../../assets/js/common.js?v=311"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/sockjs-client/1.3.0/sockjs.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.js"></script>
<script type="text/javascript">
layui.use(['jquery','laytpl'],function(){
var $ = layui.jquery;
var laytpl = layui.laytpl;
// 日志实时推送业务处理
var stompClient = null;
function openSocket() {
if (stompClient == null) {
if($("#log-container").find("span").length==0){
$("#log-container div").after("<span>通道连接成功,静默等待.....</span><img src='../../assets/module/img/ic_loading.gif'>");
}
var socket = new SockJS(`${BASE_URL}/websocket?token=kl`);
stompClient = Stomp.over(socket);
stompClient.connect({token: "kl"}, function (frame) {
stompClient.subscribe('/topic/logMsg', function (message) {
var content = JSON.parse(message.body);
content.name = 'blog-back ';
content.timestamp = parseTime(content.timestamp);
laytpl(logItem.innerHTML).render(content,function(html){
$('#console').append(html);
})
$("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
}, {
token: "kltoen"
});
});
}
}
function closeSocket() {
if (stompClient != null) {
stompClient.disconnect();
stompClient = null;
}
}
var parseTime = function(time) {
if (time) {
var date = new Date(time)
var year = date.getFullYear()
/* 在日期格式中,月份是从0开始的,因此要加0
* 使用三元表达式在小于10的前面加0,以达到格式统一 如 09:11:05
* */
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
// 拼接
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
} else {
return ''
}
}
var widthPx = document.documentElement.clientWidth - 185 + 'px';
var heightPx = document.documentElement.clientHeight - 94.5 + 'px';
$('.container').css('min-width',widthPx);
$('.container #console').css('height',heightPx);
$('#console').html('');
var item = {name: 'blog-back-', timestamp: parseTime(new Date()), threadName: 'system-prompt-message', level: 'INFO', className: 'com.lyf.blog.BlogBackApplication' + ' :', body: 'Welcome, no log output' }
laytpl(logItem.innerHTML).render(item,function(html){
$('#console').append(html);
})
openSocket();
$('.screen_clear').click(function(){
$('#console').html('');
})
$('.layui-btn-group .layui-btn').on('click',function(){
if($(this).data('level')){
$.get(`${BASE_URL}/logback/setLevel?level=${$(this).data('level')}`,function(res){
if(res.code === 0){
layer.msg('日志级别修改成功!,当前级别为:'+res.data.levelStr,{icon:1});
}else{
layer.msg(res.msg,{icon:2});
}
},'json')
}
})
$('.layui-btn-group .refresh').on('click',function(){
closeSocket();
openSocket();
})
})
</script>
</body>
</html>
很赞哦! (0)
文章评论(共0条)
{{item.fromUserName}} :
{{item.createTime}}
{{item.commentArea}} |({{item.commentIp}})
删除
回复
|
赞 ({{item.thumbsCount||0}})
{{it.fromUserName}} : 回复@{{it.toUserName}} : {{it.content}}
{{it.createTime}}{{it.commentArea}} | ({{it.commentIp}})
删除
回复
|
赞 ({{it.thumbsCount||0}})
上一页
1
...
{{num}}
...
下一页
{{totalPage}}
跳转到:
GO
点击排行
标签云
站点信息
- 建站时间:2020-03-28
- 开发语言:JAVA
- 文章统计:13篇
- 文章评论:6条
- 统计数据:百度统计
- 微博:扫描二维码,关注

打赏本站
- 如果你觉得本站很棒,可以通过扫码支付打赏哦!
- 微信扫码:你说多少就多少~
- 支付宝:非常感谢您的慷慨支持~