SpringBoot系列——Logback日志,输出到文件以及实时输出到web页面

时间:2019-06-18
本文章向大家介绍SpringBoot系列——Logback日志,输出到文件以及实时输出到web页面,主要包括SpringBoot系列——Logback日志,输出到文件以及实时输出到web页面使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

  前言

  SpringBoot对所有内部日志使用通用日志记录,但保留底层日志实现。为Java Util Logging、Log4J2和Logback提供了默认配置。在不同的情况下,日志记录器都预先配置为使用控制台输出,同时还提供可选的文件输出。默认情况下,SpringBoot使用Logback进行日志记录。

  日志级别有(从高到低):FATAL(致命),ERROR错误),WARN警告),INFO信息),DEBUG调试),TRACE跟踪)或者 OFF(关闭),默认的日志配置在消息写入时将消息回显到控制台。默认情况下,将记录错误级别、警告级别和信息级别的消息。

  PS:Logback does not have a FATAL level. It is mapped to ERROR  Logback没有FATAL致命级别。它被映射到ERROR错误级别

  详情请戳官方文档:https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-logging

  本文主要记录Logback日志输出到文件以及实时输出到web页面

  

  输出到文件

  我们创建SpringBoot项目时,spring-boot-starter已经包含了spring-boot-starter-logging,不需要再进行引入依赖

  标准日志格式

2014-03-05 10:57:51.112  INFO 45469 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.52
2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1358 ms
2014-03-05 10:57:51.698  INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2014-03-05 10:57:51.702  INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
  • Date and Time: Millisecond precision and easily sortable. 日期和时间:毫秒精度,易于排序。
  • Log Level: ERRORWARNINFODEBUG, or TRACE日志级别:错误、警告、信息、调试或跟踪。
  • Process ID. 进程ID。
  • --- separator to distinguish the start of actual log messages. 分隔符,用于区分实际日志消息的开始。
  • Thread name: Enclosed in square brackets (may be truncated for console output). 线程名称:括在方括号中(可能会被截断以用于控制台输出)。
  • Logger name: This is usually the source class name (often abbreviated). 日志程序名称:这通常是源类名称(通常缩写)。
  • The log message. 日志消息。

  

  如何打印日志?

  方法1

    /**
     * 配置内部类
     */
    @Controller
    @Configuration
    class Config {
        /**
         * 获取日志对象,构造函数传入当前类,查找日志方便定位
         */
        private final Logger log = LoggerFactory.getLogger(this.getClass());

        @Value("${user.home}")
        private String userName;

        /**
         * 端口
         */
        @Value("${server.port}")
        private String port;
/**
         * 启动成功
         */
        @Bean
        public ApplicationRunner applicationRunner() {
            return applicationArguments -> {
                try {
                    InetAddress ia = InetAddress.getLocalHost();
                    //获取本机内网IP
                    log.info("启动成功:" + "http://" + ia.getHostAddress() + ":" + port + "/");
                    log.info("${user.home} :" + userName);
                } catch (UnknownHostException ex) {
                    ex.printStackTrace();
                }
            };
        }
    }

  方法2  使用lombok的@Slf4j,帮我们创建Logger对象,效果与方法1一样

    /**
     * 配置内部类
     */
    @Slf4j
    @Controller
    @Configuration
    class Config {

        @Value("${user.home}")
        private String userName;

        /**
         * 端口
         */
        @Value("${server.port}")
        private String port;/**
         * 启动成功
         */
        @Bean
        public ApplicationRunner applicationRunner() {
            return applicationArguments -> {
                try {
                    InetAddress ia = InetAddress.getLocalHost();
                    //获取本机内网IP
                    log.info("启动成功:" + "http://" + ia.getHostAddress() + ":" + port + "/");
                    log.info("${user.home} :" + userName);
                } catch (UnknownHostException ex) {
                    ex.printStackTrace();
                }
            };
        }
    }

  简单配置

  如果不需要进行复杂的日志配置,则在配置文件中进行简单的日志配置即可,默认情况下,SpringBoot日志只记录到控制台,不写日志文件。如果希望在控制台输出之外编写日志文件,则需要进行配置

