1. 程式人生 > 實用技巧 >Log4j2架構及概念簡介

Log4j2架構及概念簡介

>>> hot3.png

此文為讀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:顯示一個錯誤,或一個通用的錯誤情況,但還不至於會將系統掛起。這種程度的錯誤一般會觸發郵件的傳送,將訊息傳送到alertlist中,運維人員可以在文件中記錄這個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時的行為和判斷依據是不同的,具體可參加本文後面給出的文件。

應用程式通過呼叫log4j2API並傳入一個特定的名稱來向LogManager請求一個Logger例項。LogManager會定位到適當的LoggerContext然後通過它獲得一個Logger。如果LogManager不得不新建一個Logger,那麼這個被新建的Logger將與LoggerConfig相關聯,這個LoggerConfig的名稱中包含如下資訊中的一種:①與Logger名稱相同的②父logger的名稱③root。當一個LoggerConfig的名稱與一個Logger的名稱可以完全匹配時,Logger將會選擇這個LoggerConfig作為自己的配置。如果不能完全匹配,那麼Logger將按照最長匹配串來選擇自己所對應的LoggerConfigLoggerConfig物件是根據配置檔案來建立的。LoggerConfig會與Appenders相關聯,Appenders用來決定一個log request將被列印到那個目的地中,可選的列印目的地很多,如console、檔案、遠端socket server等。。LogEvent是由Appenders來實際傳遞到最終輸出目的地的,而在EvenLog到達最終被處理之前,還需要經過若干filter的過濾,用來判斷該EventLog應該在何處被轉發、何處被駁回、何處被執行。

Log4j中各個概念的簡要介紹

Logger間的層次關係

相比於純粹的System.out.println方式,使用loggingAPI的最首要以及最重要的優勢是可以在禁用一些log語句塊的同時允許其他的語句塊的輸出。這一能力建立在一種假設之上,即所有在應用中可能出現的logging語句可以按照開發者定義的標準分成不同的型別。

Log4j1.x版本時,Logger的層次是靠Logger類之間的關係來維護的。但在Log4j2中,Logger的層次則是靠LoggerConfig物件之間的關係來維護的。

LoggerLoggerConfig均是有名稱的實體。Logger的命名是大小寫敏感的,並且服從如下的分層命名規則。(與java包的層級關係類似)。例如:com.foocom.foo.Bar的父級;javajava.util的父級,是java.util.vector的祖先。

rootLoggerConfig位於LoggerConfig層級關係的最頂層。它將永遠存在與任何LoggerConfig層次中。任何一個希望與rootLoggerConfig相關聯的Logger可以通過如下方式獲得:

Loggerlogger=LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

其他的Logger例項可以呼叫LogManager.getLogger靜態方法並傳入想要得到的Logger的名稱來獲得。

LoggerContext

LoggerContextLoggingSystem中扮演了錨點的角色。根據情況的不同,一個應用可能同時存在於多個有效的LoggerContext中。在同一LoggerContext下,logsystem是互通的。如:StandaloneApplicationWebApplicationsJavaEEApplications"Shared"WebApplicationsRESTServiceContainers,就是不同廣度範圍的log上下文環境。

Configuration

每一個LoggerContext都有一個有效的ConfigurationConfiguration包含了所有的Appenders、上下文範圍內的過濾器、LoggerConfigs以及StrSubstitutor.的引用。在重配置期間,新與舊的Configuration將同時存在。當所有的Logger物件都被重定向到新的Configuration物件後,舊的Configuration物件將被停用和丟棄。

Logger

如前面所述,Loggers是通過呼叫LogManager.getLogger方法獲得的。Logger物件本身並不實行任何實際的動作。它只是擁有一個name以及與一個LoggerConfig相關聯。它繼承了AbstractLogger類並實現了所需的方法。當Configuration改變時,Logger將會與另外的LoggerConfig相關聯,從而改變這個Logger的行為。

獲得Logger

使用相同的名稱引數來呼叫getLogger方法將獲得來自同一個Logger的引用。如:

Loggerx=Logger.getLogger("wombat");

Loggery=Logger.getLogger("wombat");

xy指向的是同一個Logger物件。

log4j環境的配置是在應用的啟動階段完成的。優先進行的方式是通過讀取配置檔案來完成。

log4j使採用類名(包括完整路徑)來定義Logger名變得很容易。這是一個很有用且很直接的Logger命名方式。使用這種方式命名可以很容易的定位這個logmessage產生的類的位置。當然,log4j也支援任意string的命名方式以滿足開發者的需要。不過,使用類名來定義Logger名仍然是最為推崇的一種Logger命名方式。

LoggerConfig

Loggerconfiguration中被描述時,LoggerConfig物件將被建立。LoggerConfig包含了一組過濾器。LogEvent在被傳往Appender之前將先經過這些過濾器。過濾器中包含了一組Appender的引用。Appender則是用來處理這些LogEvent的。

Log層次:

