(4) spring-boot+logback+log4j2+MDC

spring boot内部使用Commons Logging来记录日志,但也保留外部接口可以让一些日志框架来进行实现,例如Java Util Logging,Log4J2还有Logback。如果你想用某一种日志框架来进行实现的话,就必须先配置,默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。

1、日志配置

1.1、默认配置

依赖于开发者选择的日志框架,这些对应的配置文件会被加载;

日志框架配置文件
Logbacklogback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
Log4jlog4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
Log4j2log4j2-spring.xml, log4j2.xml
JDK(JAVA Util Logging)logging.properties

Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项,默认的命名规则,并且放在 src/main/resources 下面即可。

1.2、修改spring-boot日志配置

若不想使用spring-boot的默认日志路径及名称,可以在application.properties修改日志配置,配置如下:

logging.config=classpath:spring/log4j2.xml

2、logback配置

Logback是log4j框架的作者开发的新一代日志框架,它效率更高、能够适应诸多的运行环境,同时天然支持SLF4J。

2.1、pom配置

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

若项目中添加以上依赖,则spring-boot将自动使用logback作为应用的日志框架;Spring Boot启动的时候,由org.springframework.boot.logging.Logging-Application-Listener根据情况初始化并使用。但在实际的开发中无需直接添加此依赖,因为spring-boot-starter中已包含了此依赖。

2.2、配置文件位置

配置文件名称为logback-spring.xml并放在classpath下,即项目的resources目录下;或自定义配置文件名称和位置,并在application.properties中配置,如下所示:

logging.config=classpath:config/logback-spring.xml

2.3、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
       说明:
       1、日志级别及文件
           日志记录采用分级记录,级别与日志文件名相对应,不同级别的日志信息记录到不同的日志文件中
           例如:error级别记录到log_error_xxx.log或log_error.log(该文件为当前记录的日志文件),而log_error_xxx.log为归档日志,
           日志文件按日期记录,同一天内,若日志文件大小等于或大于100M,则按0、1、2...顺序分别命名
           例如log-level-2013-12-21.0.log
           其它级别的日志也是如此。
       2、文件路径
           若开发、测试用,在Eclipse中运行项目,则到Eclipse的安装路径查找logs文件夹,以相对路径../logs。
           若部署到Tomcat下,则在Tomcat下的logs文件中
       3、Appender
           FILEERROR对应error级别,文件名以log-error-xxx.log形式命名
           FILEWARN对应warn级别,文件名以log-warn-xxx.log形式命名
           FILEINFO对应info级别,文件名以log-info-xxx.log形式命名
           FILEDEBUG对应debug级别,文件名以log-debug-xxx.log形式命名
           FILEALL对应所有级别,文件名以log-all-xxx.log形式命名
           stdout将日志信息输出到控制上,为方便开发测试使用
    -->
    <contextName>SpringBootDemo</contextName>
    <property name="LOG_PATH" value="C:\Users\zhaozhou\Desktop\demo" />
    <!--设置系统日志目录-->
    <property name="APPDIR" value="SpringBootDemo" />

    <!-- 日志记录器,日期滚动记录 -->
    <appender name="FILEERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_error.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,例如今天是2013-12-21日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
            而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录error级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 日志记录器,日期滚动记录 -->
    <appender name="FILEWARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_warn.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,例如今天是2013-12-21日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
            而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 日志记录器,日期滚动记录 -->
    <appender name="FILEINFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_info.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,例如今天是2013-12-21日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
            而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 日志记录器,日期滚动记录 -->
    <appender name="FILEALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_all.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,例如今天是2013-12-21日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
            而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/${APPDIR}/all/log-all-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>trace</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
    </appender>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--encoder 默认配置为PatternLayoutEncoder-->
        <encoder>
            <pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
    </appender>

    <logger name="org.springframework" level="WARN" />
    <logger name="org.mybatis" level="DEBUG" />
    <logger name="com.springboot" level="INFO"/>

    <!-- 生产环境下,将此级别配置为适合的级别,以免日志文件太多或影响程序性能 -->
    <root level="DEBUG">
        <appender-ref ref="FILEERROR" />
        <appender-ref ref="FILEWARN" />
        <appender-ref ref="FILEINFO" />
        <appender-ref ref="FILEALL" />

        <!-- 生产环境将请stdout,testfile去掉 -->
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
2.3.1、设置上下文名称<contextName>