spring:
  logging:
    path: /Users/Administrator/Desktop/杂七杂八/ims #日志文件路径
    file: ims.log #日志文件名称
    level:
      root: info #日志级别 root表示所有包,也可以单独配置具体包 fatal error warn info debug trace off

  重新启动项目

  打开ims.log

  扩展配置

   Spring Boot包含许多Logback扩展,可以帮助进行高级配置。您可以在您的logback-spring.xml配置文件中使用这些扩展。如果需要比较复杂的配置,建议使用扩展配置的方式

  PS:SpringBoot推荐我们使用带-spring后缀的 logback-spring.xml 扩展配置,因为默认的的logback.xml标准配置,Spring无法完全控制日志初始化。(spring扩展对springProfile节点的支持)

  

  以下是项目常见的完整logback-spring.xml,SpringBoot默认扫描classpath下面的logback.xml、logback-spring.xml,所以不需要再指定spring.logging.config,当然,你指定也没有问题

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--日志文件主目录:这里${user.home}为当前服务器用户主目录-->
    <property name="LOG_HOME" value="${user.home}/log"/>
    <!--日志文件名称:这里spring.application.name表示工程名称-->
    <springProperty scope="context" name="APP_NAME" source="spring.application.name"/>

    <!--默认配置-->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!--配置控制台(Console)-->
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

    <!--配置日志文件(File)-->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--设置策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件路径:这里%d{yyyyMMdd}表示按天分类日志-->
            <FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/${APP_NAME}.log</FileNamePattern>
            <!--日志保留天数-->
            <MaxHistory>15</MaxHistory>
        </rollingPolicy>
        <!--设置格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <!-- 或者使用默认配置 -->
            <!--<pattern>${FILE_LOG_PATTERN}</pattern>-->
            <charset>utf8</charset>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>100MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 多环境配置 按照active profile选择分支 -->
    <springProfile name="dev">
        <!--root节点 全局日志级别,用来指定最基础的日志输出级别-->
        <root level="INFO">
            <appender-ref ref="FILE"/>
            <appender-ref ref="CONSOLE"/>
        </root>

        <!-- 子节点向上级传递 局部日志级别-->
        <logger level="WARN" name="org.springframework"/>
        <logger level="WARN" name="com.netflix"/>
        <logger level="DEBUG" name="org.hibernate.SQL"/>
    </springProfile>
    <springProfile name="prod">

    </springProfile>
</configuration>

  启动项目,去到${user.home}当前服务器用户主目录,日志按日期进行产生,如果项目产生的日志文件比较大,还可以按照小时进行.log文件的生成  

  当然,使用简单配置照样能进行按日期分类

