log4j源碼粗讀
幾乎每個大的應用都包括他自己的日誌或跟蹤API
日誌也有它自己的缺點,它會降低應用的運行速度;如果太過詳細,它會導致屏幕過閃;為了減輕這些擔心,log4j被設計成是可依賴的,快速的和可擴展的。log4j很少關注某個應用,因而會努力的做到簡單易懂,易使用。
三個主要組件Loggers,Appenders,Layouts
這三個組件相互合作以使開發者根據類型和級別記錄消息,並能夠在運行時控制這些消息的格式及在何處記錄。
Logger
logger的層級
根據開發者不同的選擇區域,logging的空間是被分類的,基於此,我們之前選擇category作為這個包的中心概念。然而在1.2的版本後,logger類替換了category類,那些早期的版本logger類可以近似看成category類的另一個名字。
Logger是命名的實體,是大小寫敏感的,並且遵循下面的層級規則,按點分割,點號後面的被認為是前面子孫。如com.foo是com.foo.Bar的父類
根logger處在整個logger層級的頂部,它有兩個例外
1. 它總是存在
2. 它不能通過名字獲取到,通過Logger.getRootLogger獲取
所有其他logger都通過Logger.getLogger方法獲取,這個方法獲取一個logger的名字作為參數
logger共有
TRACE,
DEBUG,
INFO,
WARN,
ERROR and
FATAL
這幾個可能的級別,當然也可以自己繼承Level類定義自己的log級別,雖然我們不鼓勵這麼做。
如果一個logger沒有分配級別,它會繼承它最近的祖先的級別。為保證所有logger都有級別,rootlogger總是有一個分配的級別(默認為debug)
用相同的name參數使用getLogger方法總是會返回同一個logger對象
與正常邏輯相反的是log4j可以以任何順序構造,比如,一個parent的logger可以被找到並鏈接到它的子孫即使它在它的子孫後面實例化
Appender
log4j允許logging列印到多個地方,每一個輸出的地方叫一個appender,如console,文件,gui組件,socket等等。
一個logger可以綁定多個appender
appender也會繼承,比如rootLogger綁定了console的appender,則所有子孫都會綁定console。可以通過設定additivity flag關掉這種繼承性
Layout
layout用於定製化輸出格式
PatternLayout類讓我們可以使用類似printf的方式輸出配置日誌格式
ObjectRender用於將對象渲染成字元串,對象的渲染遵循類的層級。例如,假定orange是水果,如果你註冊了一個FruitRender,則所有包含orange的fruit將會使用FruitRender渲染,除非你特殊註冊一個orange的render。Object Render必須實現ObjectRender介面。
配置文件可以寫成xml或kv格式的java properties
默認初始化過程
log4j不對環境做任何假定,尤其是,沒有默認的appender。
Logger的靜態初始器將會嘗試自動配置
1. 將log4j.defaultInitOverride這個system property設置成任何一個非false的值,將導致跳過默認的初始化過程
2. 設置resource變數的值為log4j.configuration system property,如果這個屬性未定義,則resource將會被設置成默認值「log4j.properties」
3. 嘗試將resource變數轉換為一個URL
4. 如果失敗,則嘗試通過getResource方法從classpath搜索,並返回一個URL。注意log4j.properties是一個合法的URL格式。getResource方法搜索的順序是,當前線程上下文對應classloader的resource,載入這個類的classloader。
5. 如果沒有URL可以找到,就放棄默認初始化過程。否則從這個url配置log4j
Tomcat下的默認初始化
默認的初始化過程在web-server環境下仍然是有用的。在Tomcat3.x/4.x,你應該將log4j.properties放在WEB-INF/classes目錄下,這樣log4j將會找到配置文件並進行初始化。
也可以設置system property log4j.configuration在Tomcat啟動前。Tomcat3.x用TOMCAT_OPTS 環境變數設置,4.x用CATALINA_OPTS
NDC
Nested diagnostic contexts, 嵌套的上下文診斷,線程信息保存是基於Stack
NDC是線程級別的,它的所有操作都只是針對一個線程,不會對其他線程的NDC產生影響。例如,一個servlet可以為每個客戶端請求創建一個NDC,包含客戶端的hostname和其他請求內的信息。
只要通過調用NDC.push就開業創建一個NDC,進入一個上下文。
當調用NDC.pop就離開這個上下文
當退出一個線程要確保調用了NDC.remove方法,以釋放內存,避免泄露
不需要每個push都對應一個pop,但不能真正應用中的上下文和NDC當前設置的上下文不匹配
有一個機制可以使你有時粗心的忘記在退出線程前調用remove方法,一個線程可以繼承另一個線程(可能是父線程)的NDC,通過調用inherit方法,是同一個NDC
一個線程可以通過cloneStack方法獲取一個NDC的一個拷貝(clone),不是同一個對象
NDC內沒有使用synchronized關鍵字同步,這在多線程的環境下看起來可能是危險的,所有線程共享一個hashtable,但hashtable是線程安全的。
REAP_THRESHOLD是在我們調用lazyRemove方法前調用push方法最多的次數,lazyRemove方法不會被很頻繁的調用,我們因此避免太頻繁的創造Enumeration
hashtable的key就是當前的線程對象。
lazyRemove方法:這個方法中會獲取hashtable的key的Enumeration(相當於iterator),將沒有主動調用remove的線程加入到一個vector中,然後相當於調用這些線程的,從hashtable中刪掉。但是我們最多處理4個線程,ht中死亡的線程比例越高,remove調用的可能性越高
MDC
MDC類似NDC,除了MDC是基於一個map,NDC是一個Stack
java2的版本是一個ThreadLocalMap,java1是不支持的。map的value是一個hashtable
Logger的log方法粗略過程
1. 檢查priority
2. 獲取appenderList
3. 對每個appender添加這個logEvent
4. 每個Appender檢查自己的filter
5. 通過layout格式化輸出
6. 執行這個log的輸出
了解上面這些再看源碼其實就很好理解了,具體代碼就不細列出了,文章對應版本是1.2,maven依賴如下
log4j
log4j
1.2.17
※當上導演的,都是持續寫劇本的人——讀李安《十年一覺電影夢》之一
※米開朗基羅·皮斯特萊托:一錘掄下的勇氣和責任
TAG:全球大搜羅 |