Spring Boot 日志各種使用姿勢(shì)资厉,是時(shí)候捋清楚了厅缺!

@[toc]
之前錄過(guò)一個(gè)視頻和大家分享 Spring Boot 日志問(wèn)題,但是總感覺(jué)差點(diǎn)意思宴偿,因此松哥打算再通過(guò)一篇文章來(lái)和大家捋一捋 Java 中的日志問(wèn)題湘捎,順便我們把 Spring Boot 中的日志問(wèn)題也說(shuō)清楚。

1. Java 日志概覽

說(shuō)到 Java 日志窄刘,很多初學(xué)者可能都比較懵窥妇,因?yàn)檫@里涉及到太多東西了:Apache Commons LoggingSlf4j娩践、Log4j活翩、Log4j2Logback翻伺、Java Util Logging 等等材泄,這些框架各自有什么作用?他們之間有什么區(qū)別吨岭?

1.1 總體概覽

下面這張圖很好的展示了 Java 中的日志體系:

image

可以看到拉宗,Java 中的日志框架主要分為兩大類(lèi):日志門(mén)面日志實(shí)現(xiàn)

日志門(mén)面

日志門(mén)面定義了一組日志的接口規(guī)范辣辫,它并不提供底層具體的實(shí)現(xiàn)邏輯旦事。Apache Commons LoggingSlf4j 就屬于這一類(lèi)。

日志實(shí)現(xiàn)

日志實(shí)現(xiàn)則是日志具體的實(shí)現(xiàn)急灭,包括日志級(jí)別控制姐浮、日志打印格式、日志輸出形式(輸出到數(shù)據(jù)庫(kù)葬馋、輸出到文件卖鲤、輸出到控制臺(tái)等)肾扰。Log4jLog4j2蛋逾、Logback 以及 Java Util Logging 則屬于這一類(lèi)白对。

將日志門(mén)面和日志實(shí)現(xiàn)分離其實(shí)是一種典型的門(mén)面模式,這種方式可以讓具體業(yè)務(wù)在不同的日志實(shí)現(xiàn)框架之間自由切換换怖,而不需要改動(dòng)任何代碼甩恼,開(kāi)發(fā)者只需要掌握日志門(mén)面的 API 即可。

日志門(mén)面是不能單獨(dú)使用的沉颂,它必須和一種具體的日志實(shí)現(xiàn)框架相結(jié)合使用条摸。

那么日志框架是否可以單獨(dú)使用呢?

技術(shù)上來(lái)說(shuō)當(dāng)然沒(méi)問(wèn)題铸屉,但是我們一般不會(huì)這樣做钉蒲,因?yàn)檫@樣做可維護(hù)性很差,而且后期擴(kuò)展不易彻坛。例如 A 開(kāi)發(fā)了一個(gè)工具包使用 Log4j 打印日志顷啼,B 引用了這個(gè)工具包,但是 B 喜歡使用 Logback 打印日志昌屉,此時(shí)就會(huì)出現(xiàn)一個(gè)業(yè)務(wù)使用兩個(gè)甚至多個(gè)日志框架钙蒙,開(kāi)發(fā)者也需要維護(hù)多個(gè)日志的配置文件。因此我們都是用日志門(mén)面打印日志间驮。

1.2 日志級(jí)別

使用日志級(jí)別的好處在于躬厌,調(diào)整級(jí)別,就可以屏蔽掉很多調(diào)試相關(guān)的日志輸出竞帽。不同的日志實(shí)現(xiàn)定義的日志級(jí)別不太一樣扛施,不過(guò)也都大同小異。

Java Util Logging

Java Util Logging 定義了 7 個(gè)日志級(jí)別屹篓,從嚴(yán)重到普通依次是:

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

因?yàn)槟J(rèn)級(jí)別是 INFO疙渣,因此 INFO 級(jí)別以下的日志,不會(huì)被打印出來(lái)堆巧。

Log4j

Log4j 定義了 8 個(gè)日志級(jí)別(除去 OFF 和 ALL妄荔,可以說(shuō)分為 6 個(gè)級(jí)別),從嚴(yán)重到普通依次是:

  • OFF:最高等級(jí)的恳邀,用于關(guān)閉所有日志記錄懦冰。
  • FATAL:重大錯(cuò)誤灶轰,這種級(jí)別可以直接停止程序了谣沸。
  • ERROR:打印錯(cuò)誤和異常信息,如果不想輸出太多的日志笋颤,可以使用這個(gè)級(jí)別乳附。
  • WARN:警告提示内地。
  • INFO:用于生產(chǎn)環(huán)境中輸出程序運(yùn)行的一些重要信息,不能濫用赋除。
  • DEBUG:用于開(kāi)發(fā)過(guò)程中打印一些運(yùn)行信息阱缓。
  • TRACE
  • ALL 最低等級(jí)的,用于打開(kāi)所有日志記錄举农。

