通過閱讀本篇文章將了解到 配置文件logback-spring.xml 部分標(biāo)簽說明 logback 高級(jí)特性異步輸出日志 異步輸出日志性能測(cè)試 異步日志輸出原理 源碼
受臺(tái)風(fēng)利奇馬的影響很多地方都迎來了強(qiáng)降雨,雙休日不能出去玩一起學(xué)習(xí)吧,就先從最基本的配置講起再介紹高級(jí)特性異步輸出日志。如果只想看異步輸出日志提升性能的部分請(qǐng)將文章往下拉一點(diǎn)。 通過閱讀本篇文章將了解到日志輸出到文件并根據(jù)LEVEL級(jí)別將日志分類保存到不同文件 通過異步輸出日志減少磁盤IO提高性能 異步輸出日志的原理
配置文件logback-spring.xmlSpringBoot工程自帶logback和slf4j的依賴,所以重點(diǎn)放在編寫配置文件上,需要引入什么依賴,日志依賴沖突統(tǒng)統(tǒng)都不需要我們管了。 logback框架會(huì)默認(rèn)加載classpath下命名為logback-spring或logback的配置文件。將所有日志都存儲(chǔ)在一個(gè)文件中文件大小也隨著應(yīng)用的運(yùn)行越來越大并且不好排查問題,正確的做法應(yīng)該是將error日志和其他日志分開,并且不同級(jí)別的日志根據(jù)時(shí)間段進(jìn)行記錄存儲(chǔ)。 <?xml version='1.0' encoding='utf-8'?> <configuration> <property resource='logback.properties'/> <appender name='CONSOLE-LOG' class='ch.qos.logback.core.ConsoleAppender'> <layout class='ch.qos.logback.classic.PatternLayout'> <pattern>[%d{yyyy-MM-dd' 'HH:mm:ss.sss}] [%C] [%t] [%L] [%-5p] %m%n</pattern> </layout> </appender> <!--獲取比info級(jí)別高(包括info級(jí)別)但除error級(jí)別的日志--> <appender name='INFO-LOG' class='ch.qos.logback.core.rolling.RollingFileAppender'> <filter class='ch.qos.logback.classic.filter.LevelFilter'> <level>ERROR</level> <onMatch>DENY</onMatch> <onMismatch>ACCEPT</onMismatch> </filter> <encoder> <pattern>[%d{yyyy-MM-dd' 'HH:mm:ss.sss}] [%C] [%t] [%L] [%-5p] %m%n</pattern> </encoder>
<!--滾動(dòng)策略--> <rollingPolicy class='ch.qos.logback.core.rolling.TimeBasedRollingPolicy'> <!--路徑--> <fileNamePattern>${LOG_INFO_HOME}//%d.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> </appender> <appender name='ERROR-LOG' class='ch.qos.logback.core.rolling.RollingFileAppender'> <filter class='ch.qos.logback.classic.filter.ThresholdFilter'> <level>ERROR</level> </filter> <encoder> <pattern>[%d{yyyy-MM-dd' 'HH:mm:ss.sss}] [%C] [%t] [%L] [%-5p] %m%n</pattern> </encoder> <!--滾動(dòng)策略--> <rollingPolicy class='ch.qos.logback.core.rolling.TimeBasedRollingPolicy'> <!--路徑--> <fileNamePattern>${LOG_ERROR_HOME}//%d.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> </appender>
<root level='info'> <appender-ref ref='CONSOLE-LOG' /> <appender-ref ref='INFO-LOG' /> <appender-ref ref='ERROR-LOG' /> </root> </configuration>
部分標(biāo)簽說明<root> 標(biāo)簽,必填標(biāo)簽,用來指定最基礎(chǔ)的日志輸出級(jí)別
<append> 標(biāo)簽,通過使用該標(biāo)簽指定日志的收集策略
<filter> 標(biāo)簽,通過使用該標(biāo)簽指定過濾策略
<encoder> 標(biāo)簽,使用該標(biāo)簽下的標(biāo)簽指定日志輸出格式
<rollingPolicy> 標(biāo)簽指定收集策略,比如基于時(shí)間進(jìn)行收集
 logback 高級(jí)特性異步輸出日志之前的日志配置方式是基于同步的,每次日志輸出到文件都會(huì)進(jìn)行一次磁盤IO。采用異步寫日志的方式而不讓此次寫日志發(fā)生磁盤IO,阻塞線程從而造成不必要的性能損耗。異步輸出日志的方式很簡(jiǎn)單,添加一個(gè)基于異步寫日志的appender,并指向原先配置的appender即可 <!-- 異步輸出 --> <appender name='ASYNC-INFO' class='ch.qos.logback.classic.AsyncAppender'> <!-- 不丟失日志.默認(rèn)的,如果隊(duì)列的80%已滿,則會(huì)丟棄TRACT、DEBUG、INFO級(jí)別的日志 --> <discardingThreshold>0</discardingThreshold> <!-- 更改默認(rèn)的隊(duì)列的深度,該值會(huì)影響性能.默認(rèn)值為256 --> <queueSize>256</queueSize> <!-- 添加附加的appender,最多只能添加一個(gè) --> <appender-ref ref='INFO-LOG'/> </appender>
