探索java世界中的日誌奧祕

                                  java日誌簡單介紹java

 

 

 

對於一個應用程序來講日誌記錄是必不可少的一部分。線上問題追蹤,基於日誌的業務邏輯統計分析等都離不日誌。JAVA領域存在多種日誌框架,目前經常使用的日誌框架包括Log4jLog4j 2Commons LoggingSlf4jLogbackJulweb

 

 

1、java日誌發展史數據庫

 

2、java 經常使用日誌框架類別介紹apache

 

3、門面、實現、橋接設計模式

 

4、commons logging vs Slf4japi

 

5、 log4j vs logback數組

6、 slf4j 源碼解讀安全

 

7、logback多線程

 

8、MDCapp

 

 

 

 

 

 

 

 

 

 

 

 

 

1、java日誌發展史

 

1996年早期,歐洲安全電子市場項目組決定編寫它本身的程序跟蹤API(Tracing API)。通過不斷的完善,這個API終於成爲一個十分受歡迎的Java日誌軟件包,即Log4j。後來Log4j成爲Apache基金會項目中的一員。

 

期間Log4j近乎成了Java社區的日誌標準。聽說Apache基金會還曾經建議sun引入Log4jjava的標準庫中,但Sun拒絕了。

2002Java1.4發佈,Sun推出了本身的日誌庫JUL(Java Util Logging),其實現基本模仿了Log4j的實現。在JUL出來之前,log4j就已經成爲一項成熟的技術,使得log4j在選擇上佔據了必定的優點。

接着,Apache推出了Jakarta Commons LoggingJCL只是定義了一套日誌接口(其內部也提供一個Simple Log的簡單實現),支持運行時動態加載日誌組件的實現,也就是說,在你應用代碼裏,只需調用Commons Logging的接口,底層實現能夠是log4j,也能夠是Java Util Logging

後來(2006)Ceki Gülcü不適應Apache的工做方式,離開了Apache。而後前後建立了slf4j(日誌門面接口,相似於Commons Logging)Logback(Slf4j的實現)兩個項目,並回瑞典建立了QOS公司,QOS官網上是這樣描述Logback的:The GenericReliable Fast&Flexible Logging Framework(一個通用,可靠,快速且靈活的日誌框架)

現今,Java日誌領域被劃分爲兩大陣營:Commons Logging陣營和SLF4J陣營。
Commons LoggingApache大樹的籠罩下,有很大的用戶基數。但有證據代表,形式正在發生變化。2013年末有人分析了GitHub30000個項目,統計出了最流行的100Libraries,能夠看出slf4j的發展趨勢更好:

 

 

Apache眼看有被Logback反超的勢頭,於2012-07重寫了log4j 1.x,成立了新的項目Log4j 2Log4j 2具備logback的全部特性。

 

 

2、java經常使用日誌框架類別介紹

 

         • Log4j Apache Log4j是一個基於Java的日誌記錄工具。如今則是Apache軟件基金會的一個項目。 Log4j是幾種Java日誌框架之一。 Log4j應該說是Java領域資格最老,應用最廣的日誌工具。從誕生之日到如今一直廣受業界歡迎。Log4j是高度可配置的,並可經過在運行時的外部文件配置。它根據記錄的優先級別,並提供機制,以指示記錄信息到許多的目的地,諸如:數據庫,文件,控制檯,UNIX系統日誌等。

Log4j 2 Apache Log4j 2apache開發的一款Log4j的升級產品。

 

Logback 一套日誌組件的實現(slf4j陣營)

        Jul (Java Util Logging),Java1.4以來的官方日誌實現。JDK1.4開始,經過java.util.logging提供日誌功能。它能知足基本的日誌須要,可是功能沒有Log4j強大,並且使用範圍也沒有Log4j普遍。

 

Commons Logging Apache基金會所屬的項目,是一套Java日誌接口,以前叫Jakarta Commons Logging,後改名爲Commons Logging