Logback

Logback 日志級(jí)別比較簡(jiǎn)單荆针,從嚴(yán)重到普通依次是:

  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE

1.3 綜合對(duì)比

Java Util Logging 系統(tǒng)在 JVM 啟動(dòng)時(shí)讀取配置文件并完成初始化,一旦應(yīng)用程序開(kāi)始運(yùn)行颁糟,就無(wú)法修改配置航背。另外,這種日志實(shí)現(xiàn)配置也不太方便棱貌,只能在 JVM 啟動(dòng)時(shí)傳遞參數(shù)玖媚,像下面這樣:

-Djava.util.logging.config.file=<config-file-name>。

由于這些局限性婚脱,導(dǎo)致 Java Util Logging 并未廣泛使用今魔。

Log4j 雖然配置繁瑣,但是一旦配置完成障贸,使用起來(lái)就非常方便错森,只需要將相關(guān)的配置文件放到 classpath 下即可。在很多情況下篮洁,Log4j 的配置文件我們可以在不同的項(xiàng)目中反復(fù)使用问词。

Log4j 可以和 Apache Commons Logging 搭配使用,Apache Commons Logging 會(huì)自動(dòng)搜索并使用 Log4j嘀粱,如果沒(méi)有找到 Log4j激挪,再使用 Java Util Logging

Log4j + Apache Commons Logging 組合更得人心的是 Slf4j + Logback 組合锋叨。

LogbackSlf4j 的原生實(shí)現(xiàn)框架垄分,它也出自 Log4j 作者(Ceki Gülcü)之手,但是相比 Log4j娃磺,它擁有更多的優(yōu)點(diǎn)薄湿、特性以及更強(qiáng)的性能。

1.4 最佳實(shí)踐

  • 如果不想添加任何依賴(lài)偷卧,使用 Java Util Logging 或框架容器已經(jīng)提供的日志接口豺瘤。
  • 如果比較在意性能,推薦:Slf4j + Logback听诸。
  • 如果項(xiàng)目中已經(jīng)使用了 Log4j 且沒(méi)有發(fā)現(xiàn)性能問(wèn)題坐求,推薦組合為:Slf4j + Log4j2

2. Spring Boot 日志實(shí)現(xiàn)

Spring Boot 使用 Apache Commons Logging 作為內(nèi)部的日志框架門(mén)面晌梨,它只是一個(gè)日志接口桥嗤,在實(shí)際應(yīng)用中需要為該接口來(lái)指定相應(yīng)的日志實(shí)現(xiàn)须妻。

Spring Boot 默認(rèn)的日志實(shí)現(xiàn)是 Logback。這個(gè)很好查看:隨便啟動(dòng)一個(gè) Spring Boot 項(xiàng)目泛领,從控制臺(tái)找一行日志荒吏,例如下面這樣:

image

考慮到最后的 prod 是一個(gè)可以變化的字符,我們?cè)陧?xiàng)目中全局搜索:The following profiles are active渊鞋,結(jié)果如下:

image

在日志輸出的那一行 debug绰更。然后再次啟動(dòng)項(xiàng)目,如下圖:

image

此時(shí)我們就可以看到真正的日志實(shí)現(xiàn)是 Logback锡宋。

其他的諸如 Java Util Logging动知、Log4j 等框架,Spring Boot 也有很好的支持员辩。

在 Spring Boot 項(xiàng)目中盒粮,只要添加了如下 web 依賴(lài),日志依賴(lài)就自動(dòng)添加進(jìn)來(lái)了:

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

2.1 Spring Boot 日志配置

Spring Boot 的日志系統(tǒng)會(huì)自動(dòng)根據(jù) classpath 下的內(nèi)容選擇合適的日志配置奠滑,在這個(gè)過(guò)程中首選 Logback丹皱。

如果開(kāi)發(fā)者需要修改日志級(jí)別,只需要在 application.properties 文件中通過(guò) logging.level 前綴+包名 的形式進(jìn)行配置即可宋税,例如下面這樣:

logging.level.org.springframework.web=debug
logging.level.org.hibernate=error

如果你想將日志輸出到文件摊崭,可以通過(guò)如下配置指定日志文件名:

logging.file.name=javaboy.log

