上善若水
          In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
          posts - 146,comments - 147,trackbacks - 0

          Layout負責將LoggingEvent中的信息格式化成一行日志信息。對不同格式的日志可能還需要提供頭和尾等信息。另外有些Layout不會處理異常信息,此時ignoresThrowable()方法返回false,并且異常信息需要Appender來處理,如PatternLayout

          Log4J自身實現(xiàn)了7Layout類,我們可以通過繼承自Layout類以實現(xiàn)用戶自定義的日志消息格式。Log4J中已定義的Layout類結(jié)構(gòu)如圖:


          測試類

          簡單的寫了一個功能性測試的類,從而對不同Layout的輸出有比較直觀的了解。為了簡單起見,所有的測試都打印到控制臺。

           1 public class LayoutTest {
           2     private Logger root;    
           3     @Before
           4     public void setup() {
           5         root = LogManager.getRootLogger();
           6     }
           7     @Test
           8     public void testXXXLayout() {
           9         configSetup(new XXXLayout());
          10         logTest();
          11     }
          12     private void logTest() {
          13         Logger log = Logger.getLogger("levin.log4j.test.TestBasic");
          14         log.info("Begin to execute testBasic() method");
          15         log.info("Executing");
          16         try {
          17             throw new Exception("Deliberately throw an Exception");
          18         } catch(Exception e) {
          19             log.error("Catching an Exception", e);
          20         }
          21         log.info("Execute testBasic() method finished.");
          22     }
          23     private void configSetup(Layout layout) {
          24         root.addAppender(createConsoleAppender(layout));
          25     }
          26     private Appender createConsoleAppender(Layout layout) {
          27         return new ConsoleAppender(layout);
          28     }
          29 }

           

          Layout抽象類

          Layout類是所有Log4JLayout的基類,它是一個抽象類,定義了Layout的接口。

          1.       format()方法:將LoggingEvent類中的信息格式化成一行日志。

          2.       getContentType():定義日志文件的內(nèi)容類型,目前在Log4J中只是在SMTPAppender中用到,用于設(shè)置發(fā)送郵件的郵件內(nèi)容類型。而Layout本身也只有HTMLLayout實現(xiàn)了它。

          3.       getHeader():定義日志文件的頭,目前在Log4J中只是在HTMLLayout中實現(xiàn)了它。

          4.       getFooter():定義日志文件的尾,目前在Log4J中只是HTMLLayout中實現(xiàn)了它。

          5.       ignoresThrowable():定義當前layout是否處理異常類型。在Log4J中,不支持處理異常類型的有:TTCLayoutPatternLayoutSimpleLayout

          6.       實現(xiàn)OptionHandler接口,該接口定義了一個activateOptions()方法,用于配置文件解析完后,同時應(yīng)用所有配置,以解決有些配置存在依賴的情況。該接口將在配置文件相關(guān)的小節(jié)中詳細介紹。

          由于Layout接口定義比較簡單,因而其代碼也比較簡單:

           1 public abstract class Layout implements OptionHandler {
           2     public final static String LINE_SEP = System.getProperty("line.separator");
           3     public final static int LINE_SEP_LEN = LINE_SEP.length();
           4     abstract public String format(LoggingEvent event);
           5     public String getContentType() {
           6         return "text/plain";
           7     }
           8     public String getHeader() {
           9         return null;
          10     }
          11     public String getFooter() {
          12         return null;
          13     }
          14     abstract public boolean ignoresThrowable();
          15 }

          SimpleLayout

          SimpleLayout是最簡單的Layout,它只是打印消息級別和渲染后的消息,并且不處理異常信息。不過這里很奇怪為什么把sbuf作為成員變量?個人感覺這個會在多線程中引起問題~~~~其代碼如下:

           1 public String format(LoggingEvent event) {
           2     sbuf.setLength(0);
           3     sbuf.append(event.getLevel().toString());
           4     sbuf.append(" - ");
           5     sbuf.append(event.getRenderedMessage());
           6     sbuf.append(LINE_SEP);
           7     return sbuf.toString();
           8 }
           9 public boolean ignoresThrowable() {
          10     return true;
          11 }

          測試用例:

          1 @Test
          2 public void testSimpleLayout() {
          3     configSetup(new SimpleLayout());
          4     logTest();
          5 }

          測試結(jié)果:

          INFO - Begin to execute testBasic() method
          INFO 
          - Executing
          ERROR 
          - Catching an Exception
          java.lang.Exception: Deliberately 
          throw an Exception
              at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:
          48)
              at levin.log4j.layout.LayoutTest.testSimpleLayout(LayoutTest.java:
          25)
              
          INFO 
          - Execute testBasic() method finished.

          HTMLLayout

          HTMLLayout將日志消息打印成HTML格式,Log4JHTMLLayout的實現(xiàn)中將每一條日志信息打印成表格中的一行,因而包含了一些HeaderFooter信息。并且HTMLLayout類還支持配置是否打印位置信息和自定義title。最終HTMLLayout的日志打印格式如下:

          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
          <html>
          <head>
          <title>${title}</title>
          <style type="text/css">
          <!--
          body, table {font
          -family: arial,sans-serif; font-size: x-small;}
          th {background: #
          336699; color: #FFFFFF; text-align: left;}
          -->
          </style>
          </head>
          <body bgcolor="#FFFFFF" topmargin="6" leftmargin="6">
          <hr size="1" noshade>
          Log session start time ${currentTime}
          <br>
          <br>
          <table cellspacing="0" cellpadding="4" border="1" bordercolor="#224466" width="100%">
          <tr>
          <th>Time</th>
          <th>Thread</th>
          <th>Level</th>
          <th>Category</th>
          <th>File:Line</th>
          <th>Message</th>
          </tr>

          <tr>
          <td>${timeElapsedFromStart}</td>
          <td title="${threadName} thread">${theadName}</td>
          <td title="Level">
          #
          if(${level} == “DEBUG”)
              
          <font color="#339933">DEBUG</font>
          #elseif(${level} 
          >= “WARN”)
              
          <font color=”#993300><strong>${level}</Strong></font>
          #
          else
          ${level}
          </td>
          <td title="${loggerName} category">levin.log4j.test.TestBasic</td>
          <td>${fileName}:${lineNumber}</td>
          <td title="Message">${renderedMessage}</td>
          </tr>

          <tr><td bgcolor="#EEEEEE" style="font-size : xx-small;" colspan="6" title="Nested Diagnostic Context">NDC: ${NDC}</td></tr>

          <tr><td bgcolor="#993300" style="color:White; font-size : xx-small;" colspan="6">java.lang.Exception: Deliberately throw an Exception
          <br>&nbsp;&nbsp;&nbsp;&nbsp;    at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:51)
          <br>&nbsp;&nbsp;&nbsp;&nbsp;    at levin.log4j.layout.LayoutTest.testHTMLLayout(LayoutTest.java:34)

          </td></tr>

          以上所有HTML內(nèi)容信息都要經(jīng)過轉(zhuǎn)義,即: ’<’ => &lt; ‘>’ => &gt; ‘&’ => &amp; ‘”’ => &quot;從上信息可以看到HTMLLayout支持異常處理,并且它也實現(xiàn)了getContentType()方法:

          1 public String getContentType() {
          2     return "text/html";
          3 }
          4 public boolean ignoresThrowable() {
          5     return false;
          6 }

          測試用例:

          1 @Test
          2 public void testHTMLLayout() {
          3     HTMLLayout layout = new HTMLLayout();
          4     layout.setLocationInfo(true);
          5     layout.setTitle("Log4J Log Messages HTMLLayout test");
          6     configSetup(layout);
          7     logTest();
          8 }

          XMLLayout

          XMLLayout將日志消息打印成XML文件格式,打印出的XML文件不是一個完整的XML文件,它可以外部實體引入到一個格式正確的XML文件中。如XML文件的輸出名為abc,則可以通過以下方式引入:

          <?xml version="1.0" ?>
            
          <!DOCTYPE log4j:eventSet PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd" [<!ENTITY data SYSTEM "abc">]>
            
          <log4j:eventSet version="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/">
                    
          &data;
          </log4j:eventSet>

          XMLLayout還支持設(shè)置是否支持打印位置信息以及MDCMapped Diagnostic Context)信息,他們的默認值都為false

          1 private boolean locationInfo = false;
          2 private boolean properties = false;

          XMLLayout的輸出格式如下:

          <log4j:event logger="${loggerName}" timestamp="${eventTimestamp}" level="${Level}" thread="${threadName}">
          <log4j:message><![CDATA[${renderedMessage}]]></log4j:message>
          #
          if ${ndc} != null
          <log4j:NDC><![CDATA[${ndc}]]</log4j:NDC>
          #endif
          #
          if ${throwableInfo} != null
          <log4j:throwable><![CDATA[java.lang.Exception: Deliberately throw an Exception
              at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:
          54)
              at levin.log4j.layout.LayoutTest.testXMLLayout(LayoutTest.java:
          43)
              
          ]]
          ></log4j:throwable>
          #endif
          #
          if ${locationInfo} != null
          <log4j:locationInfo class="${className}" method="${methodName}" file="${fileName}" line="${lineNumber}"/>
          #endif
          #
          if ${properties} != null
          <log4j:properties>
          #foreach ${key} in ${keyset}
          <log4j:data name=”${key}” value=”${propValue}”/>
          #end
          </log4j:properties>
          #endif
          </log4j:event>

          從以上日志格式也可以看出XMLLayout已經(jīng)處理了異常信息。

          1 public boolean ignoresThrowable() {
          2     return false;
          3 }

          測試用例:

          1 @Test
          2 public void testXMLLayout() {
          3     XMLLayout layout = new XMLLayout();
          4     layout.setLocationInfo(true);
          5     layout.setProperties(true);
          6     configSetup(layout);
          7     logTest();
          8 }

          TTCCLayout

          TTCCLayout貌似有特殊含義,不過這個我還不太了解具體是什么意思。從代碼角度上,該Layout包含了time, thread, category, nested diagnostic context information, and rendered message等信息。其中是否打印thread(threadPrinting), category(categoryPrefixing), nested diagnostic(contextPrinting)信息是可以配置的。TTCCLayout不處理異常信息。其中format()函數(shù)代碼:

           1 public String format(LoggingEvent event) {
           2     buf.setLength(0);
           3     dateFormat(buf, event);
           4     if (this.threadPrinting) {
           5         buf.append('[');
           6         buf.append(event.getThreadName());
           7         buf.append("");
           8     }
           9     buf.append(event.getLevel().toString());
          10     buf.append(' ');
          11     if (this.categoryPrefixing) {
          12         buf.append(event.getLoggerName());
          13         buf.append(' ');
          14     }
          15     if (this.contextPrinting) {
          16         String ndc = event.getNDC();
          17         if (ndc != null) {
          18             buf.append(ndc);
          19             buf.append(' ');
          20         }
          21     }
          22     buf.append("");
          23     buf.append(event.getRenderedMessage());
          24     buf.append(LINE_SEP);
          25     return buf.toString();
          26 }

          這里唯一需要解釋的就是dateFormat()函數(shù),它是在其父類DateLayout中定義的,用于格式化時間信息。DateLayout支持的時間格式有:

          NULL_DATE_FORMATNULL,此時dateFormat字段為null

          RELATIVE_TIME_DATE_FORMATRELATIVE,默認值,此時dateFormat字段為RelativeTimeDateFormat實例。其實現(xiàn)即將LoggingEvent中的timestamp-startTimeRelativeTimeDateFormat實例化是初始化)。

          ABS_TIME_DATE_FORMATABSOLUTE,此時dateFormat字段為AbsoluteTimeDateFormat實例。它將時間信息格式化成HH:mm:ss,SSS格式。這里對性能優(yōu)化有一個可以參考的地方,即在格式化是,它只是每秒做一次格式化計算,而對后綴sss的變化則直接計算出來。

          DATE_AND_TIME_DATE_FORMATDATE,此時dateFormat字段為DateTimeDateFormat實例,此時它將時間信息格式化成dd MMM yyyy HH:mm:ss,SSS

          ISO8601_DATE_FORMATISO8601,此時dateFormat字段為ISO8601DateFormat實例,它將時間信息格式化成yyyy-MM-dd HH:mm:ss,SSS

          以及普通的SimpleDateFormat中設(shè)置pattern的支持。

          Log4J推薦使用自己定義的DateFormat,其文檔上說Log4J中定義的DateFormat信息有更好的性能。

          測試用例:

          1 @Test
          2 public void testTTCCLayout() {
          3     TTCCLayout layout = new TTCCLayout();
          4     layout.setDateFormat("ISO8601");
          5     configSetup(layout);
          6     logTest();
          7 }

          測試結(jié)果:

          2012-07-02 23:07:34,017 [main] INFO levin.log4j.test.TestBasic - Begin to execute testBasic() method
          2012-07-02 23:07:34,018 [main] INFO levin.log4j.test.TestBasic - Executing
          2012-07-02 23:07:34,019 [main] ERROR levin.log4j.test.TestBasic - Catching an Exception
          java.lang.Exception: Deliberately 
          throw an Exception
              at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:
          63)
              
          2012-07-02 23:07:34,022 [main] INFO levin.log4j.test.TestBasic - Execute testBasic() method finished.

          PatternLayout

          個人感覺PatternLayoutLog4J中最常用也是最復(fù)雜的Layout了。PatternLayout的設(shè)計理念是LoggingEvent實例中所有的信息是否顯示、以何種格式顯示都是可以自定義的,比如要用PatternLayout實現(xiàn)TTCCLayout中的格式,可以這樣設(shè)置:

          1 @Test
          2 public void testPatternLayout() {
          3     PatternLayout layout = new PatternLayout();
          4     layout.setConversionPattern("%r [%t] %p %c %x - %m%n");
          5     configSetup(layout);
          6     logTest();
          7 }

          該測試用例的運行結(jié)果和TTCCLayout中默認的結(jié)果是一樣的。完整的,PatternLayout中可以設(shè)置的參數(shù)有(模擬C語言的printf中的參數(shù)):

          格式字符

          結(jié)果

          c

          顯示logger name,可以配置精度,如%c{2},從后開始截取。

          C

          顯示日志寫入接口的雷鳴,可以配置精度,如%C{1},從后開始截取。注:會影響性能,慎用。

          d

          顯示時間信息,后可定義格式,如%d{HH:mm:ss,SSS}或Log4J中定義的格式,如%d{ISO8601}%d{ABSOLUTE}Log4J中定義的時間格式有更好的性能。

          F

          顯示文件名,會影響性能,慎用。

          l

          顯示日志打印是的詳細位置信息,一般格式為full.qualified.caller.class.method(filename:lineNumber)注:該參數(shù)會極大的影響性能,慎用。

          L

          顯示日志打印所在源文件的行號。注:該參數(shù)會極大的影響性能,慎用。

          m

          顯示渲染后的日志消息。

          M

          顯示打印日志所在的方法名。注:該參數(shù)會極大的影響性能,慎用。

          n

          輸出平臺相關(guān)的換行符。

          p

          顯示日志Level

          r

          顯示相對時間,即從程序開始(實際上是初始化LoggingEvent類)到日志打印的時間間隔,以毫秒為單位。

          t

          顯示打印日志對應(yīng)的線程名稱。

          x

          顯示與當前線程相關(guān)聯(lián)的NDCNested Diagnostic Context)信息。

          X

          顯示和當前想成相關(guān)聯(lián)的MDCMapped Diagnostic Context)信息。

          %

          %%表達顯示%字符

          而且PatternLayout還支持在格式字符串前加入精度信息:

          %-min.max[conversionChar],如%-20.30c表示顯示日志名,左對齊,最短20個字符,最長30個字符,不足用空格補齊,超過的截取(從后往前截取)。

          因而PatternLayout實現(xiàn)中,最主要要解決的是如何解析上述定義的格式。實現(xiàn)上述格式的解析,一種最直觀的方法是每次遍歷格式字符串,當遇到’%’,則進入解析模式,根據(jù)’%’后不同的字符做不同的解析,對其他字符,則直接作為輸出的字符。這種代碼會比較直觀,但是它每次都要遍歷格式字符串,會引起一些性能問題,而且如果在將來引入新的格式字符,需要直接改動PatternLayout代碼,不利于可擴展性。

          為了解決這個問題,PatternLayout引入了解釋器模式:


          其中PatternParser負責解析PatternLayout中設(shè)置的conversion pattern,它將conversion pattern解析出一個鏈狀的PatternConverter,而后在每次格式化LoggingEvent實例是,只需要遍歷該鏈即可:

          1 public String format(LoggingEvent event) {
          2     PatternConverter c = head;
          3     while (c != null) {
          4         c.format(sbuf, event);
          5         c = c.next;
          6     }
          7     return sbuf.toString();
          8 }

          在解析conversion pattern時,PatternParser使用了有限狀態(tài)機的方法:

           

          PatternParser定義了五種狀態(tài),初始化時LITERAL_STATE,當遍歷完成,則退出;否則,如果當前字符不是’%’,則將該字符添加到currentLiteral中,繼續(xù)遍歷;否則,若下一字符是’%’,則將其當做基本字符處理,若下一字符是’n’,則添加換行符,否則,將之前收集的literal字符創(chuàng)建LiteralPatternConverter實例,添加到相應(yīng)的PatternConverter鏈中,清空currentLiteral實例,并添加下一字符,解析器進入CONVERTER_STATE狀態(tài):

           1 case LITERAL_STATE:
           2     // In literal state, the last char is always a literal.
           3     if (i == patternLength) {
           4         currentLiteral.append(c);
           5         continue;
           6     }
           7     if (c == ESCAPE_CHAR) {
           8         // peek at the next char.
           9         switch (pattern.charAt(i)) {
          10         case ESCAPE_CHAR:
          11             currentLiteral.append(c);
          12             i++// move pointer
          13             break;
          14         case 'n':
          15             currentLiteral.append(Layout.LINE_SEP);
          16             i++// move pointer
          17             break;
          18         default:
          19             if (currentLiteral.length() != 0) {
          20                 addToList(new LiteralPatternConverter(
          21                         currentLiteral.toString()));
          22                 // LogLog.debug("Parsed LITERAL converter: \""
          23                 // +currentLiteral+"\".");
          24             }
          25             currentLiteral.setLength(0);
          26             currentLiteral.append(c); // append %
          27             state = CONVERTER_STATE;
          28             formattingInfo.reset();
          29         }
          30     } else {
          31         currentLiteral.append(c);
          32     }
          33     break;

          CONVERTER_STATE狀態(tài),若當前字符是’-‘,則表明左對齊;若遇到’.’,則進入DOT_STATE狀態(tài);若遇到數(shù)字,則進入MIN_STATE狀態(tài);若遇到其他字符,則根據(jù)字符解析出不同的PatternConverter,并且如果存在可選項信息(’{}’中的信息),一起提取出來,并將狀態(tài)重新設(shè)置成LITERAL_STATE狀態(tài):

           1 case CONVERTER_STATE:
           2     currentLiteral.append(c);
           3     switch (c) {
           4     case '-':
           5         formattingInfo.leftAlign = true;
           6         break;
           7     case '.':
           8         state = DOT_STATE;
           9         break;
          10     default:
          11         if (c >= '0' && c <= '9') {
          12             formattingInfo.min = c - '0';
          13             state = MIN_STATE;
          14         } else
          15             finalizeConverter(c);
          16     } // switch
          17     break;

          進入MIN_STATE狀態(tài),首先判斷當期字符是否為數(shù)字,若是,則繼續(xù)計算精度的最小值;若遇到’.’,則進入DOT_STATE狀態(tài);否則,根據(jù)字符解析出不同的PatternConverter,并且如果存在可選項信息(’{}’中的信息),一起提取出來,并將狀態(tài)重新設(shè)置成LITERAL_STATE狀態(tài):

           1 case MIN_STATE:
           2     currentLiteral.append(c);
           3     if (c >= '0' && c <= '9')
           4         formattingInfo.min = formattingInfo.min * 10 + (c - '0');
           5     else if (c == '.')
           6         state = DOT_STATE;
           7     else {
           8         finalizeConverter(c);
           9     }
          10     break;

          進入DOT_STATE狀態(tài),如果當前字符是數(shù)字,則進入MAX_STATE狀態(tài);格式出錯,回到LITERAL_STATE狀態(tài):

           1 case DOT_STATE:
           2     currentLiteral.append(c);
           3     if (c >= '0' && c <= '9') {
           4         formattingInfo.max = c - '0';
           5         state = MAX_STATE;
           6     } else {
           7         LogLog.error("Error occured in position " + i
           8                 + ".\n Was expecting digit, instead got char \""
           9                 + c + "\".");
          10         state = LITERAL_STATE;
          11     }
          12     break;

          進入MAX_STATE狀態(tài),若為數(shù)字,則繼續(xù)計算最大精度值,否則,根據(jù)字符解析出不同的PatternConverter,并且如果存在可選項信息(’{}’中的信息),一起提取出來,并將狀態(tài)重新設(shè)置成LITERAL_STATE狀態(tài):

          1 case MAX_STATE:
          2     currentLiteral.append(c);
          3     if (c >= '0' && c <= '9')
          4         formattingInfo.max = formattingInfo.max * 10 + (c - '0');
          5     else {
          6         finalizeConverter(c);
          7         state = LITERAL_STATE;
          8     }
          9     break;

          finalizeConvert()方法的實現(xiàn),只是簡單的根據(jù)不同的格式字符創(chuàng)建相應(yīng)的PatternConverter,而且各個PatternConverter中的實現(xiàn)也是比較簡單的,有興趣的童鞋可以直接看源碼,這里不再贅述。

          PatternLayout的這種有限狀態(tài)機的設(shè)置是代碼結(jié)構(gòu)更加清晰,而引入解釋器模式,以后如果需要增加新的格式字符,只需要添加一個新的PatternConverter以及一小段case語句塊即可,減少了因為需求改變而引起的代碼的傾入性。

          EnhancedPatternLayout

          Log4J文檔中指出PatternLayout中存在同步問題以及其他問題,因而推薦使用EnhancedPatternLayout來替換它。對這句話我個人并沒有理解,首先關(guān)于同步問題,感覺其他Layout中也有涉及到,而且對一個Appender來說,它的doAppend()方法是同步方法,因而只要不在多個Appender之間共享同一個Layout實例,也不會出現(xiàn)同步問題;更令人費解的是關(guān)于其他問題的表述,說實話,我還沒有發(fā)現(xiàn)具體有什么其他問題,所以期待其他人來幫我解答。

          但是不管怎么樣,我們還是來簡單的了解一下EnhancedPatternLayout的一些設(shè)計思想吧。EnhancedPatternLayout提供了和PatternLayout相同的接口,只是其內(nèi)部實現(xiàn)有一些改變。EnhancedPatternLayout引入了LoggingEventPatternConverter,它會根據(jù)不同的子類的定義從LoggingEvent實例中獲取相應(yīng)的信息;使用PatternParser解析出關(guān)于patternConvertersFormattingInfo兩個相對獨立的集合,遍歷這兩個集合,構(gòu)建出兩個對應(yīng)的數(shù)組,以在以后的解析中使用。大體上,EnhancedPatternLayout還是類似PatternLayout的設(shè)計。這里不再贅述。

          NDCMDC

          有時候,一段相同的代碼需要處理不同的請求,從而導(dǎo)致一些看似相同的日志其實是在處理不同的請求。為了避免這種情況,從而使日志能夠提供更多的信息。

          要實現(xiàn)這種功能,一個簡單的做法每個請求都有一個唯一的IDName,從而在處理這樣的請求的日志中每次都寫入該信息從而區(qū)分看似相同的日志。但是這種做法需要為每個日志打印語句添加相同的代碼,而且這個IDName信息要一直隨著方法調(diào)用傳遞下去,非常不方便,而且容易出錯。Log4J提供了兩種機制實現(xiàn)類似的需求:NDCMDCNDCNested Diagnostic Contexts的簡稱,它提供一個線程級別的棧,用戶向這個棧中壓入信息,這些信息可以通過Layout顯示出來。MDCMapped Diagnostic Contexts的簡稱,它提供了一個線程級別的Map,用戶向這個Map中添加鍵值對信息,這些信息可以通過Layout以指定Key的方式顯示出來。

          NDC主要的使用接口有:

          1 public class NDC {
          2     public static String get();
          3     public static String pop();
          4     public static String peek();
          5     public static void push(String message);
          6     public static void remove();
          7 }

          即使用前,將和當前上下文信息push如當前線程棧,使用完后pop出來:

           1 @Test
           2 public void testNDC() {
           3     PatternLayout layout = new PatternLayout();
           4     layout.setConversionPattern("%x - %m%n");
           5     configSetup(layout);
           6     
           7     NDC.push("Levin");
           8     NDC.push("Ding");
           9     logTest();
          10     NDC.pop();
          11     NDC.pop();
          12 }
          13 Levin Ding - Begin to execute testBasic() method
          14 Levin Ding - Executing
          15 Levin Ding - Catching an Exception
          16 java.lang.Exception: Deliberately throw an Exception
          17     at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:86)
          18     
          19 Levin Ding - Execute testBasic() method finished.

          NDC所有的操作都是針對當前線程的,因而不會影響其他線程。而在NDC實現(xiàn)中,使用一個Hashtable,其Key是線程實例,這樣的實現(xiàn)導(dǎo)致用戶需要手動的調(diào)用remove方法,移除那些push進去的數(shù)據(jù)以及移除那些已經(jīng)過期的線程數(shù)據(jù),不然就會出現(xiàn)內(nèi)存泄露的情況;另外,如果使用線程池,在沒有及時調(diào)用remove方法的情況下,容易前一線程的數(shù)據(jù)影響后一線程的結(jié)果。很奇怪為什么這里沒有ThreadLocal或者是WeakReference,這樣就可以部分的解決忘記調(diào)用remove引起的后果,貌似是出于兼容性的考慮?

          MDC使用了TheadLocal,因而它只能使用在JDK版本大于1.2的環(huán)境中,然而其代碼實現(xiàn)和接口也更加簡潔:

          1 public class MDC {
          2     public static void put(String key, Object o);
          3     public static Object get(String key);
          4     public static void remove(String key);
          5     public static void clear();
          6 }

          類似NDCMDC在使用前也需要向其添加數(shù)據(jù),結(jié)束后將其remove,但是remove操作不是必須的,因為它使用了TheadLocal,因而不會引起內(nèi)存問題;不過它還是可能在使用線程池的情況下引起問題,除非線程池在每一次線程運行結(jié)束后或每一次線程運行前將ThreadLocal的數(shù)據(jù)清除:

           1 @Test
           2 public void testMDC() {
           3     PatternLayout layout = new PatternLayout();
           4     layout.setConversionPattern("IP:%X{ip} Name:%X{name} - %m%n");
           5     configSetup(layout);
           6     
           7     MDC.put("ip""127.0.0.1");
           8     MDC.put("name""levin");
           9     logTest();
          10     MDC.remove("ip");
          11     MDC.remove("name");
          12 }
          13 IP:127.0.0.1 Name:levin - Begin to execute testBasic() method
          14 IP:127.0.0.1 Name:levin - Executing
          15 IP:127.0.0.1 Name:levin - Catching an Exception
          16 java.lang.Exception: Deliberately throw an Exception
          17     at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:100)
          18     
          19 IP:127.0.0.1 Name:levin - Execute testBasic() method finished.
          雖然Log4J提供了NDCMDC機制,但是感覺它的實現(xiàn)還是有一定的侵入性的,如果要替換Log模塊,則會出現(xiàn)一定的改動,雖然我也想不出更好的解決方法,但是總感覺這個不是一個比較好的方法,在我自己的項目中基本上沒有用到這個特性。
          posted on 2012-07-04 01:05 DLevin 閱讀(5425) 評論(1)  編輯  收藏 所屬分類: Logging

          FeedBack:
          # re: 深入Log4J源碼之Layout
          2013-12-11 10:50 | eccentric lee
          StringBuffer本身是線程安全的吧  回復(fù)  更多評論
            
          主站蜘蛛池模板: 兴城市| 京山县| 慈利县| 石楼县| 巧家县| 辰溪县| 茂名市| 长乐市| 双柏县| 奉节县| 临潭县| 庆云县| 梁平县| 循化| 湖南省| 阿瓦提县| 枞阳县| 辉县市| 兖州市| 云安县| 保康县| 信阳市| 上思县| 白山市| 石家庄市| 太仆寺旗| 昌黎县| 延川县| 新津县| 永年县| 靖西县| 文化| 错那县| 南通市| 邵武市| 上思县| 井陉县| 广昌县| 泰安市| 婺源县| 汉寿县|