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

spring boot內部使用Commons Logging來記錄日志,但也保留外部接口可以讓一些日志框架來進行實現,例如Java Util Logging,Log4J2還有Logback。如果你想用某一種日志框架來進行實現的話,就必須先配置,默認情況下,Spring Boot會用Logback來記錄日志,并用INFO級別輸出到控制臺。

1、日志配置

1.1、默認配置

依賴于開發者選擇的日志框架,這些對應的配置文件會被加載;

日志框架 配置文件
Logback logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
Log4j log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
Log4j2 log4j2-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基礎環境搭建 【http://www.lxweimin.com/p/ee36bb9faa10
spring-boot配置詳解【http://www.lxweimin.com/p/1d037ab638ef
spring-boot+druid+mybatis環境搭建【http://www.lxweimin.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
http://www.lxweimin.com/p/44640b298c45
https://blog.csdn.net/u012050154/article/details/77370297

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容