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

spring boot內(nèi)部使用Commons Logging來記錄日志宣谈,但也保留外部接口可以讓一些日志框架來進(jìn)行實(shí)現(xiàn)瓮钥,例如Java Util Logging,Log4J2還有Logback探遵。如果你想用某一種日志框架來進(jìn)行實(shí)現(xiàn)的話番挺,就必須先配置姆坚,默認(rèn)情況下囱晴,Spring Boot會(huì)用Logback來記錄日志膏蚓,并用INFO級(jí)別輸出到控制臺(tái)。

1畸写、日志配置

1.1降允、默認(rèn)配置

依賴于開發(fā)者選擇的日志框架,這些對(duì)應(yīng)的配置文件會(huì)被加載艺糜;

日志框架 配置文件
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官方推薦優(yōu)先使用帶有-spring的文件名作為你的日志配置(如使用logback-spring.xml,而不是logback.xml)幢尚,命名為logback-spring.xml的日志配置文件破停,spring boot可以為它添加一些spring boot特有的配置項(xiàng),默認(rèn)的命名規(guī)則尉剩,并且放在 src/main/resources 下面即可真慢。

1.2、修改spring-boot日志配置

若不想使用spring-boot的默認(rèn)日志路徑及名稱理茎,可以在application.properties修改日志配置黑界,配置如下:

logging.config=classpath:spring/log4j2.xml

2管嬉、logback配置

Logback是log4j框架的作者開發(fā)的新一代日志框架,它效率更高朗鸠、能夠適應(yīng)諸多的運(yùn)行環(huán)境蚯撩,同時(shí)天然支持SLF4J。

2.1烛占、pom配置

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

若項(xiàng)目中添加以上依賴胎挎,則spring-boot將自動(dòng)使用logback作為應(yīng)用的日志框架;Spring Boot啟動(dòng)的時(shí)候忆家,由org.springframework.boot.logging.Logging-Application-Listener根據(jù)情況初始化并使用犹菇。但在實(shí)際的開發(fā)中無需直接添加此依賴,因?yàn)閟pring-boot-starter中已包含了此依賴芽卿。

2.2揭芍、配置文件位置

配置文件名稱為logback-spring.xml并放在classpath下,即項(xiàng)目的resources目錄下卸例;或自定義配置文件名稱和位置称杨,并在application.properties中配置,如下所示:

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

