<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    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自身實現了7Layout類,我們可以通過繼承自Layout類以實現用戶自定義的日志消息格式。Log4J中已定義的Layout類結構如圖:


    測試類

    簡單的寫了一個功能性測試的類,從而對不同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():定義日志文件的內容類型,目前在Log4J中只是在SMTPAppender中用到,用于設置發送郵件的郵件內容類型。而Layout本身也只有HTMLLayout實現了它。

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

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

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

    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格式,Log4JHTMLLayout的實現中將每一條日志信息打印成表格中的一行,因而包含了一些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內容信息都要經過轉義,即: ’<’ => &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還支持設置是否支持打印位置信息以及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已經處理了異常信息。

    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_FORMATNULL,此時dateFormat字段為null

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

    ABS_TIME_DATE_FORMATABSOLUTE,此時dateFormat字段為AbsoluteTimeDateFormat實例。它將時間信息格式化成HH:mm:ss,SSS格式。這里對性能優化有一個可以參考的地方,即在格式化是,它只是每秒做一次格式化計算,而對后綴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中設置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

    個人感覺PatternLayoutLog4J中最常用也是最復雜的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解析出關于patternConvertersFormattingInfo兩個相對獨立的集合,遍歷這兩個集合,構建出兩個對應的數組,以在以后的解析中使用。大體上,EnhancedPatternLayout還是類似PatternLayout的設計。這里不再贅述。

    NDCMDC

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

    要實現這種功能,一個簡單的做法每個請求都有一個唯一的IDName,從而在處理這樣的請求的日志中每次都寫入該信息從而區分看似相同的日志。但是這種做法需要為每個日志打印語句添加相同的代碼,而且這個IDName信息要一直隨著方法調用傳遞下去,非常不方便,而且容易出錯。Log4J提供了兩種機制實現類似的需求: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實現中,使用一個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 }

    類似NDCMDC在使用前也需要向其添加數據,結束后將其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提供了NDCMDC機制,但是感覺它的實現還是有一定的侵入性的,如果要替換Log模塊,則會出現一定的改動,雖然我也想不出更好的解決方法,但是總感覺這個不是一個比較好的方法,在我自己的項目中基本上沒有用到這個特性。
    posted on 2012-07-04 01:05 DLevin 閱讀(5417) 評論(1)  編輯  收藏 所屬分類: Logging

    FeedBack:
    # re: 深入Log4J源碼之Layout
    2013-12-11 10:50 | eccentric lee
    StringBuffer本身是線程安全的吧  回復  更多評論
      
    主站蜘蛛池模板: 国产精品国产亚洲精品看不卡| 久久国产精品免费观看| 亚洲无圣光一区二区| 亚洲欧洲自拍拍偷午夜色无码| 午夜在线a亚洲v天堂网2019| 亚洲精品无码久久久影院相关影片| 色吊丝最新永久免费观看网站| 91高清免费国产自产| a级在线免费观看| 一级做a爰黑人又硬又粗免费看51社区国产精品视 | 亚洲另类无码专区丝袜| 亚洲成av人片不卡无码| 亚洲αv久久久噜噜噜噜噜| 亚洲欧洲日本在线| 国产性生交xxxxx免费| 成人au免费视频影院| 免费AA片少妇人AA片直播| 在线人成精品免费视频| 国产精品免费一区二区三区四区| a高清免费毛片久久| 日日狠狠久久偷偷色综合免费| 亚洲国产欧洲综合997久久| 亚洲制服在线观看| 亚洲日本香蕉视频| 亚洲视频中文字幕在线| 91亚洲国产成人久久精品网站| 亚洲日本中文字幕区| 亚洲AV人无码激艳猛片| 亚洲AV区无码字幕中文色| 亚洲av一综合av一区| 亚洲AV福利天堂一区二区三| 亚洲国产一区二区三区青草影视| 亚洲AV无码欧洲AV无码网站| 亚洲av鲁丝一区二区三区| 久久久久亚洲AV成人片| 亚洲成a人片在线网站| 亚洲1区1区3区4区产品乱码芒果| 亚洲国产精品综合福利专区| 亚洲jjzzjjzz在线播放| 亚洲熟妇成人精品一区| 亚洲日韩在线中文字幕综合|