SpringBoot 之日志框架

日志框架简述

市面上的日志框架: JUL、JCL(Apache 基金会的)、Jboss-logging、logback、log4j、log4j2、slf4j....

日志门面(抽象层)日志实现
JCL(Jakarta Commons Logging-属于Apache基金会)、jboss-loggingJUL(java.util.logging-Java JDK自带的)
SLF4j(Simple Logging Facade for Java)Log4j 、 Log4j2 、Logback

SLF4jLog4jLogback 出自同一人之手。
Log4j2 是另外一个人开发的,并不是 Log4j 的升级版,只是借用了 Log4j 的名号而已,Logback 才是 Log4j 的升级版。
Log4j2相比Log4jLogback有很大的性能提升。
Log4j2能够避免死锁。

日志分级

日志级别从低到高分为:TRACE < DEBUG < INFO < WARN < ERROR
如果设置为WARN,则低于WARN的信息都不会输出。

SLF4J 使用

SLF4J Github 开源地址:https://github.com/qos-ch/slf4j
SLF4J 使用文档地址:https://www.slf4j.org/manual.html

开发的时候,应用中使用日志系统时,为了将来的扩展,日志记录方法的调用,不应该直接调用日志的实现类,而是调用日志抽象层里面的方法;这样以后即使更换了实现类也没有关系。

使用方式一:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
	private static final Logger log = LoggerFactory.getLogger(HelloWorld.class);
	public static void main(String[] args) {    
      log.info("Hello World");
    }
}

使用方式二:

import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HelloWolrd {
    public static void main(String[] args) {       
        log.info("this is info log...");
    }
}

SpringBoot 日志使用(Logback)

SpringBoot 默认使用 SLF4jLogback 记录日志。