2.3币厕、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
       說明:
       1列另、日志級(jí)別及文件
           日志記錄采用分級(jí)記錄,級(jí)別與日志文件名相對(duì)應(yīng)旦装,不同級(jí)別的日志信息記錄到不同的日志文件中
           例如:error級(jí)別記錄到log_error_xxx.log或log_error.log(該文件為當(dāng)前記錄的日志文件)页衙,而log_error_xxx.log為歸檔日志,
           日志文件按日期記錄阴绢,同一天內(nèi)店乐,若日志文件大小等于或大于100M,則按0呻袭、1眨八、2...順序分別命名
           例如log-level-2013-12-21.0.log
           其它級(jí)別的日志也是如此。
       2左电、文件路徑
           若開發(fā)廉侧、測(cè)試用,在Eclipse中運(yùn)行項(xiàng)目篓足,則到Eclipse的安裝路徑查找logs文件夾段誊,以相對(duì)路徑../logs。
           若部署到Tomcat下栈拖,則在Tomcat下的logs文件中
       3连舍、Appender
           FILEERROR對(duì)應(yīng)error級(jí)別,文件名以log-error-xxx.log形式命名
           FILEWARN對(duì)應(yīng)warn級(jí)別涩哟,文件名以log-warn-xxx.log形式命名
           FILEINFO對(duì)應(yīng)info級(jí)別索赏,文件名以log-info-xxx.log形式命名
           FILEDEBUG對(duì)應(yīng)debug級(jí)別盼玄,文件名以log-debug-xxx.log形式命名
           FILEALL對(duì)應(yīng)所有級(jí)別,文件名以log-all-xxx.log形式命名
           stdout將日志信息輸出到控制上潜腻,為方便開發(fā)測(cè)試使用
    -->
    <contextName>SpringBootDemo</contextName>
    <property name="LOG_PATH" value="C:\Users\zhaozhou\Desktop\demo" />
    <!--設(shè)置系統(tǒng)日志目錄-->
    <property name="APPDIR" value="SpringBootDemo" />

    <!-- 日志記錄器埃儿,日期滾動(dòng)記錄 -->
    <appender name="FILEERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日志文件的路徑及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_error.log</file>
        <!-- 日志記錄器的滾動(dòng)策略,按日期砾赔,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 歸檔的日志文件的路徑蝌箍,例如今天是2013-12-21日志,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定暴心,可以將此文件與file指定文件路徑設(shè)置為不同路徑妓盲,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄。
            而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筋粗,日志文件會(huì)以索引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級(jí)別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 日志記錄器娜亿,日期滾動(dòng)記錄 -->
    <appender name="FILEWARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日志文件的路徑及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_warn.log</file>
        <!-- 日志記錄器的滾動(dòng)策略,按日期蚌堵,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 歸檔的日志文件的路徑买决,例如今天是2013-12-21日志,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定吼畏,可以將此文件與file指定文件路徑設(shè)置為不同路徑督赤,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄。
            而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没卸,日志文件會(huì)以索引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級(jí)別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 日志記錄器约计,日期滾動(dòng)記錄 -->
    <appender name="FILEINFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日志文件的路徑及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_info.log</file>
        <!-- 日志記錄器的滾動(dòng)策略,按日期滩褥,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 歸檔的日志文件的路徑,例如今天是2013-12-21日志炫加,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定瑰煎,可以將此文件與file指定文件路徑設(shè)置為不同路徑铺然,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄。
            而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沽瘦,日志文件會(huì)以索引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級(jí)別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 日志記錄器析恋,日期滾動(dòng)記錄 -->
    <appender name="FILEALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日志文件的路徑及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_all.log</file>
        <!-- 日志記錄器的滾動(dòng)策略,按日期盛卡,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 歸檔的日志文件的路徑助隧,例如今天是2013-12-21日志,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定滑沧,可以將此文件與file指定文件路徑設(shè)置為不同路徑并村,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄。
            而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膝昆,日志文件會(huì)以索引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級(jí)別的 -->
        <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 默認(rèn)配置為PatternLayoutEncoder-->
        <encoder>
            <pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--此日志appender是為開發(fā)使用外潜,只配置最底級(jí)別,控制臺(tái)輸出的日志級(jí)別是大于或等于此級(jí)別的日志信息-->
        <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"/>

    <!-- 生產(chǎn)環(huán)境下挠唆,將此級(jí)別配置為適合的級(jí)別处窥,以免日志文件太多或影響程序性能 -->
    <root level="DEBUG">
        <appender-ref ref="FILEERROR" />
        <appender-ref ref="FILEWARN" />
        <appender-ref ref="FILEINFO" />
        <appender-ref ref="FILEALL" />

        <!-- 生產(chǎn)環(huán)境將請(qǐng)stdout,testfile去掉 -->
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

2.3.1、設(shè)置上下文名稱<contextName>

每個(gè)logger都關(guān)聯(lián)到logger上下文玄组,默認(rèn)上下文名稱為“default”滔驾。但可以使用設(shè)置成其他名字,用于區(qū)分不同應(yīng)用程序的記錄俄讹。一旦設(shè)置哆致,不能修改,可以通過%contextName來打印日志上下文名稱。

<contextName>SpringBootDemo</contextName>

2.3.2患膛、設(shè)置變量<property>

用來定義變量值的標(biāo)簽摊阀, 有兩個(gè)屬性,name和value;其中name的值是變量的名稱胞此,value的值時(shí)變量定義的值臣咖。通過定義的值會(huì)被插入到logger上下文中。定義變量后漱牵,可以使“${}”來使用變量夺蛇。

