log4j源碼解析

(一)幾個基本概念java

Logger - 日誌寫出器,供程序員輸出日誌信息
Appender - 日誌目的地,把格式化好的日誌信息輸出到指定的地方去
ConsoleAppender - 目的地爲控制檯的Appender
FileAppender - 目的地爲文件的Appender
RollingFileAppender - 目的地爲大小受限的文件的Appender
Layout - 日誌格式化器,用來把程序員的logging request格式化成字符串
PatternLayout - 用指定的pattern格式化logging request的Layout程序員

日誌級別web

每一個Logger都被了一個日誌級別(log level),用來控制日誌信息的輸出。日誌級別從高到低分爲:
A:off         最高等級,用於關閉全部日誌記錄。
B:fatal       指出每一個嚴重的錯誤事件將會致使應用程序的退出。
C:error      指出雖然發生錯誤事件,但仍然不影響系統的繼續運行。
D:warm     代表會出現潛在的錯誤情形。
E:info         通常和在粗粒度級別上,強調應用程序的運行全程。
F:debug     通常用於細粒度級別上,對調試應用程序很是有幫助。
G:all           最低等級,用於打開全部日誌記錄。spring

上面這些級別是定義在org.apache.log4j.Level類中。Log4j只建議使用4個級別,優先級從高到低分別是 error,warn,info和debug。經過使用日誌級別,能夠控制應用程序中相應級別日誌信息的輸出。例如,若是使用了info級別,則應用程 序中全部低於info級別的日誌信息(如debug)將不會被打印出來。sql

 

(二)源碼解析apache

 

基本步驟:tomcat

一、LogManager類對log4j.xml或者log4j.properties文件進行解析(xml文件優先,若存在xml文件則不對properties文件進行解析)app

 

二、逐個解析配置文件中的logger對象,並將這些對象放入一個Hierarchy的實例中(Hierarchy類中定義了一個hashtable來存儲全部的logger)webapp

      a、取得RootLoggeroop

      b、取得RootLogger對應的Appender (必須在配置文件中指明rootLogger的Appender)

      c、取得Appender的Layout,將Layout追加到Appender中 

      d、將Appender追加到RootLogger中

      e、將RootLogger添加進Hierarchy中

      f、依照上面abcde的步驟將其餘logger添加進Hierarchy中

 

三、經過LogManager.getLogger(String name)或LogManager.getLogger(Class clazz)獲取獲取到指定的logger對象(事實上,傳遞進去的clazz也會被轉換爲字符串,不過要加上它的包名,如Test.Class-->com.test.Test)

      a、經過傳遞進來的參數,判斷Hierarchy中是否已經含有此logger;(經過hashtable.get(key)獲取再判斷)

      b、若該logger已經存在於Hierarchy中則將Hierarchy的logger返回

      c、經過new Logger(String name)或new Logger(Class clazz)建立一個新的logger,設置這個logger的parent屬性,將此對象放入Hierarchy中並返回    

//爲使代碼更簡潔明瞭,此段代碼我作了部分的刪減
private final void updateParents(Logger cat)
    {
        String name = cat.name;
        int length = name.length();
        boolean parentFound = false;
        //由此段代碼可看出,這裏是根據包名斷定父節點的
        for(int i = name.lastIndexOf('.', length - 1); i >= 0; i = name.lastIndexOf('.', i - 1))
        {
            String substr = name.substring(0, i);
            CategoryKey key = new CategoryKey(substr);
            Object o = ht.get(key);
            if(o instanceof Category)
            {
                parentFound = true;
                cat.parent = (Category)o;
                break;
            }
        }
        //若沒找到父節點,則默認其父節點爲root
        if(!parentFound)
            cat.parent = root;
    }

 

四、經過logger的error(Object message, Throwable t)等方法(包括error,warn,info,debug)輸出日誌

      a、判斷你要輸入的日誌級別是否高於該logger的level,若高於,則調用該logger的各個appender輸出日誌,若低於,則不進行輸出

           注:若是沒有設置日誌記錄器(Logger)的級別,那麼它將會繼承最近的祖先的級別。 

