Log4j日誌體系結構解讀

Float_Luuu發表於2016-11-28

摘要

我們在寫日誌的時候首先要獲取logger,在每一個使用log4j的專案都有很多個地方要獲取logger,這些logger是真實的被例項化的Logger物件,他們有可能被分散在無數不同的類中,日誌體系結構講的是這些logger物件是如何組織的,他們之間又有什麼樣的關係。

體系結構

我們舉個具體的例項來看看,假設我的專案包結構如下:

專案結構

說明一下:com.flu.jdk包下面有兩個類分別是LogTest1和LogTest2,然後在包com.flu包下面有一個LogTest3類,很顯然,com.flu.jdk包是com.flu包的子包。假設我們在這三個類中分別通過LogManager.getLogger(xxx.class)獲取三個logger例項,他們分別是logger1、logger2和logger3,我們將要討論這三個logger的關係。

值得注意的是log4j的日誌體系中,有一個特殊的日誌物件叫做root(根),他是始終存在的,假設我們首先獲取logger例項,log4j將構造下面這樣一個圖形(我不能把它叫做樹):

當只有logger1的時候

當我們獲取logger2例項的時候,這個圖將變成:

當加入logger2日誌例項時結構圖

當我們獲取logger3例項的時候,這個圖又變了一個樣,如下:

當加入logger3日誌例項之後

僅僅才三個日誌例項,圖就搞的略複雜,可想log4j應用中,將會有無數的日誌例項按照這個規律組成紛繁複雜的有向圖結構,雖然看起來雜亂,但是又規律。那麼問題來了,這樣的結構有什麼用呢?下一節我們將會看到這種結構對於日誌配置繼承的影響。

配置繼承

log4j日誌級別定義

在往下面看之前我們先來看看log4j對日誌級別的定義:

public final static int OFF_INT = Integer.MAX_VALUE;
public final static int FATAL_INT = 50000;
public final static int ERROR_INT = 40000;
public final static int WARN_INT  = 30000;
public final static int INFO_INT  = 20000;
public final static int DEBUG_INT = 10000;
  //public final static int FINE_INT = DEBUG_INT;
public final static int ALL_INT = Integer.MIN_VALUE;

很顯然,log4j的日誌級別有下面的關係:

OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL

log4j在寫日誌的時候只有噹噹前寫日誌的級別大於等於當前日誌例項的配置級別的時候,日誌寫操作才生效,比如當前日誌例項的配置級別為INFO,那麼log.info會寫成功,而log.debug則不會寫。

日誌寫原始碼剖析

我們來看看一句簡單的log.info(“this is log message”)的背後,先來看看一段原始碼:

public void info(Object message) {
  if(repository.isDisabled(Level.INFO_INT))
    return;
  if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel()))
    forcedLog(FQCN, Level.INFO, message, null);
}
public boolean isDisabled(int level) {
  return thresholdInt > level;
}

首先看看當前寫的日誌級別是否被禁止的,預設的情況下thresholdInt為ALL,因此INFO的級別顯然比ALL大,因此下面會繼續看看INFO的級別是否大於等於當前日誌例項生效的級別,this.getEffectiveLevel()獲取的例項是什麼呢?我們繼續看看程式碼:

public Level getEffectiveLevel() {
   for(Category c = this; c != null; c=c.parent) {
     if(c.level != null)
		return c.level;
   }
   return null; // If reached will cause an NullPointerException.
 }

當前日誌生效的級別邏輯為首先看看當前日誌例項是否有配置級別,如果沒有,那麼就繼續找當前日誌例項的parent節點,按照上一節中所表述的,如果當前日誌的日誌級別沒有配置,當找到root的日誌級別,並根據root的日誌級別來斷定是否繼續進行日誌寫。這裡體現了日誌級別的繼承關係,其實不僅僅是日誌級別,日誌其他相關的配置也會基於這種繼承的特性,比如appender元件等。

專案應用

瞭解Log4j的日誌體系結構以及日誌級別配置的繼承特性之後,我們現在應該比較清楚專案中應該如何配置了。以Log4j.xml配置檔案為例子,滿足基本需求我們只需要配置root這個日誌例項的日誌級別即可,如下:

<root>
    <level value="INFO" />
    <appender-ref ref="CONSOLE" />
</root>

上面配置了root日誌例項的日誌級別為INFO,如果獲取按照一定規範(當前類的許可權定名作為日誌例項名),那麼我們可以保證所有的日誌例項將繼承root所配置的日誌級別。

配置隔離

上面的配置略粗糙,假如我們想為不同的模組給予不同的配置怎麼辦呢?最常見的是業務日誌與中介軟體日誌,比如我們的業務業務包名為com.dianping.biz,而我們的rpc元件的包名字為com.dianping.pigeon,則我們可以使用下面方法給予不同的模組不同的配置:

<!--業務日誌配置-->
<category name="com.dianping.biz">
    <level value="INFO" />
    <appender-ref ref="CONSOLE" />
</category>

<!--pigeon元件日誌配置-->
<category name="com.dianping.pigeon">
    <level value="DEBUG" />
    <appender-ref ref="CONSOLE" />
</category>

通過上面的配置,我們可以指定com.dianping.biz包下面所有類獲取的logger都繼承name為com.dianping.biz的日誌配置,而com.dianping.pigeon包下面的所有類獲取的logger都繼承name為com.dianping.pigeon的日誌篇日誌。不過通常設計良好的中介軟體都定製了日誌配置以確保中介軟體日誌與業務日誌隔離。

總結

昨天有個同事對log4j進行了一些分享,會上聽的意猶未盡因此課下忍不住扒一扒log4j的內褲,日誌作為java應用的一項重要內容,其不僅僅包括日誌如何寫、以什麼格式寫、以及日誌寫到哪裡的問題,還包括效能、擴充套件性、分散式、日誌實時分析等方面問題,本文在介紹log4j日誌體系的基礎之上稍微聊一下專案應用於配置隔離相關內容,如果讀者有興趣可以深入研究,必定收貨滿滿。

相關文章