Spring Boot 指定 Logback 日志配置文件:
1、logback.xml: 应用启动时会直接被日志框架 logback 识别而被使用,不经过Spring Boot,所以使用 logback.xml 作为日志配置文件,不能使用Spring Boot 的 Profile 高级功能。
2、logback-spring.xml: logback 日志框架就不能直接识别到,而会由 Spring Boot 解析此日志配置文件,从而可以使用Spring Boot 的 Profile 高级功能。

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">

    <contextName>logback</contextName>

    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <!-- 这里定义日志的根目录 -->
    <property name="log.path" value="./" />
    <property name="info.fileName" value="info" />
    <property name="debug.fileName" value="debug" />
    <property name="warn.fileName" value="warn" />
    <property name="error.fileName" value="error" />

    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 ALL -->
    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/${info.fileName}.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
                滚动时产生日志的归档路径及文件名
                %d{yyyy-MM-dd}:按天进行日志滚动
                %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${log.path}/${info.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>90</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/${debug.fileName}.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/debug/${debug.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <!--用于配置符合过滤条件的操作,ACCEPT表示日志会被立即处理,不再经过剩余过滤器-->
            <onMatch>ACCEPT</onMatch>
            <!--用于配置不符合过滤条件的操作,DENY表示日志将立即被抛弃不再经过其他过滤器-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/${warn.fileName}.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/${warn.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/${error.fileName}.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/${error.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="REQUEST" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/request.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/request-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
    </appender>

    <!--
        用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>;
        通过getLogger("name/class")获取到这个logger,输出日志的时候,就会使用这个logger配置的appender输出,同时还会使用rootLogger配置的appender;
        可以使用logger节点的additivity="false"属性来屏蔽rootLogger的appender,这样就可以不使用rootLogger的appender输出日志了;
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        addtivity:是否向上级logger传递打印信息。默认是true。
    -->
    <logger name="com.example.springbootprofile.controller" level="info">
        <appender-ref ref="REQUEST"/>
    </logger>

    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
     -->
    <logger name="com.example.mapper" level="debug"/>


    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->
    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE_ALL"/>
        <appender-ref ref="DEBUG_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>

    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <root level="info">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <!--生产环境:输出到文件-->
    <springProfile name="prd">
        <root level="info">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE_ALL"/>
            <appender-ref ref="DEBUG_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>

</configuration>

logback 日志配置文件案例

logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR,如果设置为INFO,则低于INFO的信息都不会输出 -->
<configuration>
    <!-- 上下文名称,默认上下文名称为“default”,可以通过%contextName来打印日志上下文名称 -->
    <contextName>boot</contextName>
    <!-- 读取SpringBoot配置文件属性值,日志文件必须为:logback-spring.xml 才生效 -->
    <springProperty scope="context" name="port" source="server.port" defaultValue="localhost"/>
    <!-- 日志存放路径 -->
    <property scope="context" name="log.path" value="logs"/>
    <!-- 日志输出格式 -->
    <property scope="context" name="log.pattern"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %X{traceId} [%thread] %logger{36}:[%L] - %msg%n"/>

    <!-- 输出到控制台 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %X{traceId} %green([%thread]) %boldMagenta(%logger{36}:[%L]) - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 输出到文件 -->
    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录,会自动对日志进行拆分 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!--超过500MB就拆分日志-->
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- 输出到文件 -->
    <appender name="FILE_REQUEST" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/request.log</file>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/request-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
    </appender>

    <!-- 输出到文件,只打印ERROR日志 -->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <springProfile name="local">
        <!-- 设置包下的日志打印级别 -->
        <logger name="com.example.boot3.mapper" level="DEBUG"/>
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <!-- 指定开发环境日志 -->
    <springProfile name="dev">
        <!-- 设置包下的日志打印级别为 debug -->
        <logger name="com.example.boot3.mapper" level="DEBUG"/>
        <!-- 设置包下的日志输出到指定 appender,日志打印级别为 info -->
        <logger name="com.example.boot3.controller" level="INFO">
            <appender-ref ref="FILE_REQUEST"/>
        </logger>
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE_ALL"/>
            <appender-ref ref="FILE_ERROR"/>
        </root>
    </springProfile>

    <!-- 指定生产环境日志输 -->
    <springProfile name="prod">
        <!-- 设置包下的日志输出到指定 appender,日志打印级别为 info -->
        <logger name="com.example.boot3.controller">
            <appender-ref ref="FILE_REQUEST"/>
        </logger>
        <root level="INFO">
            <appender-ref ref="FILE_ALL"/>
            <appender-ref ref="FILE_ERROR"/>
        </root>
    </springProfile>

</configuration>

logback 配置文件解析

一、configuration 根节点

属性:
// scan : 配置文件如果发生改变,将会重新加载,默认值为true;
// scanPeriod : 检测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位时毫秒,当scan为true时,这个属性生效,默认时间间隔为1min。
// debug : 默认为false ,设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。
<configuration scan="true" scanPeriod="2" debug="true">
	<!--TODO : 子节点信息-->
</configuration>

基本结构:
多个 appender 元素,零个或多个 logger 元素,最多一个 root 元素。
在这里插入图片描述
二、appender 子节点
负责写日志的组件。
基本结构:
在这里插入图片描述
属性:
name:appender 名称;
class:指定 appender 的全限定名;

<?xml version="1.0" encoding="utf-8"?> 
<configuration debug="true" scan="true" scanPeriod="2">
    <!-- conf consoel out -->
    <appender name="console_out" class="ch.qos.logback.core.ConsoleAppender">
    </appender>

    <!-- conf file out -->
    <appender name="file_out" class="ch.qos.logback.core.FileAppender">
    </appender>
    
    <!-- conf file out -->
    <appender name="file_out" class="ch.qos.logback.core.RollingFileAppender">
    </appender>

    <root></root>
    <loger></loger>
</configuration>

三、ConsoleAppender 日志输出到控制台
以下节点:
encoder : 对日志进行格式化。
target : 字符串System.out 或者 System.err, 默认 System.out;

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <!-- conf consoel out -->
  <appender name="console_out" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
  </appender>

  <root level="INFO">             
    <appender-ref ref="console_out" />   
  </root>     
</configuration>

四、FileAppender 日志添加到文件
有如下节点:
file : 被写入的文件名,可以是相对目录 , 也可以是绝对目录 , 如果目录不存在则会自动创建
append : 如果是true , 日志被追加到文件结尾 , 如果是false,清空现存文件 , 默认是true
encoder : 对日志进行格式化 [具体的转换符说明请参见官网.]
prodent : 如果是true,日志会被安全的写入文件 , 即使其他的FileAppender也会向此文件做写入操作 , 默认是false

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <!-- conf file out -->
    <appender name="file_out" class="ch.qos.logback.core.FileAppender">
        <file>logs/debug.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
</configuration>

SpringBoot 集成 Log4j2

Springboot整合log4j2
Maven依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- 排除springboot自带的log依赖 -->
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-logging</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
            <exclusion>
                <groupId>log4j</groupId>
                <artifactId>*</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>*</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- 添加springboot的log4j2依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
</dependencies>

application.properties配置:

logging.config=classpath:log4j2-spring.xml

log4j2.xml日志配置:

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="600">

    <!-- 优先级从高到低分别是 OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL -->
    <!-- 单词解释: Match:匹配 DENY:拒绝 Mismatch:不匹配 ACCEPT:接受 -->
    <!-- DENY,日志将立即被抛弃不再经过其他过滤器; NEUTRAL,有序列表里的下个过滤器过接着处理日志; ACCEPT,日志会被立即处理,不再经过剩余过滤器。 -->
    <!--输出日志的格式
     %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间
     %p : 日志输出格式
     %c : logger的名称
     %m : 日志内容,即 logger.info("message")
     %n : 换行符
     %C : Java类名
     %L : 日志输出所在行数
     %M : 日志输出所在方法名
     hostName : 本地机器名
     hostAddress : 本地ip地址 -->

    <!--变量配置-->
    <Properties>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
        <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%L  - %msg%n" />
        <!-- 定义日志存储的路径,不要配置相对路径 -->
        <property name="FILE_PATH" value="E:\logs" />
        <!--项目名称-->
        <property name="FILE_NAME" value="SpringBootDemo" />
    </Properties>

    <appenders>

        <!--输出控制台的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!-- 输出控制台的配置,这里输出error级别的信息到System.err,在控制台上看到的是红色文字 -->
        <Console name="console_err_appender" target="SYSTEM_ERR">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY" />
        </Console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </File>

        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!--这个log专门为一个类设置的,因为这个类的日志太多,滚的太快,影响其他日志查看-->
        <RollingFile name="RollingFileRequest" fileName="${FILE_PATH}/${FILE_NAME}/request.log" filePattern="${FILE_PATH}/${FILE_NAME}-REQUEST-%d{yyyy-MM-dd}_%i.log.gz">
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

    </appenders>

    <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
    <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>

        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>
        <!--监控系统信息-->
        <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>

<!--        <logger name="com.example.demo.controller" level="INFO" additivity="false">-->
<!--            <appender-ref ref="RollingFileRequest"/>-->
<!--        </logger>-->

        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="Filelog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
            <appender-ref ref="console_err_appender"/>
        </root>
    </loggers>
</configuration>

测试类:

@RestController
@Slf4j
public class TestController {
    @RequestMapping(value = "test")
    public String test(){
        log.info("info级别");
        log.debug("debug级别");
        log.warn("warn级别");
        log.error("error级别");
        return "HELLO-BUG";
    }
}

案例:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">

    <Properties>
        <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%L  - %msg%n"/>
        <property name="FILE_PATH" value="F:\\"/>
        <property name="FILE_NAME" value="SpringBootDemo"/>
    </Properties>

    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>
        <Console name="Console_debug" target="SYSTEM_OUT">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>
        <RollingFile name="RollingFile" fileName="${FILE_PATH}/info.log"
                     filePattern="${FILE_PATH}/info.log.%d{yyyy-MM-dd}">
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS}[%5p][%t][%c{1}:%L%X{logMark}]:%m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
    </appenders>
    <loggers>
        <logger name="com.example.mapper" level="debug" additivity="false">
            <appender-ref ref="Console_debug"/>
        </logger>
        <root level="all">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFile"/>
        </root>
    </loggers>
</configuration>

使用 MDC 实现全链路调用日志跟踪

日志拦截器

import com.evcas.charge.pile.platform.common.annotation.LogNoTrace;
import com.evcas.charge.pile.platform.common.utils.StringUtils;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.UUID;

public class LogInterceptor implements HandlerInterceptor {

    private static final String TRACE_ID = "traceId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        Method method = ((HandlerMethod) handler).getMethod();
        if (method.isAnnotationPresent(LogNoTrace.class)) {
            return true;
        }
        String tid = UUID.randomUUID().toString().replace("-", "").substring(0, 10);
        // 如果有上层调用就用上层的ID
        if (!StringUtils.isEmpty(request.getHeader(TRACE_ID))) {
            tid = request.getHeader(TRACE_ID);
        }
        MDC.put(TRACE_ID, tid);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) {
        MDC.remove(TRACE_ID);
    }

}
import java.lang.annotation.*;

/**
 * 不需要日志跟踪
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogNoTrace {

}
import com.gotion.opencenter.interceptor.LogInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {
    @Bean
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor());
    }
}

修改日志格式

<property name="pattern">%d{HH:mm:ss.SSS} %X{traceId} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>

重点是 %X{traceId} traceId 和 MDC 中的键名称一致。

trackId 丢失解决

异步子线程会丢失了trackId:
思路: 将父线程的trackId传递下去给子线程即可。

1、自定义线程池,交给 Spring 管理

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class MyThreadPoolConfig {
    @Bean("MyExecutor")
    public Executor asyncExecutor() {
        MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
        //核心线程数5:线程池创建时候初始化的线程数
        executor.setCorePoolSize(5);
        //最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(5);
        //缓冲队列500:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("myExecutor");
        executor.initialize();
        return executor;
    }
}
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;

public class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    public MyThreadPoolTaskExecutor() {
        super();
    }

    @Override
    public void execute(Runnable task) {
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }


    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}
import org.slf4j.MDC;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;

public class ThreadMdcUtil {

    private static final String TRACE_ID = "traceId";

    // 获取唯一性标识
    public static String generateTraceId() {
        return UUID.randomUUID().toString();
    }

    public static void setTraceIdIfAbsent() {
        if (MDC.get(TRACE_ID) == null) {
            MDC.put(TRACE_ID, generateTraceId());
        }
    }

    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     *
     * @param callable
     * @param context
     * @param <T>
     * @return
     */
    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }

    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     *
     * @param runnable
     * @param context
     * @return
     */
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值