每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。

<contextName>SpringBootDemo</contextName>
2.3.2、设置变量<property>

用来定义变量值的标签, 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。

<property name="LOG_PATH" value="C:\Users\zhaozhou\Desktop\demo" />
<!--设置系统日志目录-->
<property name="APPDIR" value="SpringBootDemo" />

此处设置系统日志的path和应用日志的目录;

2.3.3、输出到RollingFileAppender

随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。RollingFileAppender用于切分文件日志,配置如下:

    <!-- 日志记录器,日期滚动记录 -->
    <appender name="FILEERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_error.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,例如今天是2013-12-21日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
            而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录error级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
  • 此处配置日志类型为RollingFileAppender,<rollingPolicy>配置滚动存储的策略,一天一个文件,当文件大于100M时,按文件大小分文件存储。
  • <encoder>配置日志文件的格式,<pattern>定义了格式;
  • <filter>定义日志过滤器,本文件的日志级别为error,当日志级别为error时进行存储,不符合时不处理;
2.3.4、配置说明

本日志配置了五个处理器,分别处理error、warn、info、all及debug日志;

  • error处理器只处理并存储error级别的日志;
  • warn处理器只处理并存储warn级别的日志;
  • info处理器只处理并存储info级别的日志;
  • all处理器处理所有级别的日志;
  • debug及以上的日志级别都会在标准输出中输出;

最终的日志文件会包含4个:

  • log_all.log:存储所有debug及以上级别的日志;
  • log_error.log:存储error级别的日志;
  • log_warn.log:存储warn级别的日志;
  • log_info.log:存储info级别的日志;

3、log4j2配置

由于spring-boot的所有starter的默认日志框架为logback,所以在配置log4j2时,需要将logback的包给排除,以免日志包冲突;

3.1、pom配置

    <dependencies>
        <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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

       <!-- <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>-->


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.25</version>
        </dependency>-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>


        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.8</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

将所有会包含spring-boot-starter-logging的包给去除,并添加spring-boot-starter-log4j2的依赖包;

3.1、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" name="defaultLogConfig"
               packages="">
    <properties>
        <property name="LOG_HOME">C:\Users\zhaozhou\Desktop\demo</property>
        <property name="patternlayout">%-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ]- [%X{userName}] - [%X{reqId}] - [%-5p] %c-%M:%L - %m%n%throwable{full}</property>
    </properties>
    <Appenders>
        <!--这个输出控制台的配置-->
        <!--follow 不知道干嘛的-->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${patternlayout}" />
        </Console>
        <RollingFile name="LOGERROR" fileName="${LOG_HOME}/error_log.log" filePattern="${LOG_HOME}/error/log-error-%d{yyyy-MM-dd}.%i.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${patternlayout}"/>
            <SizeBasedTriggeringPolicy size="100MB"/>
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>

        <RollingFile name="LOGWARN" fileName="${LOG_HOME}/warn_log.log" filePattern="${LOG_HOME}/warn/log-warn-%d{yyyy-MM-dd}.%i.log">
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${patternlayout}"/>
            <SizeBasedTriggeringPolicy size="100MB"/>
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>

        <RollingFile name="LOGALL" fileName="${LOG_HOME}/all_log.log" filePattern="${LOG_HOME}/all/log-all-%d{yyyy-MM-dd}.%i.log">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="ACCEPT"/>
            <PatternLayout pattern="${patternlayout}"/>
            <SizeBasedTriggeringPolicy size="100MB"/>
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>

    </Appenders>
    <Loggers>

        <!-- spring log -->
        <AsyncLogger name="org.springframework" level="warn"/>

        <!-- activiti log -->
        <AsyncLogger name="org.activiti" level="info" />


        <AsyncLogger name="org.mybatis" level="debug" />

        <AsyncLogger name="org.apache" level="info" />

        <AsyncLogger name="com.springboot.demo" level="debug"/>


        <Root level="debug">
            <AppenderRef ref="LOGERROR" />
            <AppenderRef ref="LOGWARN"/>
            <AppenderRef ref="LOGALL"/>
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