//獲取有效的日誌級別
//若該logger沒有定義level則使用其最近的祖先定義的level
// Loger類繼承於Category 
   public Level getEffectiveLevel()
    {
        for(Category c = this; c != null; c = c.parent)
            if(c.level != null)
                return c.level;

        return null;
    }

      b、逐個調用該logger的appender輸出日誌

           注:appender中也有級別限制,只有知足這個限制的日誌才能被輸出

     c、 若該logger的additive屬性爲true,則調用父類的appender輸出日誌(additive屬性默認爲true)

 public void callAppenders(LoggingEvent event)
    {
        category;
        JVM INSTR monitorexit ;
        continue;
        exception;
        throw exception;
        int writes = 0;
        //由如下代碼能夠看出若是additive屬性爲true則會逐層調用祖先的appender,直到root或者遇到additive屬性爲false的祖先
label0:
        for(Category c = this; c != null; c = c.parent)
        {
label1:
            {
                synchronized(c)
                {
                    if(c.aai != null)
                        writes += c.aai.appendLoopOnAppenders(event);
                    if(c.additive)
                        break label1;
                }
                break label0;
            }
        }

        if(writes == 0)
            repository.emitNoAppenderWarning(this);
        return;
    }

 

(三)關於日記文件的備份

RollFileAppender

當日志文件大小超出了指定值時,log4j會將咱們以前存放在指定文件(如:normal.log)的日誌備份(即轉移)到一個新的日記文件(如normal_1.log)

DailyRollFileAppender

按指定的時間頻道,將以前存放在指定文件(如:normal.log)的日誌備份(即轉移)到一個新的日誌文件(如 normal_20120101.log)

在DailyRollingFileAppender中能夠指定monthly(每個月)、 weekly(每週)、daily(天天)、half-daily(每半天)、hourly(每小時)和minutely(每分鐘)六個頻度,這是經過爲 DatePattern選項賦予不一樣的值來完成的。DatePattern選項的有效值爲:

  • '.'yyyy-MM,對應monthly(每個月)

  • '.'yyyy-ww,對應weekly(每週)

  • '.'yyyy-MM-dd,對應daily(天天)

  • '.'yyyy-MM-dd-a,對應half-daily(每半天)

  • '.'yyyy-MM-dd-HH,對應hourly(每小時)

  • '.'yyyy-MM-dd-HH-mm,對應minutely(每分鐘)

PS:以上種方式是分別經過大小和時間進行日誌的備份,若是在實際項目開發中須要根據實際條件指定新的備份方式,咱們能夠參照log4j.jar中的RollFileAppender和DailyRollFileAppender的源碼,本身進行編寫一個Appender類就好了

 

(四)SSH與Log4j整合的好處

1.配置文件(log4j.properties或者log4j.xml)能夠不用放在class-path中,而可讓咱們指定,通常咱們會把該配置文件與項目中的其餘配置文件放在一塊兒

在web.xml中的詳細設定以下:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>WEB-INF/log4j.properties</param-value>
</context-param>

 

2. 日誌文件的存放路徑能夠用相對路徑指定,好比說放在當前項目下的WEB-INF/logs文件夾中,而不須要用絕對路徑定義日誌存放位置

<context-param>   <param-name>webAppRootKey</param-name>   <param-value>web.root</param-value> </context-param> <!-- 須要添加spring-web.jar包-->
<listener>     <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener>

 

這裏涉及到了另外一個知識點,Spring經過 org.springframework.web.util.WebAppRootListener 這個監聽器將咱們當前項目的所在路徑配置進了系統的環境變量中,

也就是至關於在系統環境變量中配置了一個叫「web.root」的變量,這個變量指向了咱們當前項目的物理路徑。這樣,咱們在項目中就能夠經過System.getProperty("web.root")來獲取當前項目的物理路徑,同時,咱們也能夠在log4j.properties 裏這樣定義logfile位置log4j.appender.logfile.File=${webapp.root}/WEB-INF/logs/mylog.log

 

若是在web.xml中已經配置了 org.springframework.web.util.Log4jConfigListener
這個監聽器,則不須要配置WebAppRootListener了。由於Log4jConfigListener已經包含了WebAppRootListener的功能

部署在同一容器中的Web項目,要配置不一樣的<param-value>,不能重複,由於這個值事實上就是至關於path環境變量中的一個key,key天然不能重複。

 

三、動態的改變記錄級別和策略,即修改log4j.properties,不須要重啓Web應用,這須要在web.xml中設置自動掃描log4j文件的間隔(每隔一段時間刷新一次配置)

在web.xml中的配置以下:
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>

 

補充擴展

              正式上線的項目中應儘可能少用system.out.println(),由於該語句會把內容打印到tomcat默認的日誌文件中,不便於日誌的管理,並且容易佔據系統資源;

              hibernate自帶的showsql功能也是默認將對應的sql語句輸出到tomcat默認的日誌文件中,hibernate的日誌輸出也是依賴於log4j實現的,因此咱們也能夠經過配置將該日誌輸出控制起來。

相關文章
相關標籤/搜索