爲何使用logbackhtml
記得前幾年工做的時候,公司使用的日誌框架仍是log4j,大約從16年中到如今,不論是我參與的別人已經搭建好的項目仍是我本身主導的項目,日誌框架基本都換成了logback,總結一下,logback大約有如下的一些優勢:java
總而言之,若是你們的項目裏面須要選擇一個日誌框架,那麼我我的很是建議使用logback。數組
logback加載安全
咱們簡單分析一下logback加載過程,當咱們使用logback-classic.jar時,應用啓動,那麼logback會按照以下順序進行掃描:性能優化
以上任何一項找到了,就不進行後續掃描,按照對應的配置進行logback的初始化,具體代碼實現可見ch.qos.logback.classic.util.ContextInitializer類的findURLOfDefaultConfigurationFile方法。微信
當全部以上四項都找不到的狀況下,logback會調用ch.qos.logback.classic.BasicConfigurator的configure方法,構造一個ConsoleAppender用於向控制檯輸出日誌,默認日誌輸出格式爲"%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"。app
logback的configuration框架
logback的重點應當是Appender、Logger、Pattern,在這以前先簡單瞭解一下logback的<configuration>,<configuration>只有三個屬性:異步
<logger>與<root>函數
先從最基本的<logger>與<root>開始。
<logger>用來設置某一個包或者具體某一個類的日誌打印級別、以及指定<appender>。<logger>能夠包含零個或者多個<appender-ref>元素,標識這個appender將會添加到這個logger。<logger>僅有一個name屬性、一個可選的level屬性和一個可選的additivity屬性:
<root>也是<logger>元素,可是它是根logger,只有一個level屬性,由於它的name就是ROOT,關於這個地方,有朋友微信上問起,源碼在LoggerContext中:
1 public LoggerContext() { 2 super(); 3 this.loggerCache = new ConcurrentHashMap<String, Logger>(); 4 5 this.loggerContextRemoteView = new LoggerContextVO(this); 6 this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this); 7 this.root.setLevel(Level.DEBUG); 8 loggerCache.put(Logger.ROOT_LOGGER_NAME, root); 9 initEvaluatorMap(); 10 size = 1; 11 this.frameworkPackages = new ArrayList<String>(); 12 }
Logger的構造函數爲:
Logger(String name, Logger parent, LoggerContext loggerContext) { this.name = name; this.parent = parent; this.loggerContext = loggerContext; }
看到第一個參數就是Root的name,而這個Logger.ROOT_LOGGER_NAME的定義爲final public String ROOT_LOGGER_NAME = "ROOT",由此能夠看出<root>節點的name就是"ROOT"。
接着寫一段代碼來測試一下:
1 public class Slf4jTest { 2 3 @Test 4 public void testSlf4j() { 5 Logger logger = LoggerFactory.getLogger(Object.class); 6 logger.trace("=====trace====="); 7 logger.debug("=====debug====="); 8 logger.info("=====info====="); 9 logger.warn("=====warn====="); 10 logger.error("=====error====="); 11 } 12 13 }
logback.xml的配置爲:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <configuration scan="false" scanPeriod="60000" debug="false"> 3 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 4 <layout class="ch.qos.logback.classic.PatternLayout"> 5 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> 6 </layout> 7 </appender> 8 9 <root level="info"> 10 <appender-ref ref="STDOUT" /> 11 </root> 12 13 </configuration>
root將打印級別設置爲"info"級別,<appender>暫時無論,控制檯的輸出爲:
2018-03-26 22:57:48.779 [main] INFO java.lang.Object - =====info===== 2018-03-26 22:57:48.782 [main] WARN java.lang.Object - =====warn===== 2018-03-26 22:57:48.782 [main] ERROR java.lang.Object - =====error=====
logback.xml的意思是,當Test方法運行時,root節點將日誌級別大於等於info的交給已經配置好的名爲"STDOUT"的<appender>進行處理,"STDOUT"將信息打印到控制檯上。
接着理解一下<logger>節點的做用,logback.xml修改一下,加入一個只有name屬性的<logger>:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <configuration scan="false" scanPeriod="60000" debug="false"> 3 4 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 5 <layout class="ch.qos.logback.classic.PatternLayout"> 6 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> 7 </layout> 8 </appender> 9 10 <logger name="java" /> 11 12 <root level="debug"> 13 <appender-ref ref="STDOUT" /> 14 </root> 15 16 </configuration>
注意這個name表示的是LoggerFactory.getLogger(XXX.class),XXX的包路徑,包路徑越少越是父級,咱們測試代碼裏面是Object.class,即name="java"是name="java.lang"的父級,root是全部<logger>的父級。看一下輸出爲:
2018-03-27 23:02:02.963 [main] DEBUG java.lang.Object - =====debug===== 2018-03-27 23:02:02.965 [main] INFO java.lang.Object - =====info===== 2018-03-27 23:02:02.966 [main] WARN java.lang.Object - =====warn===== 2018-03-27 23:02:02.966 [main] ERROR java.lang.Object - =====error=====
出現這樣的結果是由於:
由此可知,<logger>的打印信息向<root>傳遞,<root>使用"STDOUT"這個<appender>打印出全部大於等於debug級別的日誌。觸類旁通,咱們將<logger>的additivity配置爲false,那麼控制檯應該不會打印出任何日誌,由於<logger>的打印信息不會向父級<root>傳遞且<logger>沒有配置任何<appender>,你們能夠本身試驗一下。
接着,咱們再配置一個<logger>:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <configuration scan="false" scanPeriod="60000" debug="false"> 3 4 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 5 <layout class="ch.qos.logback.classic.PatternLayout"> 6 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> 7 </layout> 8 </appender> 9 10 <logger name="java" additivity="false" /> 11 <logger name="java.lang" level="warn"> 12 <appender-ref ref="STDOUT" /> 13 </logger> 14 15 <root level="debug"> 16 <appender-ref ref="STDOUT" /> 17 </root> 18 19 </configuration>
若是讀懂了上面的例子,那麼這個例子應當很好理解:
由此分析,得出最終的打印結果爲:
2018-03-27 23:12:16.147 [main] WARN java.lang.Object - =====warn===== 2018-03-27 23:12:16.150 [main] ERROR java.lang.Object - =====error=====
觸類旁通,上面的name="java"這個<appender>能夠把additivity設置爲true試試看是什麼結果,若是對前面的分析理解的朋友應該很容易想到,有兩部分日誌輸出,一部分是日誌級別大於等於warn的、一部分是日誌級別大於等於debug的。
<appender>
接着看一下<appender>,<appender>是<configuration>的子節點,是負責寫日誌的組件。<appender>有兩個必要屬性name和class:
<appender>有好幾種,上面咱們演示過的是ConsoleAppender,ConsoleAppender的做用是將日誌輸出到控制檯,配置示例爲:
1 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 2 <encoder> 3 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> 4 </encoder> 5 </appender>
其中,encoder表示對參數進行格式化。咱們和上一部分的例子對比一下,發現這裏是有所區別的,上面使用了<layout>定義<pattern>,這裏使用了<encoder>定義<pattern>,簡單說一下:
關於<encoder>中的格式下一部分再說。接着咱們看一下FileAppender,FileAppender的做用是將日誌寫到文件中,配置示例爲:
1 <appender name="FILE" class="ch.qos.logback.core.FileAppender"> 2 <file>D:/123.log</file> 3 <append>true</append> 4 <encoder> 5 <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern> 6 </encoder> 7 </appender>
它的幾個節點爲:
接着來看一下RollingFileAppender,RollingFileAppender的做用是滾動記錄文件,先將日誌記錄到指定文件,當符合某個條件時再將日誌記錄到其餘文件,RollingFileAppender配置比較靈活,所以使用得更多,示例爲:
1 <appender name="ROLLING-FILE-1" class="ch.qos.logback.core.rolling.RollingFileAppender"> 2 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 3 <fileNamePattern>rolling-file-%d{yyyy-MM-dd}.log</fileNamePattern> 4 <maxHistory>30</maxHistory> 5 </rollingPolicy> 6 <encoder> 7 <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern> 8 </encoder> 9 </appender>
這種是僅僅指定了<rollingPolicy>的寫法,<rollingPolicy>的做用是當發生滾動時,定義RollingFileAppender的行爲,其中上面的TimeBasedRollingPolicy是最經常使用的滾動策略,它根據時間指定滾動策略,既負責滾動也負責觸發滾動,有如下節點:
向其餘還有SizeBasedTriggeringPolicy,用於按照文件大小進行滾動,能夠本身查閱一下資料。
異步寫日誌
日誌一般來講都以文件形式記錄到磁盤,例如使用<RollingFileAppender>,這樣的話一次寫日誌就會發生一次磁盤IO,這對於性能是一種損耗,所以更多的,對於每次請求必打的日誌(例如請求日誌,記錄請求API、參數、請求時間),咱們會採起異步寫日誌的方式而不讓這次寫日誌發生磁盤IO,阻塞線程從而形成沒必要要的性能損耗。(不要小看這個點,能夠網上查一下服務端性能優化的文章,只是由於將日誌改成異步寫,整個QPS就有了大幅的提升)。
接着咱們看下如何使用logback進行異步寫日誌配置:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <configuration scan="false" scanPeriod="60000" debug="false"> 3 4 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 5 <encoder> 6 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> 7 </encoder> 8 </appender> 9 10 <appender name="ROLLING-FILE-1" class="ch.qos.logback.core.rolling.RollingFileAppender"> 11 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 12 <fileNamePattern>D:/rolling-file-%d{yyyy-MM-dd}.log</fileNamePattern> 13 <maxHistory>30</maxHistory> 14 </rollingPolicy> 15 <encoder> 16 <pattern>%-4relative [%thread] %-5level %lo{35} - %msg%n</pattern> 17 </encoder> 18 </appender> 19 20 <!-- 異步輸出 --> 21 <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender"> 22 <!-- 不丟失日誌.默認的,若是隊列的80%已滿,則會丟棄TRACT、DEBUG、INFO級別的日誌 --> 23 <discardingThreshold>0</discardingThreshold> 24 <!-- 更改默認的隊列的深度,該值會影響性能.默認值爲256 --> 25 <queueSize>256</queueSize> 26 <!-- 添加附加的appender,最多隻能添加一個 --> 27 <appender-ref ref ="ROLLING-FILE-1"/> 28 </appender> 29 30 <logger name="java" additivity="false" /> 31 <logger name="java.lang" level="DEBUG"> 32 <appender-ref ref="ASYNC" /> 33 </logger> 34 35 <root level="INFO"> 36 <appender-ref ref="STDOUT" /> 37 </root> 38 39 </configuration>
即,咱們引入了一個AsyncAppender,先說一下AsyncAppender的原理,再說一下幾個參數:
當咱們配置了AsyncAppender,系統啓動時會初始化一條名爲"AsyncAppender-Worker-ASYNC"的線程
當Logging Event進入AsyncAppender後,AsyncAppender會調用appender方法,appender方法中再將event填入Buffer(使用的Buffer爲BlockingQueue,具體實現爲ArrayBlockingQueye)前,會先判斷當前Buffer的容量以及丟棄日誌特性是否開啓,當消費能力不如生產能力時,AsyncAppender會將超出Buffer容量的Logging Event的級別進行丟棄,做爲消費速度一旦跟不上生產速度致使Buffer溢出處理的一種方式。
上面的線程的做用,就是從Buffer中取出Event,交給對應的appender進行後面的日誌推送
從上面的描述咱們能夠看出,AsyncAppender並不處理日誌,只是將日誌緩衝到一個BlockingQueue裏面去,並在內部建立一個工做線程從隊列頭部獲取日誌,以後將獲取的日誌循環記錄到附加的其餘appender上去,從而達到不阻塞主線程的效果。所以AsyncAppender僅僅充當的是事件轉發器,必須引用另一個appender來作事。
從上述原理,咱們就能比較清晰地理解幾個參數的做用了:
<encoder>
<encoder>節點負責兩件事情:
目前PatternLayoutEncoder是惟一有用的且默認的encoder,有一個<pattern>節點,就像上面演示的,用來設置日誌的輸入格式,使用「%+轉換符"的方式,若是要輸出"%"則必須使用"\%"對"%"進行轉義。
<encoder>的一些可用參數用表格表示一下:
轉換符 | 做 用 | 是否避免使用 |
c{length} lo{length} logger{length} |
輸出日誌的logger名稱,可有一個整型參數來縮短<logger>名稱,有幾種狀況: 一、不輸入表示輸出完整的<logger>名稱 二、輸入0表示只輸出<logger>最右邊點號以後的字符串 三、輸入其餘數字表示輸出小數點最後邊點號以前的字符數量 |
否 |
C{length} class{length} |
輸出指定記錄的請求的調用者的全限定名,length同上 | 是 |
d{pattern} date{pattern} |
輸出時間格式,模式語法同java.text.SimpleDateFormat兼容 | 否 |
caller{depth} | 輸出生成日誌的調用者的位置信息,整數選項表示輸出信息深度 | 否 |
L | 輸出執行日誌的請求行號 | 是 |
m msg message |
輸出應用程序提供的信息 | 否 |
m | 輸入執行日誌請求的方法名 | 是 |
n | 輸出平臺相關的分行符"\n"或者"\r\n",即換行 | 否 |
p le level |
輸出日誌級別 | 否 |
r relative |
輸出從程序啓動到建立日誌記錄的時間,單位爲毫秒 | 否 |
t thread |
輸出產生日誌的線程名稱 | 否 |
看到最後一列是"是否避免使用",這是由於這些信息是沒法直接拿到的(好比請求行號、調用方法名),logback必須經過一些特殊手段去獲取這些數據(好比在日誌打印出產生一個堆棧信息),這種操做會比較影響效率,所以除非必要,不然不建議打印這些數據。
Filter
最後來看一下<filter>,<filter>是<appender>的一個子節點,表示在當前給到的日誌級別下再進行一次過濾,最基本的Filter有ch.qos.logback.classic.filter.LevelFilter和ch.qos.logback.classic.filter.ThresholdFilter,首先看一下LevelFilter:
1 <configuration scan="false" scanPeriod="60000" debug="false"> 2 3 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 4 <encoder> 5 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> 6 </encoder> 7 <filter class="ch.qos.logback.classic.filter.LevelFilter"> 8 <level>WARN</level> 9 <onMatch>ACCEPT</onMatch> 10 <onMismatch>DENY</onMismatch> 11 </filter> 12 </appender> 13 14 <logger name="java" additivity="false" /> 15 <logger name="java.lang" level="DEBUG"> 16 <appender-ref ref="STDOUT" /> 17 </logger> 18 19 <root level="INFO"> 20 <appender-ref ref="STDOUT" /> 21 </root> 22 23 </configuration>
看一下輸出:
2018-03-31 22:22:58.843 [main] WARN java.lang.Object - =====warn=====
看到儘管<logger>配置的是DEBUG,可是輸出的只有warn,由於在<filter>中對匹配到WARN級別時作了ACCEPT(接受),對未匹配到WARN級別時作了DENY(拒絕),固然只能打印出WARN級別的日誌。
再看一下ThresholdFilter,配置爲:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <configuration scan="false" scanPeriod="60000" debug="false"> 3 4 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 5 <encoder> 6 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> 7 </encoder> 8 <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> 9 <level>INFO</level> 10 </filter> 11 </appender> 12 13 <logger name="java" additivity="false" /> 14 <logger name="java.lang" level="DEBUG"> 15 <appender-ref ref="STDOUT" /> 16 </logger> 17 18 <root level="INFO"> 19 <appender-ref ref="STDOUT" /> 20 </root> 21 22 </configuration>
看一下輸出爲:
2018-03-31 22:41:32.353 [main] INFO java.lang.Object - =====info===== 2018-03-31 22:41:32.358 [main] WARN java.lang.Object - =====warn===== 2018-03-31 22:41:32.359 [main] ERROR java.lang.Object - =====error=====
由於ThresholdFilter的策略是,會將日誌級別小於<level>的所有進行過濾,所以雖然指定了DEBUG級別,可是隻有INFO及以上級別的才能被打印出來。
原文連接:https://www.cnblogs.com/xrq730/p/8628945.html