隨筆-7  評(píng)論-23  文章-0  trackbacks-0
          最近需要用到log4j動(dòng)態(tài)定制Logger的場(chǎng)景,然后加上以前對(duì)于這個(gè)日志工具拿來(lái)就用而不知其原理的原因,所以決定花點(diǎn)時(shí)間看下它的源碼,如果你還對(duì)log4j如何使用感到困惑,那么請(qǐng)首先簡(jiǎn)要瀏覽下它的官網(wǎng)http://logging.apache.org/log4j/

          Log4j總體來(lái)說(shuō)是一個(gè)可定制,支持同時(shí)多種形式輸出日志,并且高度結(jié)構(gòu)化的日志庫(kù)。可定制,也就是既可以通過(guò)log4j.properties或者log4j.xml定義日志輸出的級(jí)別(Level),形式(Appender)以及文本格式(Layout),也可以通過(guò)Logger類或者LogManager類取得Logger實(shí)例,并且設(shè)置日志輸出級(jí)別(Level),形式(Appender)以及文本格式(Layout),可以說(shuō)是相當(dāng)?shù)暮?jiǎn)便與靈活。下面一段代碼簡(jiǎn)單地說(shuō)明了后者的實(shí)現(xiàn)。
          Logger MY_LOG = Logger.getLogger("MY_LOG");

          //文件形式的輸出方式實(shí)例化
          DailyRollingFileAppender appender = new DailyRollingFileAppender();
          appender.setName(name);
          appender.setAppend(
          true);
          appender.setEncoding(
          "GBK");
          //文本的輸出格式采用PatternLayout
          appender.setLayout(new PatternLayout(pattern));
          appender.setFile(
          new File(getLogPath(), fileName).getAbsolutePath());
          appender.activateOptions();

          //將appender加入到MY_LOG的appender集合
          MY_LOG.addAppender(appender);
          //設(shè)定日志輸出級(jí)別為INFO
          MY_LOG.setLevel(Level.INFO);

          Log4j初始化的代碼是在LogManager的靜態(tài)塊里面,這個(gè)靜態(tài)塊無(wú)論如何都會(huì)實(shí)例化一個(gè)日志級(jí)別為DEBUGRootLogger,并且初始化一個(gè)以這個(gè)RootLogger為根節(jié)點(diǎn)的級(jí)聯(lián)結(jié)構(gòu),然后檢查有沒(méi)有用戶指定重寫這個(gè)日志系統(tǒng)的初始化工作,如果沒(méi)有,那么先去找log4j.xml,如果沒(méi)有找到,那么再去找log4j.properties(也就是log4j.xml的優(yōu)先級(jí)高于log4j.properties), 只有找到這兩個(gè)配置文件的其中一個(gè),再初始化文件里面內(nèi)容,主要是一些配置文件中指定的Logger初始化,以及各個(gè)LoggerAppender列表設(shè)定,各個(gè)Appender的具體實(shí)例,Layout等。其實(shí),沒(méi)找到任何log4j配置文件也沒(méi)什么關(guān)系,因?yàn)橐呀?jīng)有RootLogger,日志系統(tǒng)骨架已經(jīng)完成了,所以也可以通過(guò)如前面的代碼添加Logger

          //初始化以RootLogger為根的級(jí)聯(lián)結(jié)構(gòu),rootLogger默認(rèn)DEBUG級(jí)別
          Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
          //有沒(méi)指定log4j.configuration(內(nèi)部使用,不知出于什么目的,未探究)
          if(configurationOptionStr == null{
              
          //加載classpath下log4j.xml
               url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
             
          if(url == null{
                  
          //如果log4j.xml沒(méi)有,加載log4j.properties
                  url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
              }

          }
           else {
              
          try {
              url 
          = new URL(configurationOptionStr);
              }
           catch (MalformedURLException ex) {
              url 
          = Loader.getResource(configurationOptionStr); 
              }

          }
          if(url != null{
              LogLog.debug(
          "Using URL ["+url+"] for automatic log4j configuration."); 
              
          //開始真正解析配置文件     
              OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());
          }
           else {
              LogLog.debug(
          "Could not find resource:["+configurationOptionStr+"].");
          }

          簡(jiǎn)要介紹完Log4J的初始化后,我們有必要來(lái)看下從Logger myLogger =LogManager. getLogger(“MY_ LOG”), myLogger.debug(“some message”)結(jié)束之后,Log4j到底為我們做了些什么。


           
          上面這幅序列圖主要描述的過(guò)程就是從LoggerManager取得一個(gè)Logger的過(guò)程,其中最重要的操作在Hierarchy類中,這個(gè)類說(shuō)白了就是存儲(chǔ)Logger的倉(cāng)庫(kù),其內(nèi)部使用一個(gè)HashMap ht來(lái)存儲(chǔ)LoggerProvisionNode

          這里需要解釋下ProvisionNode,這個(gè)類是一個(gè)Vector的實(shí)現(xiàn),之前我們談到過(guò)初始化的一開始,以RootLogger為根節(jié)點(diǎn)的級(jí)聯(lián)結(jié)構(gòu)(就是Hierarchy實(shí)例),那么這個(gè)ProvisionNode想當(dāng)于這個(gè)級(jí)聯(lián)結(jié)構(gòu)中的樹節(jié)點(diǎn),比如我在定義了一個(gè)名字為a.b.cLogger,那么總共會(huì)生成”a”,”a.b”兩個(gè)ProvisionNode,以及一個(gè)名字為”a.b.c”LoggerHierarchy并沒(méi)有一個(gè)鏈表來(lái)維護(hù)他們之間的順序,ProvisionNode會(huì)通過(guò)其本身就是向量的特性將屬于它的Logger進(jìn)行有序的排列,而Logger本身則通過(guò)parent屬性記住他們的日志屬性可以從哪里繼承。

          這樣做的好處有兩點(diǎn),第一點(diǎn)就是只要名字相同,從LogManager中取出來(lái)的Logger就是同一個(gè)實(shí)例,第二點(diǎn)好處就是級(jí)聯(lián)結(jié)構(gòu),可以定義出差異化的Logger,特別是其以a.b.c.d類似包結(jié)構(gòu)的拆分日志節(jié)點(diǎn),使得包級(jí)別的日志差異化輸出更加的容易,并且這種特性提供了日志節(jié)點(diǎn)屬性繼承的功能。

          舉個(gè)例子,我們一般取得一個(gè)Logger實(shí)例是這樣的, Logger log=LogManager.getLogger(Class class), Log4j處理方式為getLogger(class.getName()),這也就是說(shuō),這個(gè)Logger的名字是帶包名的完全限定名字,所以如果我們通過(guò)名為a.b.c.aclass,a.b.c.d.bclass這么兩個(gè)類取得Logger實(shí)例,然后定義名為a.b.c和a.b.c.d 2個(gè)Logger,那么總共會(huì)生成如下的節(jié)點(diǎn) 
                     
                      ProvisionNode: a,a.b
                              Logger: a.b.c(加入a,a.b 2個(gè)ProvisionNode中并且parent為RootLogger)
                                       a.b.c.d(加入a,a.b 2個(gè)ProvisionNode中,并且parent為a.b.c)
                                       a.b.c.aclass  (加入到a,a.b 2個(gè)ProvisionNode中,并且parent為a.b.c), 
                                       a.b.c.d.bclass(加入到a,a.b 2個(gè)ProvisionNode中,并且parent為a.b.c.d)

          其中ProvisionNode可能升級(jí)為L(zhǎng)ogger,當(dāng)同名的Logger加入時(shí),該P(yáng)rovisionNode中所有以該P(yáng)rovisionNode為父節(jié)點(diǎn)的子節(jié)點(diǎn)修改parent指向新的Logger.

          final private void updateChildren(ProvisionNode pn, Logger logger) {
              
          final int last = pn.size();
             
              
          for(int i = 0; i < last; i++{
                   Logger l 
          = (Logger) pn.elementAt(i);
                    
          //有可能子節(jié)點(diǎn)的父節(jié)點(diǎn)指向更低一級(jí)的節(jié)點(diǎn),比如孫節(jié)點(diǎn)。這個(gè)應(yīng)該不難理解。
                   
           if(!l.parent.name.startsWith(logger.name)) {
                       logger.parent 
          = l.parent;
                       l.parent 
          = logger;
                  }

               }

          }

          這樣Logger a.b.c除了自身的日志輸出設(shè)置之外,還享受rootLogger的輸出(輸出級(jí)別,Appenders),Logger a.b.c.d同時(shí)享受rootLogger和a.b.c的設(shè)置,a.b.c.d.bclass享受rootLogger,a.b.c,a.b.c.d的日志輸出設(shè)定,也就是可以分別定義一批Logger的輸出級(jí)別和輸出形式以及其他屬性,當(dāng)然也有控制開關(guān)控制這種繼承。下圖說(shuō)明了一些問(wèn)題。



          取得Logger之后,隨后就是需要按指定形式輸出日志內(nèi)容,首先需要在LoggerRepository中判定當(dāng)前Level是否可以做日志輸出,包括與全局的thresholdInt進(jìn)行判定(相當(dāng)于總開關(guān),這個(gè)級(jí)別通不過(guò)直接返回,不做任何事情),通過(guò)后,再與其自身Logger日志級(jí)別比較,如果沒(méi)有設(shè)定,查其父節(jié)點(diǎn)的日志級(jí)別,仍然沒(méi)有設(shè)定,那么查父節(jié)點(diǎn)的父節(jié)點(diǎn),直到查到有日志級(jí)別設(shè)定或者最終到RootLogger(其默認(rèn)為Debug級(jí)別),比較通過(guò)后,就可以調(diào)用callDependers進(jìn)行日志輸出了。

          日志級(jí)別為 OFF>FATAL>ERROR>WARN>INFO>DEBUG>ALL,只有輸出級(jí)別大于等于Logger自身級(jí)別才能進(jìn)行輸出,比如 logger.debug,那么只有該Logger的級(jí)別(也可能是其先輩節(jié)點(diǎn)的日志級(jí)別)DEBUG或者ALL才能允許被輸出。全局的thresholdInt如果不設(shè)定是保持在ALL級(jí)別。

          一般Logger會(huì)通過(guò)AppenderAttachableImpl的實(shí)例來(lái)維護(hù)多個(gè)Appender,并且可以共享父節(jié)點(diǎn)的Appender List(包括RootLogger里面定義的appenders,所以我們一般在log4j.xml或者log4j.properties里面定義一個(gè)某個(gè)包下的Logger,然后掛有幾個(gè)Appender,而程序中通過(guò)完全限定的類名(這個(gè)類屬于前面指定的包)取得Logger,那么當(dāng)這些Logger輸出日志的時(shí)候,其本身并沒(méi)有任何Appender,但是卻通過(guò)先輩節(jié)點(diǎn)定義的Appenders得以輸出。這里需要注意的是,如果在直系節(jié)點(diǎn)間定義相同的appender,似乎會(huì)多次重復(fù)輸出,因?yàn)槠鋾?huì)遍歷自身以及所有先輩節(jié)點(diǎn)的Appender list并且逐一進(jìn)行doAppend調(diào)用,而不會(huì)去排重,并且addAppender的時(shí)候也沒(méi)有遍歷先輩節(jié)點(diǎn)排重。


          Appender持有一個(gè)Filter鏈表,doAppend的時(shí)候首先走一遍過(guò)濾器,結(jié)果有3種,Filter.DENY(直接拒絕返回),Filter.ACCEPT(接收輸出請(qǐng)求,并且不再走之后的Filter),Filter.NEUTRAL(繼續(xù)執(zhí)行下一個(gè)Filter),順利通過(guò)Filter鏈之后,進(jìn)入真正的輸出日志過(guò)程,這邊以WriteAppender為例,首先將message通過(guò)持有的Layout進(jìn)行格式化(format),然后調(diào)用輸出流輸出日志到目標(biāo)文件(FileAppender)或者屏幕(ConsoleAppender)

          至此,整個(gè)日志輸出過(guò)程結(jié)束。

          下面兩幅圖分別是Log4j的整體類圖,不是非常完整,但是大概能夠了解到整個(gè)結(jié)構(gòu)。



          總結(jié)下,
          log4J出來(lái)已經(jīng)很多年了,以前只是使用下,并沒(méi)有去探究里面機(jī)制,但其某些機(jī)制還是相當(dāng)不錯(cuò)的。文中可能出現(xiàn)一些錯(cuò)誤,請(qǐng)各位能夠指出。

          posted on 2010-10-22 10:40 BucketLI 閱讀(4303) 評(píng)論(1)  編輯  收藏

          評(píng)論:
          # re: Log4j代碼隨讀 2012-05-15 11:28 | mabusyao
          最后一張圖,Appender并沒(méi)有在Hierarchy中被使用到。倒是AppenderAttachableImpl和Appender之間應(yīng)當(dāng)有關(guān)聯(lián)關(guān)系的。

          問(wèn)樓主個(gè)問(wèn)題,Hierarchy里面的RendererMap是干什么用的?  回復(fù)  更多評(píng)論
            

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 乐陵市| 临洮县| 广河县| 逊克县| 高邮市| 城步| 兰州市| 漯河市| 陆川县| 中西区| 手机| 密云县| 成安县| 砀山县| 田东县| 新丰县| 于都县| 辽宁省| 乌什县| 额敏县| 曲靖市| 赣州市| 华亭县| 灵宝市| 静安区| 巍山| 湖北省| 邓州市| 隆子县| 诏安县| 平果县| 革吉县| 灵丘县| 都江堰市| 汾西县| 永安市| 湖南省| 神农架林区| 海南省| 汉中市| 景洪市|