您现在的位置是:网站首页>博客详情博客详情

springboot集成ws实时输出控制台日志到web页面

凡繁烦2020-04-24 15:23JAVA2852人已围观

简介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.createTime}} {{item.commentArea}} |({{item.commentIp}})
{{it.createTime}}{{it.commentArea}} | ({{it.commentIp}})
上一页 1 ... {{num}} ... 下一页 {{totalPage}} 跳转到: GO

站点信息

  • 建站时间:2020-03-28
  • 开发语言:JAVA
  • 文章统计:13篇
  • 文章评论:4条
  • 统计数据百度统计
  • 微博:扫描二维码,关注

打赏本站

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