配置文件大体和上面logback的配置差不多,在此不多赘述;

4、MDC配置

4.1、MDC简介

MDC 是 Mapped Diagnostic Context 的缩写,“映射诊断上下文”看起来高大上的样子,其实是非常简单的,就是一个临时存放k-v对的容器。和普通Map的区别是它是基于ThreadLocal实现的,所以不存在资源竞争问题,可以放心的往里面放东西。其主要用于打LOG时跟踪一个“会话“、一个”事务“。举例,有一个web controller,在同一时间可能收到来自多个客户端的请求,如果一个请求发生了错误,我们要跟踪这个请求从controller开始一步步都执行到了哪些代码、有哪些log的输出。这时我们可以看log文件,但是log文件是多个请求同时记录的,基本无法分辨哪行是哪个请求产生的,虽然我们可以看线程,但线程可能被复用,也是很难分辨出,这时MDC就派上用场了。

4.2、主要api

public class MDC {
    public static void put(String key, String val) //设置k-v
    public static String get(String key) //获取value
    public static void remove(String key) //移除k-v
    public static void clear() //clearMDC
    public static Map<String, String> getCopyOfContextMap() //获取MDC的备份map
    public static void setContextMap(Map<String, String> contextMap) //设置MDC的map
}
  • put()和setContextMap()主要进行属性设置;
  • get()和getCopyOfContextMap()主要进行属性获取;
  • remove()移除属性信息,主要在线程结束时处理设置的属性;

4.3、基于spring的HandlerInterceptorAdapter

此种方法主要适用场景为跟踪http请求,通过拦截http请求,设置MDC,并在日志中打印设置参数,即可通过日志跟踪请求链;

4.3.1、HandlerInterceptorAdapter拦截器配置

登录拦截器:

public class LoginInterceptor extends HandlerInterceptorAdapter {

    private static String MDC_KEY_USER_NAME = "userName";
    private static String MDC_KEY_REQ_ID = "reqId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MDC.put(MDC_KEY_USER_NAME,"zhaozhou");
        MDC.put(MDC_KEY_REQ_ID, UUID.randomUUID().toString());
        return super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {

        super.postHandle(request, response, handler, modelAndView);
    }

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

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        super.afterConcurrentHandlingStarted(request, response, handler);
    }
}

注册拦截器:

@Component
public class MvcLoginInterceptor implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册拦截器
        InterceptorRegistration ir = registry.addInterceptor(new LoginInterceptor());
        // 配置拦截的路径
        ir.addPathPatterns("/**");
        // 配置不拦截的路径
        ir.excludePathPatterns("/static/**","/templates/**");
    }
}
4.3.2、log4j2的pattern配置

在log4j2-spring.xml的配置做如下修改:

<properties>
    <property name="LOG_HOME">C:\Users\zhaozhou\Desktop\demo</property>
    <property name="patternlayout">%-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ]- [%X{userName}] - [%X{reqId}] - [%-5p] %c-%M:%L - %m%n%throwable{full}</property>
</properties>

在patternlayout属性中添加userName和reqId的打印;

对于logback日志的配置同此处一样;

4.3.2、输出结果

浏览器请求:http://localhost:8888/

