Log4j2官方架構文件翻譯
官網原文標題《Architecture》
翻譯時間:2017-11-14
譯者:本文介紹了log4j的主要構成元件和核心概念,並就每個元件分別進行了講解。尤其需要讀者重點理解的是log level的繼承概念,以及appender的additivity屬性。仔細理解本片後,可繼續學習配置的文章。
後續閱讀:《Configuration》(還未更新)
架構
主要元件
log4j使用下圖中所展示的class
使用Log4j2 API的程式需要向LogManager請求一個指定名稱的Logger。LogManager會定位到合適的LoggerContext,然後從它獲取logger。如果必須去建立logger,這會關聯到包含下面因素的LoggerConfig a)同樣名稱的logger,b)父包的名稱 或者c)root LoggerConfig。LoggerConfig物件通過配置中的Logger宣告來建立。LoggerConfig會關聯上appender,實際上由他負責傳遞LogEvent。
Logger的層級
任何超越System.out.println的日誌API,最首要的優勢就是它有能力使某些指定的日誌程式碼片段失效,同時允許其他的正常列印。這種能力假定日誌的空間,所有可能的日誌片段的空間,基於開發者給出的條件進行分類。
在Log4j 1.x中,Logger 層級通過Logger之間的關係進行維護。在Log4j2中,這種關係不復存在。與之代替的,層級結構圍護在LoggerConfig物件之間。
Logger和LoggerConfig是一組被命名的實體。Logger的命名是大小寫敏感的,並且遵守層級命名規範:
命名層級
一個LoggerConfig可以被稱為另外一個LoggerConfig的祖先,如果他的名字後面跟了一個點作為子孫logger名稱的字首。一個LoggerConfig可以稱為孩子LoggerConfig的父親,如果在他們之間沒有任何祖先。
例如,一個loggerConfig稱為"com.foo",是"com.foo.bar"這個logger的父親。相似的,"java"是"java.util"的父親,是"java.util.Vector"的祖先。這種命名的方式應該對於絕大多數開發者是很熟悉的。
root loggerConfig存在於LoggerConfig層級的頂層。它作為一個例外,它一直存在並且它是任何層級的一部分。直接關聯到root LoggerConfig上的logger,可以按如下方式得到:
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
其實,還可以更為簡單:
Logger logger = LogManager.getRootLogger();
所有其他的Logger也可以通過傳遞Logger名稱給LogManager.getLogger靜態方法被查到。更多關於Logging API的資訊可以在訪問Log4j 2 API.
LoggerContext
LoggerContext扮演著日誌系統中的錨點角色。其實,我們可能在程式中有多個啟用的LoggerContext,這取決於環境。關於LoggerContext更多的細節,在Log Separation 章節。
Configuration
每個LoggerContext都有一個啟用的Configuration。這個configuration含有所有的Appender,context-wide Filter,LoggerConfig,並且含有StrSubstitutor的引用。在重新配置的過程中,會存在兩個Configuration物件。一旦所有的Logger都重新關聯到新的配置上,舊的configuraion就會被停掉並被拋棄。
Logger
像之前所說,Logger通過呼叫LogManager.getLogger來建立。Logger本身並不會執行任何直接的動作。他只是有個名稱並且關聯到LoggerConfig上。它繼承於AbstractLogger ,實現了必要的方法。在當配置發生變化時,Logger可能被關聯到另外的loggerConfig上。這會使得他們的行為被修改。
Retrieving Logger
通過傳入同樣名稱,呼叫LogManager.getLogger,將會返回給你同樣的Logger物件引用。
例如:
Logger x = LogManager.getLogger("wombat");
Logger y = LogManager.getLogger("wombat");
X和y關聯到同一個logger物件上。
Log4j環境的配置一般會在程式初始化的時候完成。最佳的方式是讀取配置檔案。這些在 Configuration中討論。
Log4j使得通過軟體元件命名logger變得很容易。我們可以在class中通過初始化Logger來完成,使用class的全路徑名稱作為logger的名稱。這是很有用,並且直接的定義logger的方式。當log輸出產生日誌的logger名稱時,這種命名策略使得識別log訊息的來源變得很簡單。然而,這只是一個可能的,普通的,命名logger的策略。Log4j並不會限制,開發者可以自由命名。
用自己class的名稱來命名logger是通用的習慣,所以LogManager.getLogger()非常的方便,可以自動使用class名稱作為Logger的名稱。
雖然如此,以logger所在的class來命名logger,看起來還是目前最好的策略。
LoggerConfig
日誌配置中宣告Logger的時候,LoggerConfig物件被建立。LoggerConfig包含了一組Filter,在LogEvent傳遞給任何的appender之前,他必須允許LogEvent通過。他含有一組用於處理event的appender引用。
Log Level
LoggerConfig會被指定Log Level。內建的Level包含 TRACE,DEBUG,INFO,WARN,ERROR,及FATAL。Log4j 2也支援客製化log level。另外一種得到更細粒度的機制是使用Markers 代替。
Log4j 1.x及Logback都有"Level繼承" 的概念,在Log4j 2中,Logger及LoggerConfig是兩個不同的物件。所以這個概念實現的不太一樣。每個Logger關聯到適合的LoggerConfig上,LoggerConfig可以反過來關聯到他的父親上,這可以達到同樣的效果。
下面五張表中,展示了很多給定的level值,以及會造成的關聯到每個Logger上的level。注意下面所有的情況,如果root LoggerConfig沒有被配置,預設的層級將會被指定給它。
Logger 名稱 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | root | DEBUG | DEBUG |
X.Y | root | DEBUG | DEBUG |
X.Y.Z | root | DEBUG | DEBUG |
在上面的例1中,只有root logger被配置了,並且有log level。所有其他的Logger關聯到root LoggerConfig上,並且使用它的level
Logger 名稱 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X.Y | INFO | INFO |
X.Y.Z | X.Y.Z | WARN | WARN |
例2中,所有的logge都有配置的LoggerConfig,並且從它那裡獲取level
Logger 名稱 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X | ERROR | ERROR |
X.Y.Z | X.Y.Z | WARN | WARN |
例3中,root、X、X.Y.Z分別有配置的同名LoggerConfig。X.Y Logger沒有配置匹配名稱的LoggerConfig,所以使用了X這個LoggerConfig,因為這個LoggerConfig的名稱最長匹配了Logger的名稱的開始。
Logger 名稱 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X | ERROR | ERROR |
X.Y.Z | X | ERROR | ERROR |
例4中,root和X logger都有自己配置的同名LoggerConfig。X.Y、X.Y.Z沒有配置的LoggerConfig,所以她們的level來自於分配給它們的X這個LoggerConfig。因為他的名稱最長匹配了logger名稱的開始
Logger 名稱 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X.Y | INFO | INFO |
X.YZ | X.Y | ERROR | ERROR |
例5中,root、X、X.Y都有各自配置好的同名LoggerConfig。X.YZ這個logger沒有配置好的LoggerConfig,所以他的level來自於指定給他的loggerConfig X。因為X名稱最長匹配了logger名稱的開始。他並沒有關聯到X.Y這個loggertConfig,因為句點後的字元並沒有精確匹配。
Logger 名稱 | 被分配的 LoggerConfig | LoggerConfig Level | Logger Level |
root | root | DEBUG | DEBUG |
X | X | ERROR | ERROR |
X.Y | X.Y | ERROR | |
X.Y.Z | X.Y | ERROR |
例6中,X.Y這個LoggerConfig沒有配置level,所以他繼承了X這個LoggerConfig的level。X.Y.Z這個logger使用X.Y這個LoggerConfig,因為他沒有同名匹配的LoggerConfig。他也從LoggerConfig X那裡繼承了他的日誌level。
下面的表格表明了Level過濾是如何工作的。表各種縱向表頭是logEvent的level,橫向的表頭是關聯了相應level的LoggerConfig。相交點指出了是否LogEvent允許被傳遞做進一步處理,還是被丟棄。
Event Level | LoggerConfig Level | ||||||
TRACE | DEBUG | INFO | WARN | ERROR | FATAL | OFF | |
ALL | YES | YES | YES | YES | YES | YES | NO |
TRACE | YES | NO | NO | NO | NO | NO | NO |
DEBUG | YES | YES | NO | NO | NO | NO | NO |
INFO | YES | YES | YES | NO | NO | NO | NO |
WARN | YES | YES | YES | YES | NO | NO | NO |
ERROR | YES | YES | YES | YES | YES | NO | NO |
FATAL | YES | YES | YES | YES | YES | YES | NO |
OFF | NO | NO | NO | NO | NO | NO | NO |
Filter
在之前的章節,額外的自動日誌過濾會如我們所想而發生,Log4j提供Filter可以應用在把控制傳遞給任何LoggerConfig之前。在控制傳遞給LoggerConfig之後但是呼叫任何appender之前,在控制傳遞給LoggerConfig之前,但是在呼叫指定的Apeender之前。方式上很像防火牆filter,每個filter可以返回三種結果,Accept、Deny、Neutral。Accept含義是其他的Filter都不用再呼叫了,event應該被處理。Deny意味著event要立即被忽略,控制返回給呼叫者。Neutral表明event要被傳遞給其他的Filter。如果這裡沒有其他的filter,那麼event將被處理。
儘管event可能被filter接收,但是仍舊有可能不被記錄。這可能發生在,當event被前置的LoggerConfig Filter所接受,但是被LoggerConfig的filter所拒絕或者被所有的appender所拒絕。
Appender
基於logger配置,選擇性啟用或者禁用日誌請求的能力只是整體的一小部分。Log4j允許日誌請求來記錄日誌到不同的目的地。在Log4j中的叫法,輸出的目的地稱為Appender。當前,Appender有console,file,remote scoket伺服器,Apache Flume,JMS,remote UNIX Syslog daemon,還有很多種DB的API。學習 Appenders 章節來獲取更多可用型別的資訊。一個Logger可以分配不止一個Appender。
Appender可以被新增進logger,通過呼叫當前configuration的addLoggerAppender 方法。如果匹配Logger名稱的LoggerConfig並不存在,那麼會建立一個,Appender會被附上,之後所有Logger都會被通知更新他們的LoggerConfig引用。
對於給定的logger,任何啟用的日誌請求,都會被傳送給這個Logger的LoggerConfig中所有的appender,同樣傳遞給LoggerConfig的父親的appender。換句話講,Appender通過LoggerConfig的層級被追加繼承。舉個例子,如果console Appender被新增進root Logger,那麼所有啟用的日誌請求都會至少在console中列印。如果一個額外的檔案appender被新增到一個叫做C的LoggerConfig中,那麼對於C及C的孩子,所有的可用的日誌請求都會被記錄到檔案裡及console中。我們也可以通過在配置檔案中,關於Logger的宣告部分設定addtivity="false",以此來重寫預設的行為。
支配appender追加屬性的規則總結如下:
Appender追加性
Logger L的日誌輸出會進入和L關聯的LoggerConfig以及這個loggerConfig祖先的所有Appender。這就是appender追加性的含義。
然而,如果一個Logger L關聯的LoggerConfig的祖先,稱之為P吧,他的additivity標識被設為了false,之後L的輸出將會直接給所有L的loggerConfig中的appender,也會給向上直到P的祖先的appender,包含P。但是並不會給P的任何祖先的appender。
Logger的additivity標識預設設為true
下表展示了一個例子:
Logger Name | Added Appenders | Additivity Flag | Output Targets | Comment |
root | A1 | not applicable | A1 | The root logger has no parent so additivity does not apply to it. |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | Appenders of "x" and root. |
x.y | none | true | A1, A-x1, A-x2 | Appenders of "x" and root. It would not be typical to configure a Logger with no Appenders. |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | Appenders in "x.y.z", "x" and root. |
security | A-sec | false | A-sec | No appender accumulation since the additivity flag is set to false. |
security.access | none | true | A-sec | Only appenders of "security" because the additivity flag in "security" is set to false. |
Layout
通常,使用者想要客製化的不僅僅是輸出的目的地,還有輸出的格式。這可以通過給appender關聯layout來達成。Layout負責根據使用者希望的那樣來格式化LogEvent,反之,appender負責把格式好的輸出傳送到目的地。PatternLayout,log4j標準釋出版中的一部分,可以讓使用者指定輸出的格式,通過類似C語言print函式中約定的pattern。
例如,使用轉換pattern的PatternLayout "%r [%t] %-5p %c - %m%n" 將會輸出類似下面的內容:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一個欄位是程式啟動後,經過的毫秒數。第二個欄位是產生日誌的執行緒。第三個欄位是日誌片段的等級,第四個欄位是logger關聯的名稱。‘-’後面的文字是訊息片段。
Log4j 帶有許多不同的Layout,用於各種情況,如JSON、XML、HTML及Syslog。其他database聯結器appender,需要輸入制定的欄位,而不是普通的文字layout。
重要的是,log4j可以渲染 log訊息的內容,通過使用者指定的條件。例如,如果你需要頻繁的記錄Oranges,你當前專案中的一種物件型別。你可以建立Orangemessage,它可以接受Orange的實體並且把它傳遞給Log4j。這樣在需要的時候,Orange物件可以被格式化適合的byte陣列。
StrSubstitutor and StrLookup
StrSubstitutor 類 和 StrLookup 介面,其實是借自 Apache Commons Lang ,然後經過修改後支援LogEvent的求值。另外Interpolator class也是借自Apache Commons Configuration,來讓StrSubstitutor通過各種StrLookup去查變數的值。他也被修改來支援logEvent求值。放在一起,它們提供了一個機制,允許相關變數的配置來自於System Properties,配置檔案,ThreadContext Map,LogEvent中的StructuredData。在配置處理或者在每個event被處理的時候,如果元件有處理變數的能力,那麼變數將被賦值。檢視Lookups 獲取更多資訊。