logging.file.name 可以只指定日志文件名,也可以指定日志文件全路徑杰赛,例如下面這樣:

logging.file.name=/Users/sang/Documents/javaboy/javaboy.log

如果你只是想重新定義輸出日志文件的路徑呢簸,也可以使用 logging.file.path 屬性,如下:

logging.file.path=/Users/sang/Documents/javaboy

如果想對(duì)輸出到文件中的日志進(jìn)行精細(xì)化管理乏屯,還有如下一些屬性可以配置:

  • logging.logback.rollingpolicy.file-name-pattern:日志歸檔的文件名根时,日志文件達(dá)到一定大小之后,自動(dòng)進(jìn)行壓縮歸檔辰晕。
  • logging.logback.rollingpolicy.clean-history-on-start:是否在應(yīng)用啟動(dòng)時(shí)進(jìn)行歸檔管理蛤迎。
  • logging.logback.rollingpolicy.max-file-size:日志文件大小上限,達(dá)到該上限后含友,會(huì)自動(dòng)壓縮替裆。
  • logging.logback.rollingpolicy.total-size-cap:日志文件被刪除之前,可以容納的最大大小窘问。
  • logging.logback.rollingpolicy.max-history:日志文件保存的天數(shù)辆童。

日志文件歸檔這塊,小伙伴們感興趣可以自己試下惠赫,可以首先將 max-file-size 屬性調(diào)小把鉴,這樣方便看到效果:

logging.logback.rollingpolicy.max-file-size=1MB

然后添加如下接口:

@RestController
public class HelloController {
    private static final Logger logger = getLogger(HelloController.class);
    @GetMapping("/hello")
    public void hello() {
        for (int i = 0; i < 100000; i++) {
            logger.info("hello javaboy");
        }
    }
}

訪問(wèn)該接口,可以看到最終生成的日志文件被自動(dòng)壓縮了:

image

application.properties 中還可以配置日志分組汉形。

日志分組能夠把相關(guān)的 logger 放到一個(gè)組統(tǒng)一管理纸镊。

例如我們可以定義一個(gè) tomcat 組:

logging.group.tomcat=org.apache.catalina,org.apache.coyote, org.apache.tomcat

然后統(tǒng)一管理 tomcat 組中的所有 logger:

logging.level.tomcat=TRACE

Spring Boot 中還預(yù)定義了兩個(gè)日志分組 web 和 sql,如下:

image

不過(guò)在 application.properties 中只能實(shí)現(xiàn)對(duì)日志一些非常簡(jiǎn)單的配置概疆,如果想實(shí)現(xiàn)更加細(xì)粒度的日志配置逗威,那就需要使用日志實(shí)現(xiàn)的原生配置,例如 Logbackclasspath:logback.xml岔冀,Log4jclasspath:log4j.xml 等凯旭。如果這些日志配置文件存在于 classpath 下,那么默認(rèn)情況下使套,Spring Boot 就會(huì)自動(dòng)加載這些配置文件罐呼。

2.2 Logback 配置

2.2.1 基本配置

默認(rèn)的 Logback 配置文件名有兩種:

  • logback.xml:這種配置文件會(huì)直接被日志框架加載。
  • logback-spring.xml:這種配置文件不會(huì)被日志框架直接加載侦高,而是由 Spring Boot 去解析日志配置嫉柴,可以使用 Spring Boot 的高級(jí) Profile 功能。

Spring Boot 中為 Logback 提供了四個(gè)默認(rèn)的配置文件奉呛,位置在 org/springframework/boot/logging/logback/计螺,分別是:

  • defaults.xml:提供了公共的日志配置,日志輸出規(guī)則等瞧壮。
  • console-appender.xml:使用 CONSOLE_LOG_PATTERN 添加一個(gè)ConsoleAppender登馒。
  • file-appender.xml:添加一個(gè) RollingFileAppender。
  • base.xml:為了兼容舊版 Spring Boot 而提供的咆槽。

如果需要自定義 logback.xml 文件陈轿,可以在自定義時(shí)使用這些默認(rèn)的配置文件,也可以不使用秦忿。一個(gè)典型的 logback.xml 文件如下(resources/logback.xml):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
    <logger name="org.springframework.web" level="DEBUG"/>
</configuration>

可以通過(guò) include 引入 Spring Boot 已經(jīng)提供的配置文件麦射,也可以自定義。

2.2.2 輸出到文件

如果想禁止控制臺(tái)的日志輸出灯谣,轉(zhuǎn)而將日志內(nèi)容輸出到一個(gè)文件法褥,我們可以自定義一個(gè) logback-spring.xml 文件,并引入前面所說(shuō)的 file-appender.xml 文件酬屉。