<property name="LOG_PATH" value="C:\Users\zhaozhou\Desktop\demo" />
<!--設(shè)置系統(tǒng)日志目錄-->
<property name="APPDIR" value="SpringBootDemo" />

此處設(shè)置系統(tǒng)日志的path和應(yīng)用日志的目錄;

2.3.3酣胀、輸出到RollingFileAppender

隨著應(yīng)用的運(yùn)行時(shí)間越來越長(zhǎng)刁赦,日志也會(huì)增長(zhǎng)的越來越多,將他們輸出到同一個(gè)文件并非一個(gè)好辦法闻镶。RollingFileAppender用于切分文件日志甚脉,配置如下:

    <!-- 日志記錄器,日期滾動(dòng)記錄 -->
    <appender name="FILEERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日志文件的路徑及文件名 -->
        <file>${LOG_PATH}/${APPDIR}/log_error.log</file>
        <!-- 日志記錄器的滾動(dòng)策略儒溉,按日期宦焦,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 歸檔的日志文件的路徑,例如今天是2013-12-21日志顿涣,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定波闹,可以將此文件與file指定文件路徑設(shè)置為不同路徑,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄涛碑。
            而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,日志文件會(huì)以索引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級(jí)別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
  • 此處配置日志類型為RollingFileAppender,<rollingPolicy>配置滾動(dòng)存儲(chǔ)的策略毙籽,一天一個(gè)文件洞斯,當(dāng)文件大于100M時(shí),按文件大小分文件存儲(chǔ)坑赡。
  • <encoder>配置日志文件的格式烙如,<pattern>定義了格式;
  • <filter>定義日志過濾器毅否,本文件的日志級(jí)別為error亚铁,當(dāng)日志級(jí)別為error時(shí)進(jìn)行存儲(chǔ),不符合時(shí)不處理螟加;

2.3.4徘溢、配置說明

本日志配置了五個(gè)處理器吞琐,分別處理error、warn然爆、info顽分、all及debug日志;

  • error處理器只處理并存儲(chǔ)error級(jí)別的日志施蜜;
  • warn處理器只處理并存儲(chǔ)warn級(jí)別的日志;
  • info處理器只處理并存儲(chǔ)info級(jí)別的日志雌隅;
  • all處理器處理所有級(jí)別的日志翻默;
  • debug及以上的日志級(jí)別都會(huì)在標(biāo)準(zhǔn)輸出中輸出;

最終的日志文件會(huì)包含4個(gè):

  • log_all.log:存儲(chǔ)所有debug及以上級(jí)別的日志恰起;
  • log_error.log:存儲(chǔ)error級(jí)別的日志修械;
  • log_warn.log:存儲(chǔ)warn級(jí)別的日志;
  • log_info.log:存儲(chǔ)info級(jí)別的日志检盼;

3肯污、log4j2配置

由于spring-boot的所有starter的默認(rèn)日志框架為logback,所以在配置log4j2時(shí)吨枉,需要將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>

將所有會(huì)包含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>
        <!--這個(gè)輸出控制臺(tái)的配置-->
        <!--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簡(jiǎn)介

MDC 是 Mapped Diagnostic Context 的縮寫,“映射診斷上下文”看起來高大上的樣子书在,其實(shí)是非常簡(jiǎn)單的灰伟,就是一個(gè)臨時(shí)存放k-v對(duì)的容器。和普通Map的區(qū)別是它是基于ThreadLocal實(shí)現(xiàn)的蕊温,所以不存在資源競(jìng)爭(zhēng)問題袱箱,可以放心的往里面放東西。其主要用于打LOG時(shí)跟蹤一個(gè)“會(huì)話“义矛、一個(gè)”事務(wù)“发笔。舉例,有一個(gè)web controller凉翻,在同一時(shí)間可能收到來自多個(gè)客戶端的請(qǐng)求了讨,如果一個(gè)請(qǐng)求發(fā)生了錯(cuò)誤,我們要跟蹤這個(gè)請(qǐng)求從controller開始一步步都執(zhí)行到了哪些代碼、有哪些log的輸出前计。這時(shí)我們可以看log文件胞谭,但是log文件是多個(gè)請(qǐng)求同時(shí)記錄的,基本無法分辨哪行是哪個(gè)請(qǐng)求產(chǎn)生的男杈,雖然我們可以看線程丈屹,但線程可能被復(fù)用,也是很難分辨出伶棒,這時(shí)MDC就派上用場(chǎng)了旺垒。

