Log4j源碼解析--Layout類解析


本文轉載上善若水的博客,原文出處:http://www.blogjava.net/DLevin/archive/2012/07/04/382131.html。感謝做者的分享。css

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

Log4J自身實現了7Layout類,咱們能夠經過繼承自Layout類以實現用戶自定義的日誌消息格式。Log4J中已定義的Layout類結構如圖:
java


測試類

簡單的寫了一個功能性測試的類,從而對不一樣Layout的輸出有比較直觀的瞭解。爲了簡單起見,全部的測試都打印到控制檯。
git

 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類是全部Log4J中Layout的基類,它是一個抽象類,定義了Layout的接口。apache

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

2.       getContentType():定義日誌文件的內容類型,目前在Log4J中只是在SMTPAppender中用到,用於設置發送郵件的郵件內容類型。而Layout自己也只有HTMLLayout實現了它。性能優化

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

4.       getFooter():定義日誌文件的尾,目前在Log4J中只是HTMLLayout中實現了它。多線程

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

6.       實現OptionHandler接口,該接口定義了一個activateOptions()方法,用於配置文件解析完後,同時應用全部配置,以解決有些配置存在依賴的狀況。該接口將在配置文件相關的小節中詳細介紹。

因爲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  }

測試結果:

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格式,Log4J中HTMLLayout的實現中將每一條日誌信息打印成表格中的一行,於是包含了一些Header和Footer信息。而且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內容信息都要通過轉義,即: ’<’ => &lt; ‘>’ => &gt; ‘&’ => &amp; ‘」’ => &quot;從上信息能夠看到HTMLLayout支持異常處理,而且它也實現了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還支持設置是否支持打印位置信息以及MDC(Mapped 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已經處理了異常信息。

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()函數代碼:

 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()函數,它是在其父類DateLayout中定義的,用於格式化時間信息。DateLayout支持的時間格式有:

NULL_DATE_FORMAT:NULL,此時dateFormat字段爲null

RELATIVE_TIME_DATE_FORMAT:RELATIVE,默認值,此時dateFormat字段爲RelativeTimeDateFormat實例。其實現即將LoggingEvent中的timestamp-startTime(RelativeTimeDateFormat實例化是初始化)。

ABS_TIME_DATE_FORMAT:ABSOLUTE,此時dateFormat字段爲AbsoluteTimeDateFormat實例。它將時間信息格式化成HH:mm:ss,SSS格式。這裏對性能優化有一個能夠參考的地方,即在格式化是,它只是每秒作一次格式化計算,而對後綴sss的變化則直接計算出來。

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

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

以及普通的SimpleDateFormat中設置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  }

測試結果:

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類

我的感受PatternLayout是Log4J中最經常使用也是最複雜的Layout了。PatternLayout的設計理念是LoggingEvent實例中全部的信息是否顯示、以何種格式顯示都是能夠自定義的,好比要用PatternLayout實現TTCCLayout中的格式,能夠這樣設置:

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  }

該測試用例的運行結果和TTCCLayout中默認的結果是同樣的。完整的,PatternLayout中能夠設置的參數有(模擬C語言的printf中的參數):

格式字符

結果

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)注:該參數會極大的影響性能,慎用。

L

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

m

顯示渲染後的日誌消息。

M

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

n

輸出平臺相關的換行符。

p

顯示日誌Level

r

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

t

顯示打印日誌對應的線程名稱。

x

顯示與當前線程相關聯的NDCNested Diagnostic Context)信息。

X

顯示和當前想成相關聯的MDCMapped Diagnostic Context)信息。

%

%%表達顯示%字符

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

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

於是PatternLayout實現中,最主要要解決的是如何解析上述定義的格式。實現上述格式的解析,一種最直觀的方法是每次遍歷格式字符串,當遇到’%’,則進入解析模式,根據’%’後不一樣的字符作不一樣的解析,對其餘字符,則直接做爲輸出的字符。這種代碼會比較直觀,可是它每次都要遍歷格式字符串,會引發一些性能問題,並且若是在未來引入新的格式字符,須要直接改動PatternLayout代碼,不利於可擴展性。

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


其中PatternParser負責解析PatternLayout中設置的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使用了有限狀態機的方法:

 

