springboot日志配置

205 阅读2分钟

日志框架

  • 日志框架由日志门面+日志适配器+日志实现框架组成
  • 市面上面的一些日志框架组合如下:slf4j+log4j;slf4j+logback;slf4j+log4j2
  • log4j是一款开源的日志框架
  • log4j2是log4j的升级版,参考了logback的一些优秀的设计,log4j是Apache的一款开源的日志框架,通过log4j,我们可以给日志输出到控制台,文件等地方;我们可以控制每一条日志的输出格式,通过定义日志的每一条输出级别,细致的控制日志的生成过程;log4j由三个重要的部分组成,日志记录器logger,输出端appender,日志格式化器layout三个重要部分组成
  • logger:启用或者禁用哪些日志,以什么日志级别输出
  • appender:控制日志输出到控制台还是文件里面
  • layout:控制日志以什么格式输出
  • 日志的级别:TRACE,DEBUG,INFO,WARN,ERROR,FATAL
  • 输出的规则:级别高于配置的规则,日志才会输出
  • logback由三个部分组成,logback-core,logback-classic,logback-access三个部分组成

前言

springboot内部使用Commons Logging作为内部日志的实现,但是它也支持各种日志实现,如slf4j+logback和slf4j+log4j2,默认情况下,spingboot使用slf4j+logback进行日志记录。

logback配置

详细的配置文件

  • 在yaml里面配置日志的级别,来控制日志的输出,这里是INFO,它的级别高于logback.xml里面的level级别的控制
logging:
  level:
    root: INFO
  config: classpath:config/logback.xml
  • 不同环境的配置文件可以使用spring.profiles.active=sit的形式来指定加载不同的配置文件同时在日志文件中输出日志

image.png

  • 对应logback里面的配置

image.png

  • 详细的logback配置如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!--property-->
    <!--项目名称-->
    <property name="appName" value="test"/>
    <!--日志路径-->
    <property name="rootPath" value="/logs/"/>
    <!--日志格式-->
    <property name="logPattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%p][%thread][TRACE_ID:%X{traceId}][%logger{39}#%M %L]LINECONTENT:%m%n"/>
    <!--debug和warn日志最大保存30天-->
    <property name="log.maxHistory" value="30"/>
    <!--info日志最大保留7天-->
    <property name="log.info.maxHistory" value="7"/>
    <!--存档文件的总大小10GB-->
    <property name="log.totalSizeCap" value="10GB"/>
    <!--单个文件的大小20MB-->
    <property name="log.maxFileSize" value="20MB"/>
    <!--重启清理日志文件,否-->
    <property name="log.cleanHistoryOnStart" value="false"/>

    <!--Appender-->
    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
    </appender>

    <!--输出到DEBUG文件-->
    <appender name="debug_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${rootPath}/${appName}-debug.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${rootPath}/${appName}-debug.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>${log.maxHistory}</maxHistory>
            <totalSizeCap>${log.totalSizeCap}</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${log.maxFileSize}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <cleanHistoryOnStart>${log.cleanHistoryOnStart}</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--输出到INFO文件-->
    <appender name="info_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${rootPath}/${appName}-info.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${rootPath}/${appName}-info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>${log.info.maxHistory}</maxHistory>
            <totalSizeCap>${log.totalSizeCap}</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${log.maxFileSize}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <cleanHistoryOnStart>${log.cleanHistoryOnStart}</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--输出到WARN文件-->
    <appender name="warn_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${rootPath}/${appName}-warn.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${rootPath}/${appName}-warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>${log.maxHistory}</maxHistory>
            <totalSizeCap>${log.totalSizeCap}</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${log.maxFileSize}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <cleanHistoryOnStart>${log.cleanHistoryOnStart}</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>warn</level>
        </filter>
    </appender>

    <!-- 异步输出 -->
    <appender name ="debug_async" class= "ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold >0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref ="debug_file"/>
    </appender>

    <!-- 异步输出 -->
    <appender name ="info_async" class= "ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold >0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref ="info_file"/>
    </appender>

    <springProfile name="prod">
        <root level="info">
            <appender-ref ref="debug_async"/>
            <appender-ref ref="info_async"/>
            <appender-ref ref="warn_file"/>
        </root>
    </springProfile>
    <springProfile name="sit,uat">
        <root level="info">
            <appender-ref ref="console"/>
            <appender-ref ref="debug_file"/>
            <appender-ref ref="info_file"/>
            <appender-ref ref="warn_file"/>
        </root>
    </springProfile>
    <!--默认的包控制-->
    <logger name="org.mybatis" value="debug"/>