spring:
logging: path: ${user.home}/log/%d{yyyyMMdd} #日志文件路径 这里${user.home}为当前服务器用户主目录 file: ${spring.application.name}.log #日志文件名称 ${spring.application.name}为应用名 level: root: info #日志级别 root表示所有包,也可以单独配置具体包 fatal error warn info debug trace off

  输出到Web页面

  我们已经有日志文件.log了,为什么还要这个功能呢?(滑稽脸)为了偷懒!

  当我们把项目部署到Linux服务器,当你想看日志文件,还得打开xshell连接,定位到log文件夹,麻烦;如果我们把日志输出到Web页面,当做超级管理员或者测试账号下面的一个功能,点击就开始实时获取生成的日志并输出在Web页面,是不是爽很多呢?

  PS:这个功能可得小心使用,因为日志会暴露很多信息

  LoggingWebSocketServer

  使用WebSocket实现实时获取,建立WebSocket连接后创建一个线程任务,每秒读取一次最新的日志文件,第一次只取后面200行,后面取相比上次新增的行,为了在页面上更加方便的阅读日志,对日志级别单词进行着色(PS:如何创建springboot的websocket,请戳:SpringBoot系列——WebSocket

package cn.huanzi.ims.socket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thymeleaf.util.StringUtils;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * WebSocket获取实时日志并输出到Web页面
 */
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/logging", configurator = MyEndpointConfigure.class)
public class LoggingWebSocketServer {

    /**
     * 连接集合
     */
    private static Map<String, Session> sessionMap = new HashMap<String, Session>();
    private static Map<String, Integer> lengthMap = new HashMap<String, Integer>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        //添加到集合中
        sessionMap.put(session.getId(), session);
        lengthMap.put(session.getId(), 1);//默认从第一行开始

        //获取日志信息
        new Thread(() -> {
            log.info("LoggingWebSocketServer 任务开始");
            boolean first = true;
            while (sessionMap.get(session.getId()) != null) {
                BufferedReader reader = null;
                try {
                    //日志文件路径,获取最新的
                    String filePath = System.getProperty("user.home") + "/log/" + new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/ims.log";

                    //字符流
                    reader = new BufferedReader(new FileReader(filePath));
                    Object[] lines = reader.lines().toArray();

                    //只取从上次之后产生的日志
                    Object[] copyOfRange = Arrays.copyOfRange(lines, lengthMap.get(session.getId()), lines.length);
                    for (int i = 0; i < copyOfRange.length; i++) {
                        String line = (String) copyOfRange[i];
                        //按等级标颜色,更加美观
                        line = line.replace("DEBUG", "<span style='color: blue;'>DEBUG</span>");
                        line = line.replace("INFO", "<span style='color: green;'>INFO</span>");
                        line = line.replace("WARN", "<span style='color: orange;'>WARN</span>");
                        line = line.replace("ERROR", "<span style='color: red;'>ERROR</span>");

                        copyOfRange[i] = line;
                    }

                    //存储最新一行开始
                    lengthMap.put(session.getId(), lines.length);

                    //第一次如果太大,截取最新的200行就够了,避免传输的数据太大
                    if(first && copyOfRange.length > 200){
                        copyOfRange = Arrays.copyOfRange(copyOfRange, copyOfRange.length - 200, copyOfRange.length);
                        first = false;
                    }

                    String result = StringUtils.join(copyOfRange, "<br/>");

                    //发送
                    send(session, result);

                    //休眠一秒
                    Thread.sleep(1000);
                } catch (Exception e) {
                    //捕获但不处理
                    e.printStackTrace();
                } finally {
                    try {
                        reader.close();
                    } catch (IOException ignored) {
                    }
                }
            }
            log.info("LoggingWebSocketServer 任务结束");
        }).start();
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        //从集合中删除
        for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
            if (entry.getValue() == session) {
                sessionMap.remove(entry.getKey());
                lengthMap.remove(entry.getKey());
                break;
            }
        }
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 服务器接收到客户端消息时调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {

    }

    /**
     * 封装一个send方法,发送消息到前端
     */
    private void send(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  HTML页面

  页面收到数据就追加到div中,为了方便新增了几个功能:

  清屏,清空div内容

  滚动至底部、将div的滚动条滑到最下面

  开启/关闭自动滚动,div新增内容后自动将滚动条滑到最下面,点一下开启,再点关闭,默认关闭

  PS:引入公用部分,就是一些jquery等常用静态资源

<!DOCTYPE>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>IMS实时日志</title>

    <!-- 引入公用部分 -->
    <script th:replace="head::static"></script>

</head>
<body>
<!-- 标题 -->
<h1 style="text-align: center;">IMS实时日志</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:10086/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);

            //是否开启自动底部
            if (window.loggingAutoBottom) {
                //滚动条自动到最底部
                $loggingText.scrollTop($loggingText[0].scrollHeight);
            }
        }
    }

    //连接关闭的回调方法
    websocket.onclose = function () {
        console.log("WebSocket连接关闭")
    };
</script>
</html>

  效果展示

  后记

  有了日志记录,我们以后写代码时就要注意了,应使用下面的正确示例

//错误示例,这样写只会输出到控制台,不会输出到日志中
System.out.println("XXX");
e.printStackTrace();

//正确示例,既输出到控制台,又输出到日志
log.info("XXX");
log.error("XXX报错",e);

  SpringBoot日志暂时先记录到这里,点击官网了解更多:https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-logging

原文地址:https://www.cnblogs.com/huanzi-qch/p/11041300.html