每一個LoggerConfig會被指定一個Log層次。可用的Log層次包括TRACE,DEBUG,INFO,WARN,ERROR以及FATAL。需要注意的是,在log4j2中,Log的層次是一個Enum型變數,是不能繼承或者修改的。如果希望獲得跟多的分割粒度,可用考慮使用Markers來替代。

Log4j1.xLogback中都有“層次繼承”這麼個概念。但是在log4j2中,由於LoggerLoggerConfig是兩種不同的物件,因此“層次繼承”的概念實現起來跟Log4j1.xLogback不同。具體情況下面的五個例子。

例子一:

可用看到,應用中的LoggerConfig只有root這一種。因此,對於所有的Logger而言,都只能與該LoggerConfig相關聯而沒有別的選擇。

例子二:

在例子二中可以看到,有5種不同的LoggerConfig存在於應用中,而每一個Logger都被與最匹配的LoggerConfig相關聯著,並且擁有不同的LogLevel

例子三:

可以看到LoggerrootXX.Y.Z都找到了與各種名稱相同的LoggerConfig。而LoggerX.Y沒有與其名稱相完全相同的LoggerConfig。怎麼辦呢?它最後選擇了X作為它的LoggerConfig,因為XLoggerConfig擁有與其最長的匹配度。

例子四:

可以看到,現在應用中有兩個配置好的LoggerConfigrootX。而Logger有四個:rootXX.YX.Y.Z。其中,rootX都能找到完全匹配的LoggerConfig,而X.YX.Y.Z則沒有完全匹配的LoggerConfig,那麼它們將選擇哪個LoggerConfig作為自己的LoggerConfig呢?由圖上可知,它們都選擇了X而不是root作為自己的LoggerConfig,因為在名稱上,X擁有最長的匹配度。

例子五

可以看到,現在應用中有三個配置好的LoggerConfig,分別為:rootXX.Y。同時,有四個Logger,分別為:rootXX.Y以及X.YZ。其中,名字能完全匹配的是rootXX.Y。那麼剩下的X.YZ應該匹配X還是匹配X.Y呢?答案是X。因為匹配是按照標記點(即“.”)來進行的,只有兩個標記點之間的字串完全匹配才算,否則將取上一段完全匹配的字串的長度作為最終匹配長度。

Filter

與防火牆過濾的規則相似,log4j2的過濾器也將返回三類狀態:Accept(接受),Deny(拒絕)或Neutral(中立)。其中,Accept意味著不用再呼叫其他過濾器了,這個LogEvent將被執行;Deny意味著馬上忽略這個event,並將此event的控制權交還給過濾器的呼叫者;Neutral則意味著這個event應該傳遞給別的過濾器,如果再沒有別的過濾器可以傳遞了,那麼就由現在這個過濾器來處理。

Appender

logger的不同來決定一個loggingrequest是被禁用還是啟用只是log4j2的情景之一。log4j2還允許將loggingrequestlog資訊列印到不同的目的地中。在log4j2的世界裡,不同的輸出位置被稱為Appender。目前,Appender可以是console、檔案、遠端socket伺服器、ApacheFlumeJMS以及遠端UNIX系統日誌守護程序。一個Logger可以繫結多個不同的Appender

可以呼叫當前ConfigurationaddLoggerAppender函式來為一個Logger增加。如果不存在一個與Logger名稱相對應的LoggerConfig,那麼相應的LoggerConfig將被建立,並且新增加的Appender將被新增到此新建的LoggerConfig中。爾後,所有的Loggers將會被通知更新自己的LoggerConfig引用(PS:一個LoggerLoggerConfig引用是根據名稱的匹配長度來決定的,當新的LoggerConfig被建立後,會引發一輪配對洗牌)。

在某一個Logger中被啟用的loggingrequest將被轉發到該Logger相關聯的的所有Appenders上,並且還會被轉發到LoggerConfig的父級的Appenders上。

這樣會產生一連串的遺傳效應。例如,對LoggerConfigB來說,它的父級為AA的父級為root。如果在root中定義了一個Appenderconsole,那麼所有啟用了的loggingrequest都會在console中打印出來。另外,如果LoggerConfigA定義了一個檔案作為Appender,那麼使用LoggerConfigALoggerConfigBloggerloggingrequest都會在該檔案中列印,並且同時在console中列印。

如果想避免這種遺傳效應的話,可以在configuration檔案中做如下設定:

additivity="false"

這樣,就可以關閉Appender的遺傳效應了。具體解釋見:

Layout

通常,使用者不止希望能定義log輸出的位置,還希望可以定義輸出的格式。這就可以通過將Appender與一個layout相關聯來實現。Log4j中定義了一種類似C語言printf函式的列印格式,如"%r[%t]%-5p%c-%m%n"格式在真實環境下會列印類似如下的資訊:

176[main]INFOorg.foo.Bar-Locatednearestgasstation.

其中,各個欄位的含義分別是:

%r指的是程式執行至輸出這句話所經過的時間(以毫秒為單位);

%t指的是發起這一logrequest的執行緒;

%c指的是loglevel

%m指的是logrequest語句攜帶的message

%n為換行符。


轉載於:https://my.oschina.net/piorcn/blog/504296