原文出處:http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html。感謝上善若水的無私分享。html
在簡單的介紹了Log4J各個模塊類的做用後,如下將詳細的介紹各個模塊的具體做用以及代碼實現。java
Logger是對記錄日誌動做的抽象,它提供了記錄不一樣級別日誌的接口,日誌信息能夠包含異常信息也能夠不包含:apache
Logger類包含Level信息 ,若是當前Logger未設置Level值,它也能夠中父節點中繼承下來,該值能夠用來控制該Logger能夠記錄的日誌級別:數組
Logger是一個命名的實體,其名字通常用」.」分割以體現不一樣Logger的層次關係,其中Level和Appender信息能夠從父節點中獲取,於是Logger類中還具備name和parent屬性。服務器
在某些狀況下,咱們但願某些Logger只將日誌記錄到特定的Appender中,而不想記錄在父節點中的Appender中,Log4J爲這種需求提供了additivity屬性,即對當前Logger節點,若是其additivity屬性設置爲false,則該Logger不會繼承父節點的Appender信息,可是其子節點依然會繼承該Logger的Appender信息,除非子節點的additivity屬性也設置成了false。app
最後,爲了支持國際化,Log4J還提供了兩個l7dlog()方法,經過指定的key,以從資源文件中獲取消息內容。爲了使用這兩個方法,須要設置資源文件。一樣,資源文件也是能夠從父節點中繼承的。ide
另外,在實際開發中常常會遇到要把日誌信息同時寫到不一樣地方,如同時寫入文件和控制檯,於是一個Logger實例中能夠包含多個Appender,爲了管理多個Appender,Log4J抽象出了AppenderAttachable接口,它定義了幾個用於管理多個Appender實例的方法,這些方法由AppenderAttachableImpl類實現,而Logger會實例化AppenderAttachableImpl,並將這些方法代理給該實例:oop
有時候,爲了測試等其餘需求,咱們但願Logger自己不作什麼事情,Log4J爲這種需求提供了NOPLogger類,它繼承自Logger,可是基本上的方法都爲空。性能
Level是對日誌級別的抽象,目前Log4J支持的級別有FATAL、ERROR、WARN、INFO、DEBUG、TRACE,從頭至尾一次級別遞減,另外Log4J還支持兩種特殊的級別:ALL和OFF,它們分別表示打開和關閉日誌功能。測試
每一個Level實例包含了該Level表明的int值(通常是從級別低到級別高一次增大)、該Level的String表達、該Level和系統Level的對應值。
Level類主要提供了判斷哪一個Level級別更高的方法isGreaterOrEqual()以及將int值或String值轉換成Level實例的toLevel()方法:
另外,因爲對相同級別的Level實例來講,它必須是單例的,於是Log4J對序列化和反序列化作了一些處理。即它的三個成員都是transient,真正序列化和反序列化的代碼本身寫,而且加入readResolve()方法的支持,以保證反序列化出來的相同級別的Level實例是相同的實例。
若是要實現本身的Level類,能夠繼承自Level,而且實現相應的靜態toLevel()方法便可。關於如何實現本身的Level類將會在配置文件相關小節中詳細討論。
LoggerRepository從概念以及字面上來講它就是一個Logger實例的容器:一方面相同名字的Logger實例只須要建立一次,在後面的使用中,只須要從這個容器中取便可;另外一方面,Logger容器能夠存放從配置文件中解析出來的信息,從而使配置信息能夠無縫的應用到Log4J內部系統中;最後Logger容器還爲維護Logger的樹狀層次結構提供了方面,每一個Logger只維護父節點的信息,有了Logger容器的存在則能夠很容易的找到一個新的Logger實例的父節點;關於Logger容器將在下一節中詳細講解。
LoggingEvent我的感受用LoggingContext更合適一些,它是對一第二天志記錄時哪能獲取到的數據的封裝。它包含了如下信息以提供Layout在format()方法中使用:
1. fqnOfCategoryClass:日誌記錄接口(默認爲Logger)的類全名,該信息主要用於計算日誌記錄點的源文件、調用方法以及行號等位置信息。
2. locationInfo:經過fqnOfCategoryClass計算位置信息,位置信息的計算由LocationInfo類實現,這些信息能夠提供給Layout使用。
3. logger:目前來看主要是經過Logger實例取得LogRepository實例,並經過LogRepository取得註冊的ObjectRender實例,若是有的話。
4. loggerName:當前日誌記錄的Logger名稱,提供給Layout使用。
5. threadName:當前線程名,提供給Layout使用。
6. level:當前日誌的級別,提供給Layout使用。
7. message:當前日誌類,通常是String類型,可是也能夠經過註冊ObjectRender,而後傳入相應的其餘對象類型。
8. renderedMessage:通過ObjectRender處理後的日誌信息,提供給Layout使用。
9. throwableInfo:異常信息,若是存在的話,提供給Layout使用。
10. timestamp:建立LoggingEvent實例的時間,提供給Layout使用。
11. 其餘相對不經常使用的信息將會在後面小節中講解。
LoggingEvent只是一個簡單的數據對象(DO),於是其實現仍是比較簡單的,即在建立實例時將數據提供給它,在其餘類(Layout等)使用它時經過getXXX()方法取數據。不過仍是有幾個方法能夠簡單的講解一下。
LocationInfo所指的位置信息主要包括記錄日誌所在的源文件名、類名、方法名、所在源文件的行號。
咱們知道在異常棧中每一條記錄都包含了方法調用對應的這些信息,Log4J的這些信息正是利用了這個原理,即經過構建一個Throwable實例,然後在該Throwable的棧信息中解析出來的:
以上Throwable通常會產生以下異常棧:
於是咱們就能夠經過callers.fully.qualified.className信息來找到改行信息,這個className信息便是傳入的fqnOfCategoryClass。
若是當前JDK版本是1.4以上,咱們就能夠經過JDK提供的一些方法來查找:
不然,則須要咱們經過字符串查找的方式來查找:
對於經過字符串查找到的fullInfo值,在獲取其餘單個值時還須要作相應的字符串解析:
className:
Log4J中,對傳入的message實例,若是是非String類型,會先使用註冊的ObjectRender(在LogRepository中查找註冊的ObjectRender信息)處理成String後返回,若沒有找到相應的ObjectRender,則使用默認的ObjectRender,它只是調用該消息實例的toString()方法。
ThrowableInformation類用以處理異常棧信息,即經過Throwable實例獲取異常棧字符串數組。同時還支持自定義的ThrowableRender(在LogRepository中設置),默認的ThrowableRender經過系統printStackTrace()方法來獲取信息:
Layout負責將LoggingEvent中的信息格式化成一行日誌信息。對不一樣格式的日誌可能還須要提供頭和尾等信息。另外有些Layout不會處理異常信息,此時ignoresThrowable()方法返回false,而且異常信息須要Appender來處理,如PatternLayout。
Layout的實現比較簡單,如SimpleLayout對一行日誌信息只是打印日誌級別信息以及日誌信息。
關於Layout更詳細的信息將會在之後小節中介紹。
Appender負責定義日誌輸出的目的地,它能夠是控制檯(ConsoleAppender)、文件(FileAppender)、JMS服務器(JmsLogAppender)、以Email的形式發送出去(SMTPAppender)等。Appender是一個命名的實體,另外它還包含了對Layout、ErrorHandler、Filter等引用:
簡單的,在配置文件中,Appender會註冊到Logger中,Logger在寫日誌時,經過繼承機制遍歷全部註冊到它自己和其父節點的Appender(在additivity爲true的狀況下),調用doAppend()方法,實現日誌的寫入。在doAppend方法中,若當前Appender註冊了Filter,則doAppend還會判斷當前日誌時候經過了Filter的過濾,經過了Filter的過濾後,若是當前Appender繼承自SkeletonAppender,還會檢查當前日誌級別時候要比當前Appender自己的日誌級別閥門要打,全部這些都經過後,纔會將LoggingEvent實例傳遞給Layout實例以格式化成一行日誌信息,最後寫入相應的目的地,在這些操做中,任何出現的錯誤都由ErrorHandler字段來處理。
目前Log4J實現的Appender都繼承自SkeletonAppender類,該類對Appender接口提供了最基本的實現,而且引入了Threshold的概念,即全部的比當前Appender定義的日誌級別閥指要大的日誌纔會記錄下來。
SkeletonAppender實現了doAppend()方法,它首先檢查日誌級別是否要比threshold要大;而後若是註冊了Filter,則使用Filter對LoggingEvent實例進行過濾,若是Filter返回Filter.DENY則doAppend()退出,不然執行append()方法,該方法由子類實現。
在Log4J中,Filter組成一條鏈,它定了以decide()方法,由子類實現,若返回DENY則日誌不會被記錄、NEUTRAL則繼續檢查下一個Filter實例、ACCEPT則Filter經過,繼續執行後面的寫日誌操做。使用Filter能夠爲Appender加入一些出了threshold之外的其餘邏輯,因爲它自己是鏈狀的,並且它的執行是橫跨在Appender的doAppend方法中,於是這也是一個典型的AOP的概念。Filter的實現將會在下一小節中講解。
SkeletonAppender還重寫了finalize()方法,這是由於Log4J自己做爲一個組件,它可能仍是經過其餘組件如commons-logging或slf4j組件間接的引入,於是使用它的程序不該該對它存在依賴的,然而在程序退出以前全部的Appender須要調用close()方法以釋放它所佔據的資源,爲了避免在使用Log4J的程序手動的close()的方法,以減小Log4J代碼的侵入性,於是Log4J將close()的方法調用加入到finalize()方法中,即在垃圾回收器回收Appender實例時就會調用它的close()方法。
WriterAppender將日誌寫入Java IO中,它繼承自SkeletonAppender類。它引入了三個字段:immediateFlush,指定沒寫完一條日誌後,即將日誌內容刷新到設備中,雖然這麼作會有一點性能上的損失,可是若是不怎麼作,則會出如今程序異常終止的時候沒法看到部分日誌信息,而常常這些丟失的日誌信息要用於分析爲何會出現異常終止的狀況,於是通常推薦將該值設置爲true,即默認值;econding用於定義日誌文本的編碼方式;qw定義寫日誌的writer,它能夠是文件或是控制檯等Java IO支持的流。
在寫日誌文本前,WriterAppender還會作其餘檢查,如該Appender不能已經closed、qw和layout必須有值等,然後才能夠將layout格式化後的日誌行寫入設備中。若layout自己不處理異常問題,則有Appender處理異常問題。最後若是每行日誌須要刷新,則調用刷新操做。
ConsoleAppender繼承自WriterAppender,它只是簡單的將System.out或System.err實例傳遞給WriterAppender以構建相應的writer,最後實現將日誌寫入到控制檯中。
在Log4J中,Filter組成一條鏈,它定了以decide()方法,由子類實現,若返回DENY則日誌不會被記錄、NEUTRAL則繼續檢查下一個Filter實例、ACCEPT則Filter經過,繼續執行後面的寫日誌操做。使用Filter能夠爲Appender加入一些出了threshold之外的其餘邏輯,因爲它自己是鏈狀的,並且它的執行是橫跨在Appender的doAppend方法中,於是這也是一個典型的AOP的概念。
Log4J自己提供了四個Filter:DenyAllFilter、LevelMatchFilter、LevelRangeFilter、StringMatchFilter。
DenyAllFilter只是簡單的在decide()中返回DENY值,能夠將其應用在Filter鏈尾,實現若是以前的Filter都沒有經過,則該LoggingEvent沒有經過,相似或的操做:
StringMatchFilter經過日誌消息中的字符串來判斷Filter後的狀態:
LevelMatchFilter判斷日誌級別是否和設置的級別匹配以決定Filter後的狀態:
LevelRangeFilter判斷日誌級別是否在設置的級別範圍內以決定Filter後的狀態:
這一系列終因而結束了。本文主要介紹了Log4J核心類的實現和他們之間的交互關係。涉及到各個模塊自己的其餘詳細信息將會在接下來的小節中詳細介紹,如LogRepository與配置信息、Appender類結構的詳細信息、Layout類結構的詳細信息以及部分LoggingEvent提供的高級功能。而像Level、Logger自己,因爲內容很少,已經在這一小節中所有介紹完了。