</configuration>
  • 创建的文件文件如下&打印的日志格式

image.png

image.png

  • 分布式链路ID的实现采用slf4j里面的MDC模式来实现,对应在logback.xml里面的文件位置

image.png

  • MDC的实现方式如下,详细代码
package org.example.filter;

import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;

public class MDCUtils {

    /**
     * 追踪ID的名称
     */
    public final static String TRACE_ID = "traceId";


    /**
     * 赋值MDC
     */
    public static void  addTraceId(){
        String traceId = createTraceId();
        MDC.put(TRACE_ID,traceId);
    }

    /**
     * MDC设置traceId
     */
    public static void putTraceId(String traceId){
        MDC.put(TRACE_ID,traceId);
    }

    /**
     * 获取MDC
     */
    public static String getTraceId(){
        return MDC.get(TRACE_ID);
    }

    /**
     * 移除MDC
     */
    public static void removeTraceId(){
        MDC.remove(TRACE_ID);
    }


    /**
     * 创建traceId
     */
    public static  String createTraceId(){
        return IdUtil.getSnowflake().nextIdStr();
    }
}
package org.example.filter;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class WebTraceFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
        try {
            String traceId = request.getHeader(MDCUtils.TRACE_ID);
            if (StrUtil.isBlank(traceId)){
                MDCUtils.addTraceId();
            }else{
                MDCUtils.putTraceId(traceId);
            }
            filterChain.doFilter(request,response);
        }catch (Exception e){
           log.error("链路ID设置出错",e);
        } finally {
            MDCUtils.removeTraceId();
        }
    }
}
  • MDC的方式一些好的监控工具例如zipkin,skywalking里面也有一些好的实现,配置如下截图

image.png

image.png

  • logback配置的pom文件如下
<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.6.13</version>
  </parent>

  <dependencies>
    <!-- Spring MVC依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- springBoot的Test依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.1</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.16</version>
    </dependency>
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.16</version>
    </dependency>
</dependencies>
  • 我们在实际项目开发中,打印日志是一个比较重要的事情,在打印日志的时候,要善于利用日志的开关来控制日志的输出,这样有利于日志的输出控制和问题的排查。

image.png

log4j2

  • pom依赖,需要排除spring-boot-starter-logging和引入spring-boot-starter-log4j2
 <parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.6.13</version>
  </parent>

  <dependencies>
    <!-- Spring MVC依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <!--log4j2的依赖-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <!-- springBoot的Test依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.1</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.16</version>
    </dependency>
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.16</version>
    </dependency>
</dependencies>
  • 在resource目录下面创建log4j2.xml配置文件 image.png
  • log4j2.xml配置文件的详细内容
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<!--  %d{HH:mm:ss.SSS}  毫秒的时间
       %p代表输出该条日志的等级
       %t  当前线程名称
       %-5level  日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
       %c{1}  类名
       %L 输出行号
       %msg 日志文本
       %n 换行
  其他常用的占位符有:
       %F 输出所在的类文件名,如Client.java
       %M 输出所在方法名
       %m是输出代码指定的日志信息
       %l  输出语句所在的行数, 包括类名、方法名、文件名、行数 (这个比较强大)  -->
<configuration status="WARN" monitorInterval="30">
    <!--先定义所有的appender-->
    <appenders>
        <!--这个输出控制台的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] [%t] - %l - %m%n"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
        <File name="log" fileName="E:/log/test.log" append="false">
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] [%t] - %l - %m%n"/>
        </File>

        <!--INFO日志文件输出 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="E:/log/info.log"
                     filePattern="${sys:user.home}/log/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
        <!-- Warn日志文件输出 -->
        <RollingFile name="RollingFileWarn" fileName="E:/log/warn.log"
                     filePattern="${sys:user.home}/log/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
        <!-- Error日志文件输出 -->
        <!--<RollingFile name="RollingFileError" fileName="E:/log/error.log"-->
        <!--filePattern="${sys:user.home}/log/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">-->
        <!--<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>-->
        <!--<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>-->
        <!--<Policies>-->
        <!--<TimeBasedTriggeringPolicy/>-->
        <!--<SizeBasedTriggeringPolicy size="100 MB"/>-->
        <!--</Policies>-->
        <!--</RollingFile>-->
    </appenders>

    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等-->
        <!--<logger name="org.springframework" level="INFO"></logger>-->
        <!--<logger name="org.mybatis" level="INFO"></logger>-->
        <root level="INFO">
            <appender-ref ref="Console"/>
            <appender-ref ref="log"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <!--<appender-ref ref="RollingFileError"/>-->
        </root>
    </loggers>
</configuration>