像下面這樣(resources/logback-spring.xml):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
    <root level="INFO">
        <appender-ref ref="FILE" />
    </root>
</configuration>

2.3 Log4j 配置

如果 classpath 下存在 Log4j2 的依賴(lài)半等,Spring Boot 會(huì)自動(dòng)進(jìn)行配置。

默認(rèn)情況下 classpath 下當(dāng)然不存在 Log4j2 的依賴(lài)呐萨,如果想使用 Log4j2杀饵,可以排除已有的 Logback,然后再引入 Log4j2谬擦,如下:

<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-log4j2</artifactId>
</dependency>

Log4j2 的配置就比較容易了切距,在 reources 目錄下新建 log4j2.xml 文件,內(nèi)容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="warn">
    <properties>
        <Property name="app_name">logging</Property>
        <Property name="log_path">logs/${app_name}</Property>
    </properties>
    <appenders>
        <console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d][%t][%p][%l] %m%n" />
        </console>
        <RollingFile name="RollingFileInfo" fileName="${log_path}/info.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="INFO" />
                <ThresholdFilter level="WARN" onMatch="DENY"
                                 onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy size="2 MB" />
            </Policies>
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>
        <RollingFile name="RollingFileWarn" fileName="${log_path}/warn.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="WARN" />
                <ThresholdFilter level="ERROR" onMatch="DENY"
                                 onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy size="2 MB" />
            </Policies>
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>

        <RollingFile name="RollingFileError" fileName="${log_path}/error.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <ThresholdFilter level="ERROR" />
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy size="2 MB" />
            </Policies>
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>
    </appenders>
    <loggers>
        <root level="info">
            <appender-ref ref="Console" />
            <appender-ref ref="RollingFileInfo" />
            <appender-ref ref="RollingFileWarn" />
            <appender-ref ref="RollingFileError" />
        </root>
    </loggers>
</configuration>

首先在 properties 節(jié)點(diǎn)中指定了應(yīng)用名稱(chēng)以及日志文件位置惨远。

然后通過(guò)幾個(gè)不同的 RollingFile 對(duì)不同級(jí)別的日志分別處理谜悟,不同級(jí)別的日志將輸出到不同的文件话肖,并按照各自的命名方式進(jìn)行壓縮。

這段配置比較程式化葡幸,小伙伴們可以保存下來(lái)做成 IntelliJ IDEA 模版以便日常使用最筒。

3.小結(jié)

好啦,這就是松哥和小伙伴們分享的 Spring Boot 日志了蔚叨,整體來(lái)說(shuō)并不難床蜘,小伙伴們可以仔細(xì)品一品。

最后蔑水,松哥還搜集了 50+ 個(gè)項(xiàng)目需求文檔邢锯,想做個(gè)項(xiàng)目練練手的小伙伴不妨看看哦~

image

image

image

需求文檔地址:https://gitee.com/lenve/javadoc

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搀别,隨后出現(xiàn)的幾起案子丹擎,更是在濱河造成了極大的恐慌,老刑警劉巖歇父,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸥鹉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡庶骄,警方通過(guò)查閱死者的電腦和手機(jī)毁渗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)单刁,“玉大人灸异,你說(shuō)我怎么就攤上這事「岱桑” “怎么了肺樟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逻淌。 經(jīng)常有香客問(wèn)我么伯,道長(zhǎng),這世上最難降的妖魔是什么卡儒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任田柔,我火速辦了婚禮,結(jié)果婚禮上骨望,老公的妹妹穿的比我還像新娘硬爆。我一直安慰自己,他們只是感情好擎鸠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布缀磕。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪袜蚕。 梳的紋絲不亂的頭發(fā)上糟把,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音牲剃,去河邊找鬼遣疯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛颠黎,可吹牛的內(nèi)容都是我干的另锋。 我是一名探鬼主播滞项,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼狭归,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了文判?” 一聲冷哼從身側(cè)響起过椎,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎戏仓,沒(méi)想到半個(gè)月后疚宇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赏殃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年伴鳖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饶氏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖幢炸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薛躬,我是刑警寧澤亦鳞,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站迅矛,受9級(jí)特大地震影響妨猩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秽褒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一壶硅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧销斟,春花似錦森瘪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春窗宇,著一層夾襖步出監(jiān)牢的瞬間措伐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工军俊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侥加,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓粪躬,卻偏偏與公主長(zhǎng)得像担败,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镰官,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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