Slf4j 相似於Commons Logging,是一套簡易Java日誌門面,自己並沒有日誌的實現。(Simple Logging Facade for Java,縮寫Slf4j)。

 

                   這個些全部的日誌們都要歸功於一我的       Ceki Gülcü !!!

 

 

 

 

 

 

 

3、門面、實現、橋接

 

經歷了上述的發展,如今使用日誌框架時每每會涉及三個層面的東西。 

                

 

· 門面

· Slf4j: The simple logging facade for java.

·    JCL: Jakarta Commons Logging.

· 實現類

· log4j-1.2

· log4j-2.x

· logback

· jul: java.util.logging

·

· 橋接包

· SLF4J LOG4J 12 Binding

· JUL To SLF4J Bridge

· JCL 1.1.1 Implemented Over SLF4J ??

· SLF4J JDK14 Binding

· Apache Log4j Commons Logging Bridge

·

 

門面主要只負責定義接口,實現類才負責具體的編碼工做。

 

爲何要定義門面呢? 依賴接口而不依賴實現

 

橋接包顧名思義就是橋接門面和實現類。好比SLF4J LOG4J 12 Binding這個橋接包可使Slf4j和log4j1.2結合起來正常工做。一個已經成型的系統若是使用了這個模式,底層又想將log4j1.2換成log4j2.0實現,則只須要替換實現包爲log4j2.x以及橋接包爲 Log4j 2 SLF4J Binding。

 

 

                    

 

 

 

 

 

 

 

 

 

 

 

4、commons logging vs Slf4j

 

咱們先看一下java日誌框架之間的關係

 

Commons LoggingSlf4j是日誌門面(門面模式是軟件工程中經常使用的一種軟件設計模式,也被稱爲正面模式、外觀模式。它爲子系統中的一組接口提供一個統一的高層接口,使得子系統更容易使用)log4jLogback則是具體的日誌實現方案。能夠簡單的理解爲接口與接口的實現,調用這隻須要關注接口而無需關注具體的實現,作到解耦。

比較經常使用的組合使用方式是Slf4jLogback組合使用,Commons LoggingLog4j組合使用。