2018-10-31 12:11:06[ http-nio-8888-exec-8:17662 ]- [] - [] - [DEBUG] org.springframework.beans.factory.support.DefaultListableBeanFactory-doGetBean:254 - Returning cached instance of singleton bean 'testController'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17662 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-doDispatch:979 - Last-Modified value for [/] is: -1
2018-10-31 12:11:06[ http-nio-8888-exec-8:17663 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.SqlSessionUtils-getSqlSession:97 - Creating a new SqlSession
2018-10-31 12:11:06[ http-nio-8888-exec-8:17664 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.SqlSessionUtils-registerSessionHolder:148 - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1366610e] was not registered for synchronization because synchronization is not active
2018-10-31 12:11:06[ http-nio-8888-exec-8:17665 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doGetConnection:114 - Fetching JDBC Connection from DataSource
2018-10-31 12:11:06[ http-nio-8888-exec-8:17665 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction-openConnection:87 - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2c6224d7] will not be managed by Spring
2018-10-31 12:11:06[ http-nio-8888-exec-8:17666 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Preparing: select id, name, tel, email, create_time from t_user WHERE id = ?
2018-10-31 12:11:06[ http-nio-8888-exec-8:17667 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Parameters: 1(Long)
2018-10-31 12:11:06[ http-nio-8888-exec-8:17673 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - <== Total: 1
2018-10-31 12:11:06[ http-nio-8888-exec-8:17674 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.mybatis.spring.SqlSessionUtils-closeSqlSession:191 - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1366610e]
2018-10-31 12:11:06[ http-nio-8888-exec-8:17674 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doReleaseConnection:340 - Returning JDBC Connection to DataSource
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.view.ContentNegotiatingViewResolver-getMediaTypes:269 - Requested media types are [text/html, application/xhtml+xml, image/webp, application/xml;q=0.9, */*;q=0.8] based on Accept header types and producible media types [*/*])
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.view.BeanNameViewResolver-resolveViewName:81 - No matching bean found for view name '/index.html'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.view.ContentNegotiatingViewResolver-getBestView:348 - Returning [org.thymeleaf.spring5.view.ThymeleafView@199e0a7e] based on requested media type 'text/html'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17675 ]- [zhaozhou] - [d3b60c00-7946-4d5d-bf15-016d286c110c] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-render:1319 - Rendering view [org.thymeleaf.spring5.view.ThymeleafView@199e0a7e] in DispatcherServlet with name 'dispatcherServlet'
2018-10-31 12:11:06[ http-nio-8888-exec-8:17678 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-processRequest:1000 - Successfully completed request
2018-10-31 12:11:06[ http-nio-8888-exec-8:17678 ]- [] - [] - [DEBUG] org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter-doFilterInternal:104 - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@4b2bca43
2018-10-31 12:11:06[ http-nio-8888-exec-1:17767 ]- [] - [] - [DEBUG] org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter-initContextHolders:114 - Bound request context to thread: org.apache.catalina.connector.RequestFacade@4b2bca43
2018-10-31 12:11:06[ http-nio-8888-exec-1:17767 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-doService:891 - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/favicon.ico]

可以看到,开始系统启动过程中是无userName喝reqId打印的,当进行http请求时,MDC配置的两个参数都打印出来,且reqId一直不变,通过此id即可追踪调用链;

4.4、基于AOP设置MDC

此种方法所有类型的调用请求的调用跟踪,例如http、基于tcp的RPC调用等;

4.4.1、pom配置
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
4.4.2、切面定义
@Aspect
@Component
public class LoginAop {

    private static String MDC_KEY_USER_NAME = "userName";
    private static String MDC_KEY_REQ_ID = "reqId";

    @Pointcut("execution(public * com.springboot.demo.controller..*.*(..))")
    public void webLog(){}

    @Before("webLog()")
    public void before(JoinPoint joinPoint){
        MDC.put(MDC_KEY_USER_NAME,"zhaozhou");
        MDC.put(MDC_KEY_REQ_ID, UUID.randomUUID().toString());
    }

    @AfterReturning(pointcut = "webLog()", returning = "ret")
    public void afterReturning(Object ret){
        MDC.remove(MDC_KEY_USER_NAME);
        MDC.remove(MDC_KEY_REQ_ID);
    }
}

MDC参数与4.3.1配置相同;

4.4.3、输出结果

浏览器请求:http://localhost:8888/

2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [] - [] - [DEBUG] org.springframework.beans.factory.support.DefaultListableBeanFactory-doGetBean:254 - Returning cached instance of singleton bean 'testController'
2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [] - [] - [DEBUG] org.springframework.web.servlet.DispatcherServlet-doDispatch:979 - Last-Modified value for [/] is: -1
2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.SqlSessionUtils-getSqlSession:97 - Creating a new SqlSession
2018-10-31 12:46:24[ http-nio-8888-exec-6:24774 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.SqlSessionUtils-registerSessionHolder:148 - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@675b44f3] was not registered for synchronization because synchronization is not active
2018-10-31 12:46:24[ http-nio-8888-exec-6:24775 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doGetConnection:114 - Fetching JDBC Connection from DataSource
2018-10-31 12:46:24[ http-nio-8888-exec-6:24775 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction-openConnection:87 - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@121b39d5] will not be managed by Spring
2018-10-31 12:46:24[ http-nio-8888-exec-6:24776 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Preparing: select id, name, tel, email, create_time from t_user WHERE id = ?
2018-10-31 12:46:24[ http-nio-8888-exec-6:24776 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - ==> Parameters: 1(Long)
2018-10-31 12:46:24[ http-nio-8888-exec-6:24782 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] com.springboot.demo.dao.UserDao.getUserById-debug:159 - <== Total: 1
2018-10-31 12:46:24[ http-nio-8888-exec-6:24783 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.mybatis.spring.SqlSessionUtils-closeSqlSession:191 - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@675b44f3]
2018-10-31 12:46:24[ http-nio-8888-exec-6:24783 ]- [zhaozhou] - [519e55f8-45be-4923-a2cc-1b5181eb3050] - [DEBUG] org.springframework.jdbc.datasource.DataSourceUtils-doReleaseConnection:340 - Returning JDBC Connection to DataSource
2018-10-31 12:46:24[ http-nio-8888-exec-6:24784 ]- [] - [] - [DEBUG] org.springframework.web.servlet.view.ContentNegotiatingViewResolver-getMediaTypes:269 - Requested media types are [text/html, application/xhtml+xml, image/webp, application/xml;q=0.9, */*;q=0.8] based on Accept header types and producible media types [*/*])
2018-10-31 12:46:24[ http-nio-8888-exec-6:24784 ]- [] - [] - [DEBUG] org.springframework.web.servlet.view.BeanNameViewResolver-resolveViewName:81 - No matching bean found for view name '/index.html'

可以看到,输出与4.3.2基本相同;

参考源码:https://github.com/zhaozhou11/spring-boot-demo.git

相关阅读:
spring-boot基础环境搭建 【https://www.jianshu.com/p/ee36bb9faa10
spring-boot配置详解【https://www.jianshu.com/p/1d037ab638ef
spring-boot+druid+mybatis环境搭建【https://www.jianshu.com/p/e6c9e9945e45

参考博客:

http://blog.51cto.com/11931236/2058708
http://tengj.top/2017/04/05/springboot7/
https://juejin.im/post/5a1f86f0f265da4326529c61
https://juejin.im/post/5b128f326fb9a01e8b7814c4
https://blog.csdn.net/Evankaka/article/details/50637994
https://blog.csdn.net/wohaqiyi/article/details/72853962
https://blog.csdn.net/wohaqiyi/article/details/72853962
https://my.oschina.net/gmd/blog/615849
https://www.jianshu.com/p/44640b298c45
https://blog.csdn.net/u012050154/article/details/77370297

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值