4.2、主要api

public class MDC {
    public static void put(String key, String val) //設(shè)置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) //設(shè)置MDC的map
}
  • put()和setContextMap()主要進(jìn)行屬性設(shè)置肤无;
  • get()和getCopyOfContextMap()主要進(jìn)行屬性獲认冉;
  • remove()移除屬性信息宛渐,主要在線程結(jié)束時(shí)處理設(shè)置的屬性竞漾;

4.3、基于spring的HandlerInterceptorAdapter

此種方法主要適用場(chǎng)景為跟蹤http請(qǐng)求窥翩,通過攔截http請(qǐng)求业岁,設(shè)置MDC,并在日志中打印設(shè)置參數(shù)寇蚊,即可通過日志跟蹤請(qǐng)求鏈叨襟;

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);
    }
}

注冊(cè)攔截器:

@Component
public class MvcLoginInterceptor implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注冊(cè)攔截器
        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的打雍觥;

對(duì)于logback日志的配置同此處一樣爹梁;

4.3.2右犹、輸出結(jié)果

瀏覽器請(qǐng)求: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]

可以看到,開始系統(tǒng)啟動(dòng)過程中是無userName喝reqId打印的姚垃,當(dāng)進(jìn)行http請(qǐng)求時(shí)念链,MDC配置的兩個(gè)參數(shù)都打印出來,且reqId一直不變积糯,通過此id即可追蹤調(diào)用鏈掂墓;

4.4、基于AOP設(shè)置MDC

此種方法所有類型的調(diào)用請(qǐng)求的調(diào)用跟蹤看成,例如http君编、基于tcp的RPC調(diào)用等;

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參數(shù)與4.3.1配置相同祠乃;

4.4.3、輸出結(jié)果

瀏覽器請(qǐng)求: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

相關(guān)閱讀:
spring-boot基礎(chǔ)環(huán)境搭建 【http://www.reibang.com/p/ee36bb9faa10
spring-boot配置詳解【http://www.reibang.com/p/1d037ab638ef
spring-boot+druid+mybatis環(huán)境搭建【http://www.reibang.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.reibang.com/p/44640b298c45
https://blog.csdn.net/u012050154/article/details/77370297

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市降瞳,隨后出現(xiàn)的幾起案子嘱支,更是在濱河造成了極大的恐慌,老刑警劉巖挣饥,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斗塘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡亮靴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門于置,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茧吊,“玉大人,你說我怎么就攤上這事八毯〈曛叮” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵话速,是天一觀的道長(zhǎng)讶踪。 經(jīng)常有香客問我,道長(zhǎng)泊交,這世上最難降的妖魔是什么乳讥? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮廓俭,結(jié)果婚禮上云石,老公的妹妹穿的比我還像新娘。我一直安慰自己研乒,他們只是感情好汹忠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雹熬,像睡著了一般宽菜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竿报,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天铅乡,我揣著相機(jī)與錄音,去河邊找鬼烈菌。 笑死隆判,一個(gè)胖子當(dāng)著我的面吹牛犬庇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播侨嘀,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼臭挽,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了咬腕?” 一聲冷哼從身側(cè)響起欢峰,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涨共,沒想到半個(gè)月后纽帖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡举反,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年懊直,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片火鼻。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡室囊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出魁索,到底是詐尸還是另有隱情融撞,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布粗蔚,位于F島的核電站尝偎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鹏控。R本人自食惡果不足惜致扯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望当辐。 院中可真熱鬧急前,春花似錦、人聲如沸瀑构。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寺晌。三九已至世吨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呻征,已是汗流浹背耘婚。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陆赋,地道東北人沐祷。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓嚷闭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親赖临。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胞锰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容