log4j——Log for java。
此文為讀log4j2 user guaid時的翻譯及筆記。log4j2與log4j在Logger的繼承關系和配置方式上都做出了修改。個人感覺比較有意思的是Logger對象與LoggerConfig解耦的設計,以及Filter中的傳遞機制,有點像網絡包分發,不過多了很多可調控性。
前言
log4j2可以按照開發人員預先的設定,在指定的位置和情況下打印log語句,並且可以酌情關閉某些log語句,如開發階段debug類型的語句等。並且,可以使用layout來定義輸出語句的格式,像C語言的printf函數一樣。如:
要實現這樣標准化的日志輸出,只需要在工程中引入log4j2的相關jar包,並向LogManager對象申請一個Logger對象的引用,然后調用該對象的相應方法即可,如:
在log4j2中,一共有五種log level,分別為TRACE, DEBUG,INFO, WARN, ERROR 以及FATAL。
- FATAL:用在極端的情形中,即必須馬上獲得注意的情況。這個程度的錯誤通常需要觸發運維工程師的尋呼機。
- ERROR:顯示一個錯誤,或一個通用的錯誤情況,但還不至於會將系統掛起。這種程度的錯誤一般會觸發郵件的發送,將消息發送到alert list中,運維人員可以在文檔中記錄這個bug並提交。
- WARN:不一定是一個bug,但是有人可能會想要知道這一情況。如果有人在讀log文件,他們通常會希望讀到系統出現的任何警告。
- INFO:用於基本的、高層次的診斷信息。在長時間運行的代碼段開始運行及結束運行時應該產生消息,以便知道現在系統在干什么。但是這樣的信息不宜太過頻繁。
- DEBUG:用於協助低層次的調試。
- TRACE:用於展現程序執行的軌跡。
Log4j2類圖
通過類圖可用看到:
每一個log上下文對應一個configuration,configuration中詳細描述了log系統的各個LoggerConfig、Appender(輸出目的地)、EventLog過濾器等。每一個Logger又與一個LoggerConfig相關聯。另外,可以看到Filter的種類很多,有聚合在Configuration中的filter、有聚合在LoggerConfig中的filter也有聚合在Appender中的filter。不同的filter在過濾LogEvent時的行為和判斷依據是不同的,具體可參加本文后面給出的文檔。
應用程序通過調用log4j2的API並傳入一個特定的名稱來向LogManager請求一個Logger實例。LogManager會定位到適當的 LoggerContext 然后通過它獲得一個Logger。如果LogManager不得不新建一個Logger,那么這個被新建的Logger將與LoggerConfig相關聯,這個LoggerConfig的名稱中包含如下信息中的一種:①與Logger名稱相同的②父logger的名稱③ root 。當一個LoggerConfig的名稱與一個Logger的名稱可以完全匹配時,Logger將會選擇這個LoggerConfig作為自己的配置。如果不能完全匹配,那么Logger將按照最長匹配串來選擇自己所對應的LoggerConfig。LoggerConfig對象是根據配置文件來創建的。LoggerConfig會與Appenders相關聯,Appenders用來決定一個log request將被打印到那個目的地中,可選的打印目的地很多,如console、文件、遠程socket server等。。LogEvent是由Appenders來實際傳遞到最終輸出目的地的,而在EvenLog到達最終被處理之前,還需要經過若干filter的過濾,用來判斷該EventLog應該在何處被轉發、何處被駁回、何處被執行。
Log4j中各個概念的簡要介紹
Logger間的層次關系
相比於純粹的System.out.println方式,使用logging API的最首要以及最重要的優勢是可以在禁用一些log語句塊的同時允許其他的語句塊的輸出。這一能力建立在一種假設之上,即所有在應用中可能出現的logging語句可以按照開發者定義的標准分成不同的類型。
在 Log4j 1.x版本時,Logger的層次是靠Logger類之間的關系來維護的。但在Log4j2中, Logger的層次則是靠LoggerConfig對象之間的關系來維護的。
Logger和LoggerConfig均是有名稱的實體。Logger的命名是大小寫敏感的,並且服從如下的分層命名規則。(與java包的層級關系類似)。例如:com.foo是com.foo.Bar的父級;java是java.util的父級,是java.util.vector的祖先。
root LoggerConfig位於LoggerConfig層級關系的最頂層。它將永遠存在與任何LoggerConfig層次中。任何一個希望與root LoggerConfig相關聯的Logger可以通過如下方式獲得:
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
其他的Logger實例可以調用LogManager.getLogger 靜態方法並傳入想要得到的Logger的名稱來獲得。
LoggerContext
LoggerContext在Logging System中扮演了錨點的角色。根據情況的不同,一個應用可能同時存在於多個有效的LoggerContext中。在同一LoggerContext下,log system是互通的。如:Standalone Application、Web Applications、Java EE Applications、"Shared" Web Applications 和REST Service Containers,就是不同廣度范圍的log上下文環境。
Configuration
每一個LoggerContext都有一個有效的Configuration。Configuration包含了所有的Appenders、上下文范圍內的過濾器、LoggerConfigs以及StrSubstitutor.的引用。在重配置期間,新與舊的Configuration將同時存在。當所有的Logger對象都被重定向到新的Configuration對象后,舊的Configuration對象將被停用和丟棄。
Logger
如前面所述, Loggers 是通過調用LogManager.getLogger方法獲得的。Logger對象本身並不實行任何實際的動作。它只是擁有一個name 以及與一個LoggerConfig相關聯。它繼承了AbstractLogger類並實現了所需的方法。當Configuration改變時,Logger將會與另外的LoggerConfig相關聯,從而改變這個Logger的行為。
獲得Logger:
使用相同的名稱參數來調用getLogger方法將獲得來自同一個Logger的引用。如:
Logger x = Logger.getLogger("wombat");
Logger y = Logger.getLogger("wombat");
x和y指向的是同一個Logger對象。
log4j環境的配置是在應用的啟動階段完成的。優先進行的方式是通過讀取配置文件來完成。
log4j使采用類名(包括完整路徑)來定義Logger 名變得很容易。這是一個很有用且很直接的Logger命名方式。使用這種方式命名可以很容易的定位這個log message產生的類的位置。當然,log4j也支持任意string的命名方式以滿足開發者的需要。不過,使用類名來定義Logger名仍然是最為推崇的一種Logger命名方式。
LoggerConfig
當Logger在configuration中被描述時,LoggerConfig對象將被創建。LoggerConfig包含了一組過濾器。LogEvent在被傳往Appender之前將先經過這些過濾器。過濾器中包含了一組Appender的引用。Appender則是用來處理這些LogEvent的。
Log層次:
每一個LoggerConfig會被指定一個Log層次。可用的Log層次包括TRACE, DEBUG,INFO, WARN, ERROR 以及FATAL。需要注意的是,在log4j2中,Log的層次是一個Enum型變量,是不能繼承或者修改的。如果希望獲得跟多的分割粒度,可用考慮使用Markers來替代。
在Log4j 1.x 和Logback 中都有“層次繼承”這么個概念。但是在log4j2中,由於Logger和LoggerConfig是兩種不同的對象,因此“層次繼承”的概念實現起來跟Log4j 1.x 和Logback不同。具體情況下面的五個例子。
例子一:
可用看到,應用中的LoggerConfig只有root這一種。因此,對於所有的Logger而言,都只能與該LoggerConfig相關聯而沒有別的選擇。
例子二:
在例子二中可以看到,有5種不同的LoggerConfig存在於應用中,而每一個Logger都被與最匹配的LoggerConfig相關聯着,並且擁有不同的Log Level。
例子三:
可以看到Logger root、X、X.Y.Z都找到了與各種名稱相同的LoggerConfig。而LoggerX.Y沒有與其名稱相完全相同的LoggerConfig。怎么辦呢?它最后選擇了X作為它的LoggerConfig,因為X LoggerConfig擁有與其最長的匹配度。
例子四:
可以看到,現在應用中有兩個配置好的LoggerConfig:root和X。而Logger有四個:root、X、X.Y、X.Y.Z。其中,root和X都能找到完全匹配的LoggerConfig,而X.Y和X.Y.Z則沒有完全匹配的LoggerConfig,那么它們將選擇哪個LoggerConfig作為自己的LoggerConfig呢?由圖上可知,它們都選擇了X而不是root作為自己的LoggerConfig,因為在名稱上,X擁有最長的匹配度。
例子五
可以看到,現在應用中有三個配置好的LoggerConfig,分別為:root、X、X.Y。同時,有四個Logger,分別為:root、X、X.Y以及X.YZ。其中,名字能完全匹配的是root、X、X.Y。那么剩下的X.YZ應該匹配X還是匹配X.Y呢?答案是X。因為匹配是按照標記點(即“.”)來進行的,只有兩個標記點之間的字串完全匹配才算,否則將取上一段完全匹配的字串的長度作為最終匹配長度。
Filter
與防火牆過濾的規則相似,log4j2的過濾器也將返回三類狀態:Accept(接受), Deny(拒絕) 或Neutral(中立)。其中,Accept意味着不用再調用其他過濾器了,這個LogEvent將被執行;Deny意味着馬上忽略這個event,並將此event的控制權交還給過濾器的調用者;Neutral則意味着這個event應該傳遞給別的過濾器,如果再沒有別的過濾器可以傳遞了,那么就由現在這個過濾器來處理。
Appender
由logger的不同來決定一個logging request是被禁用還是啟用只是log4j2的情景之一。log4j2還允許將logging request中log信息打印到不同的目的地中。在log4j2的世界里,不同的輸出位置被稱為Appender。目前,Appender可以是console、文件、遠程socket服務器、Apache Flume、JMS以及遠程 UNIX 系統日志守護進程。一個Logger可以綁定多個不同的Appender。
可以調用當前Configuration的addLoggerAppender函數來為一個Logger增加。如果不存在一個與Logger名稱相對應的LoggerConfig,那么相應的LoggerConfig將被創建,並且新增加的Appender將被添加到此新建的LoggerConfig中。爾后,所有的Loggers將會被通知更新自己的LoggerConfig引用(PS:一個Logger的LoggerConfig引用是根據名稱的匹配長度來決定的,當新的LoggerConfig被創建后,會引發一輪配對洗牌)。
在某一個Logger中被啟用的logging request將被轉發到該Logger相關聯的的所有Appenders上,並且還會被轉發到LoggerConfig的父級的Appenders上。
這樣會產生一連串的遺傳效應。例如,對LoggerConfig B來說,它的父級為A,A的父級為root。如果在root中定義了一個Appender為console,那么所有啟用了的logging request都會在console中打印出來。另外,如果LoggerConfig A定義了一個文件作為Appender,那么使用LoggerConfig A和LoggerConfig B的logger 的logging request都會在該文件中打印,並且同時在console中打印。
如果想避免這種遺傳效應的話,可以在configuration文件中做如下設置:
additivity="false"
這樣,就可以關閉Appender的遺傳效應了。具體解釋見:
Layout
通常,用戶不止希望能定義log輸出的位置,還希望可以定義輸出的格式。這就可以通過將Appender與一個layout相關聯來實現。Log4j中定義了一種類似C語言printf函數的打印格式,如"%r [%t] %-5p %c - %m%n" 格式在真實環境下會打印類似如下的信息:
176 [main] INFO org.foo.Bar - Located nearest gas station.
其中,各個字段的含義分別是:
%r 指的是程序運行至輸出這句話所經過的時間(以毫秒為單位);
%t 指的是發起這一log request的線程;
%c 指的是log的level;
%m 指的是log request語句攜帶的message。
%n 為換行符。
Reference