<appender name='ASYNC-ERROR' class='ch.qos.logback.classic.AsyncAppender'> <!-- 不丟失日志.默認(rèn)的,如果隊(duì)列的80%已滿,則會(huì)丟棄TRACT、DEBUG、INFO級(jí)別的日志 --> <discardingThreshold>0</discardingThreshold> <!-- 更改默認(rèn)的隊(duì)列的深度,該值會(huì)影響性能.默認(rèn)值為256 --> <queueSize>256</queueSize> <!-- 添加附加的appender,最多只能添加一個(gè) --> <appender-ref ref='ERROR-LOG'/> </appender>
異步輸出日志性能測(cè)試既然能提高性能的話,必須進(jìn)行一次測(cè)試比對(duì),同步和異步輸出日志性能到底能提升多少倍? 服務(wù)器硬件 測(cè)試工具 同步輸出日志 測(cè)試結(jié)果  重點(diǎn)關(guān)注指標(biāo)Throughput【TPS】吞吐量:系統(tǒng)在單位時(shí)間內(nèi)處理請(qǐng)求的數(shù)量,在同步輸出日志中TPS為44.2/sec 異步輸出日志 線程數(shù) 100 Ramp-Up Loop:0
測(cè)試結(jié)果  TPS為497.5/sec,性能提升了10多倍?。。?/p> 異步日志輸出原理從logback框架下的Logger.info方法開始追蹤。一路的方法調(diào)用路徑如下圖所示:  異步輸出日志中最關(guān)鍵的就是配置文件中ch.qos.logback.classic包下AsyncAppenderBase類中的append方法,查看該方法的源碼: protected void append(E eventObject) { if(!this.isQueueBelowDiscardingThreshold() || !this.isDiscardable(eventObject)) { this.preprocess(eventObject); this.put(eventObject); } }
通過隊(duì)列情況判斷是否需要丟棄日志,不丟棄的話將它放到阻塞隊(duì)列中,通過查看代碼,這個(gè)阻塞隊(duì)列為ArrayBlockingQueueu,默認(rèn)大小為256,可以通過配置文件進(jìn)行修改。 Logger.info(…)到append(…)就結(jié)束了,只做了將日志塞入到阻塞隊(duì)列的事,然后繼續(xù)執(zhí)行Logger.info(…)下面的語句了。 在AsyncAppenderBase類中定義了一個(gè)Worker線程,run方法中的關(guān)鍵部分代碼如下: E e = parent.blockingQueue.take(); aai.appendLoopOnAppenders(e);
最主要的兩個(gè)方法就是encode和write方法,前一個(gè)法方會(huì)根據(jù)配置文件中encode指定的方式轉(zhuǎn)化為字節(jié)碼,后一個(gè)方法將轉(zhuǎn)化成的字節(jié)碼寫入到文件中去。所以寫文件是通過新起一個(gè)線程去完成的,主線程將日志扔到阻塞隊(duì)列中,然后又去做其他事情了。
|