即PatternParser定義了五種狀態,初始化時LITERAL_STATE,當遍歷完成,則退出;不然,若是當前字符不是’%’,則將該字符添加到currentLiteral中,繼續遍歷;不然,若下一字符是’%’,則將其當作基本字符處理,若下一字符是’n’,則添加換行符,不然,將以前收集的literal字符建立LiteralPatternConverter實例,添加到相應的PatternConverter鏈中,清空currentLiteral實例,並添加下一字符,解析器進入CONVERTER_STATE狀態:

 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狀態,若當前字符是’-‘,則代表左對齊;若遇到’.’,則進入DOT_STATE狀態;若遇到數字,則進入MIN_STATE狀態;若遇到其餘字符,則根據字符解析出不一樣的PatternConverter,而且若是存在可選項信息(’{}’中的信息),一塊兒提取出來,並將狀態從新設置成LITERAL_STATE狀態:

 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狀態,首先判斷當期字符是否爲數字,如果,則繼續計算精度的最小值;若遇到’.’,則進入DOT_STATE狀態;不然,根據字符解析出不一樣的PatternConverter,而且若是存在可選項信息(’{}’中的信息),一塊兒提取出來,並將狀態從新設置成LITERAL_STATE狀態:

 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狀態,若是當前字符是數字,則進入MAX_STATE狀態;格式出錯,回到LITERAL_STATE狀態:

 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狀態,若爲數字,則繼續計算最大精度值,不然,根據字符解析出不一樣的PatternConverter,而且若是存在可選項信息(’{}’中的信息),一塊兒提取出來,並將狀態從新設置成LITERAL_STATE狀態:

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()方法的實現,只是簡單的根據不一樣的格式字符建立相應的PatternConverter,並且各個PatternConverter中的實現也是比較簡單的,有興趣的童鞋能夠直接看源碼,這裏再也不贅述。

PatternLayout的這種有限狀態機的設置是代碼結構更加清晰,而引入解釋器模式,之後若是須要增長新的格式字符,只須要添加一個新的PatternConverter以及一小段case語句塊便可,減小了由於需求改變而引發的代碼的傾入性。

EnhancedPatternLayout類

在Log4J文檔中指出PatternLayout中存在同步問題以及其餘問題,於是推薦使用EnhancedPatternLayout來替換它。對這句話我我的並無理解,首先關於同步問題,感受其餘Layout中也有涉及到,並且對一個Appender來講,它的doAppend()方法是同步方法,於是只要不在多個Appender之間共享同一個Layout實例,也不會出現同步問題;更使人費解的是關於其餘問題的表述,說實話,我尚未發現具體有什麼其餘問題,因此期待其餘人來幫我解答。

可是無論怎麼樣,咱們仍是來簡單的瞭解一下EnhancedPatternLayout的一些設計思想吧。EnhancedPatternLayout提供了和PatternLayout相同的接口,只是其內部實現有一些改變。EnhancedPatternLayout引入了LoggingEventPatternConverter,它會根據不一樣的子類的定義從LoggingEvent實例中獲取相應的信息;使用PatternParser解析出關於patternConverters和FormattingInfo兩個相對獨立的集合,遍歷這兩個集合,構建出兩個對應的數組,以在之後的解析中使用。大致上,EnhancedPatternLayout仍是相似PatternLayout的設計。這裏再也不贅述。

NDC和MDC

有時候,一段相同的代碼須要處理不一樣的請求,從而致使一些看似相同的日誌實際上是在處理不一樣的請求。爲了不這種狀況,從而使日誌可以提供更多的信息。

要實現這種功能,一個簡單的作法每一個請求都有一個惟一的ID或Name,從而在處理這樣的請求的日誌中每次都寫入該信息從而區分看似相同的日誌。可是這種作法須要爲每一個日誌打印語句添加相同的代碼,並且這個ID或Name信息要一直隨着方法調用傳遞下去,很是不方便,並且容易出錯。Log4J提供了兩種機制實現相似的需求:NDC和MDC。NDC是Nested Diagnostic Contexts的簡稱,它提供一個線程級別的棧,用戶向這個棧中壓入信息,這些信息能夠經過Layout顯示出來。MDC是Mapped 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實現中,使用一個Hashtable,其Key是線程實例,這樣的實現致使用戶須要手動的調用remove方法,移除那些push進去的數據以及移除那些已通過期的線程數據,否則就會出現內存泄露的狀況;另外,若是使用線程池,在沒有及時調用remove方法的狀況下,容易前一線程的數據影響後一線程的結果。很奇怪爲何這裏沒有ThreadLocal或者是WeakReference,這樣就能夠部分的解決忘記調用remove引發的後果,貌似是出於兼容性的考慮?

MDC使用了TheadLocal,於是它只能使用在JDK版本大於1.2的環境中,然而其代碼實現和接口也更加簡潔:

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  }

相似NDC,MDC在使用前也須要向其添加數據,結束後將其remove,可是remove操做不是必須的,由於它使用了TheadLocal,於是不會引發內存問題;不過它仍是可能在使用線程池的狀況下引發問題,除非線程池在每一次線程運行結束後或每一次線程運行前將ThreadLocal的數據清除:

 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提供了NDC和MDC機制,可是感受它的實現仍是有必定的侵入性的,若是要替換Log模塊,則會出現必定的改動,雖然我也想不出更好的解決方法,可是總感受這個不是一個比較好的方法,在我本身的項目中基本上沒有用到這個特性。
相關文章
相關標籤/搜索