Logback必須配合Slf4j使用。因爲LogbackSlf4j是同一個做者,其兼容性不言而喻。(https://stackoverflow.com/questions/10117788/how-to-setup-commons-logging-to-use-logback)

 

Commons logging實現機制

Commons logging是經過動態查找機制,在程序運行時,使用本身的ClassLoader尋找和載入本地具體的實現。詳細策略能夠查看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。

 

Slf4j實現機制

Slf4j在編譯期間,靜態綁定本地的LOG庫。它是經過查找類路徑下org.slf4j.impl.StaticLoggerBinder,而後綁定工做都在這類裏面進行。

 

 

靜態綁定 & 動態綁定

靜態綁定又稱編譯時綁定,動態綁定又稱運行時綁定。

JCL做爲第一個log接口框架,使用了基於反射的動態綁定的方法,原理很簡單,預先定義好支持的log實現的工廠類的全路徑到一個數組中,遍歷這個數組,調用Class.forName依次嘗試尋找各個log實現,若是當前class loader沒找到,就去父class loader去找,直到找到任意一個實現爲止。

這種方法有致命的缺陷,這也正是SLF4J誕生的緣由。Java EE的web容器,爲了實現servlet規範中同一個容器中不一樣web app之間、web app和web容器之間的隔離,都使用的本身實現的class loader,邏輯和標準的class loader不一樣,致使一系列的沒法正常發現log實現庫的問題。

Taxonomy of class loader problems encountered when using Jakarta Commons Logging

這篇文章作了很是詳盡的分析解釋,文章的做者正是log4j和SLF4J的做者Ceki Gülcü,有興趣的同窗能夠閱讀。

 

另一個小改進:

JCL 輸出一個 debug 級別的 log:

logger.debug("start process request, url:" + url);

這個有什麼問題呢?通常生產環境 log 級別都會設到 info 或者以上,那這條 log 是不會被輸出的。然而無論會不會輸出,這其中都會作一個字符串鏈接操做,而後生產一個新的字符串。若是這條語句在循環或者被調用不少次的函數中,就會多作不少無用的字符串鏈接,影響性能。

因此 JCL 的最佳實踐推薦這麼寫:

if (logger.isDebugEnabled()) {

    logger.debug("start process request, url:" + url);

}

然而開發者經常忽略這個問題或是以爲麻煩而不肯意這麼寫。

 

 

因此SLF4J提供了新的API,方便開發者使用:

logger.debug("start process request, url:{}", url);

這樣的話,在不輸出 log 的時候避免了字符串拼接的開銷;在輸出的時候須要作一個字符串format,代價比手工拼接字符串大一些,可是能夠接受。

 

 

5、 log4j vs logback

logback算是log4j的升級版本 ,基本實現了全部log4j的功能。

logback比log4j有更多的優勢

更快的實現 

Logback的內核重寫了,在一些關鍵執行路徑上性能提高10倍以上。並且logback不只性能提高了,初始化內存加載也更小了。 

 

很是充分的測試 

Logback通過了幾年,數不清小時的測試。Logback的測試徹底不一樣級別的。在做者的觀點,這是簡單重要的緣由選擇logback而不是log4j。 

 

Logback-classic很是天然實現了SLF4j 

Logback-classic實現了SLF4j。在使用SLF4j中,你都感受不到logback-classic。並且由於logback-classic很是天然地實現了SLF4J,因此切換到log4j或者其餘,很是容易,只須要提供成另外一個jar包就OK,根本不須要去動那些經過SLF4JAPI實現的代碼。 

 

很是充分的文檔 

Logback文檔免費。Logback的全部文檔是全面免費提供的,不象Log4J那樣只提供部分免費文檔而須要用戶去購買付費文檔 

 

 

 

Filters(過濾器) 

有些時候,須要診斷一個問題,須要打出日誌。在log4j,只有下降日誌級別,不過這樣會打出大量的日誌,會影響應用性能。在Logback,你能夠繼續保持那個日誌級別而除掉某種特殊狀況,如alice這個用戶登陸,她的日誌將打在DEBUG級別而其餘用戶能夠繼續打在WARN級別。要實現這個功能只需加4行XML配置。

 

SiftingAppender(一個很是多功能的Appender) 

它能夠用來分割日誌文件根據任何一個給定的運行參數。如,SiftingAppender可以區別日誌事件跟進用戶的Session,而後每一個用戶會有一個日誌文件。 

 

自動壓縮已經打出來的log 

RollingFileAppender在產生新文件的時候,會自動壓縮已經打出來的日誌文件。壓縮是個異步過程,因此甚至對於大的日誌文件,在壓縮過程當中應用不會受任何影響。 

 

堆棧樹帶有包版本 

Logback在打出堆棧樹日誌時,會帶上包的數據。 

 

自動去除舊的日誌文件 

經過設置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory屬性,你能夠控制已經產生日誌文件的最大數量。若是設置maxHistory爲12,那那些log文件超過12個月的都會被自動移除。 

 

 

6、 slf4j源碼解讀

 

咱們寫代碼的時候是怎麼打日誌的呢?

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

LoggerFactory.getLogger(getClass());

 

這裏看不到任何跟實現類有關聯的代碼,然而咱們已經可使用get到的logger打日誌了。那麼slf4j究竟是怎麼找到實現類的了?

 

根據Path = 「org/slf4j/impl/StaticLoggerBinder.class」去加載相應的實現類

 

 

爲何要獲取類的名字,而根據名字來獲取對象呢?

由於每一個類使用的日誌處理實現可能不一樣,iLoggerFactory中也是根據名字來判斷一個類的實現方式的。

 

 

 

 

 

 

那麼這裏會有一個問題,若是找到多個實現類,最終會綁定哪個呢?

 

The warning emitted by SLF4J is just that, a warning. Even when multiple bindings are present, SLF4J will pick one logging framework/implementation and bind with it. The way SLF4J picks a binding is determined by the JVM and for all practical purposes should be considered random. As of version 1.6.6, SLF4J will name the framework/implementation class it is actually bound to.

Embedded components such as libraries or frameworks should not declare a dependency on any SLF4J binding but only depend on slf4j-api. When a library declares a compile-time dependency on a SLF4J binding, it imposes that binding on the end-user, thus negating SLF4J’s purpose. When you come across an embedded component declaring a compile-time dependency on any SLF4J binding, please take the time to contact the authors of said component/library and kindly ask them to mend their ways.

若是發現有多個實現類,那麼slf4j會打印出warning信息。可是僅僅是warning而已。即便有多個實現類,slf4j也只會挑選其中一個,這個選擇取決於JVM和全部其餘實際因素,基本算是隨機性的。同時,slf4j建議刪除多餘的實現類,僅僅保留一個。

 

 

 

 

 

7、logback

 

 

1StaticLoggerBinder 初始化並建立logFactory ()

 

 

 

StaticLoggerBinder.init();

 

初始化 new ContextInitializer(defaultLoggerContext).autoConfig();

 

getLoggerFactory()

 

 

總結一下這個過程: 

1StaticLoggerBinder在加載的時候,會去讀取配置文件,並根據配置文件對LoggerContext進行初始化 

2、而後初始化ContextSelectorStaticBinder,在這個類內部new一個DefaultContextSelector,並把第一步中配置完畢的LoggerContext傳給DefaultContextSelector 

3、調用getLoggerFactory()方法,直接返回第一步中配置的LoggerContext,或者委託DefaultContextSelector類返回LoggerContext

 

 

2loggerContext工廠類產出logger對象 

 

 

 

Logger getLogger(final String name);

 

com.darcytech.controller.LoginController

 

Logger[com]、Logger[com.darcytec]、Logger[com.darcytech.controller]、Logger[com.darcytech.controller.LoginController] 

 

 

總結一下建立Logger的完整流程: 

一、若是請求ROOT logger,則直接返回root 

二、若是請求的Logger已經存在,則直接返回 

三、若是請求的Logger還沒有建立,則從ROOT開始,級聯建立全部Logger 

四、每建立一個Logger,都要設置父子關係,繼承生效級別 

五、每建立一個Logger,都將其放入loggerCache,並將size++ 

 

 

三、Logger

 

transient private AppenderAttachableImpl<ILoggingEvent> aai;

Logger是委託這個類實現AppenderAttachable接口,也是委託這個類來調用Appender組件來實際記錄日誌,因此這個字段是最關鍵的。

 

主要方法

getChildByName

setLevel

createChildByName每建立一個Logger,都要設置父子關係,繼承生效級別

info

 

 

 

 

callAppenders

若是子Logger和父Logger都關聯了一樣的Appender,則日誌信息會重複記錄

 

 

 

總結一下Logger類中定義的字段和方法,是出於如下目的: 

 

1、定義parentchildList,用於實現父子Logger的樹形結構 

2、定義createChildByName()getChildByName()方法,是供LoggerContext建立Logger 

3、定義leveleffectiveLevelInt,是爲了斷定日誌級別是否足夠 

4、最後,filterAndLog()buildLoggingEventAndAppend()callAppenders()appendLoopOnAppenders()方法,是Logger類的核心方法,一步步地委託AppenderAttachableImpl類來實際記錄日誌 

 

 

 

4Appender

 

 

實現類就是最多見的ConsoleAppender和FileAppender

 

doAppend()

 

最終writeOut()方法委託配置給它的Encoder組件來記錄

 

 

 

五、簡單瞭解一下RollingFileAppender,rollingPolicy

經常使用的RollingFileAppender, TimeBasedRollingPolicy

 

 

 

 

 

 

 

 

8、什麼是MDC

 

 

 

MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日誌的功能。MDC 能夠當作是一個與當前線程綁定的哈希表,能夠往其中添加鍵值對,當須要記錄日誌時,只須要從 MDC 中獲取所需的信息便可。

 

 

 

此外,對於一些線程池使用的應用場景,可能咱們在最後使用結束時,須要調用clear方法來清洗將要丟棄的數據。

 

LogbackMDCAdapter

 

 

 

 

 

 

 

 

 

相關文章
相關標籤/搜索