深入Log4J源码之Layout

Layout负责将LoggingEvent中的信息格式化成一行日志信息。对不同格式的日志可能还需要提供头和尾等信息。另外有些Layout不会处理异常信息,此时ignoresThrowable()方法返回false,并且异常信息需要Appender来处理,如PatternLayout。

Log4J自身实现了7个Layout类,我们可以通过继承自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类是所有Log4J中Layout的基类,它是一个抽象类,定义了Layout的接口。

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

2.       getContentType():定义日志文件的内容类型,目前在Log4J中只是在SMTPAppender中用到,用于设置发送邮件的邮件内容类型。而Layout本身也只有HTMLLayout实现了它。

3.       getHeader():定义日志文件的头,目前在Log4J中只是在HTMLLayout中实现了它。

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

5.       ignoresThrowable():定义当前layout是否处理异常类型。在Log4J中,不支持处理异常类型的有:TTCLayout、PatternLayout、SimpleLayout。

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


显示与当前线程相关联的NDC(Nested Diagnostic Context)信息。


X


显示和当前想成相关联的MDC(Mapped 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模块,则会出现一定的改动,虽然我也想不出更好的解决方法,但是总感觉这个不是一个比较好的方法,在我自己的项目中基本上没有用到这个特性。

时间: 2024-11-05 19:36:09

深入Log4J源码之Layout的相关文章

深入Log4J源码之SimpleLog

一个月前因为某些事情被困宁波三天,在酒店里闲着无事,然后开始看Log4J的源码.原本之后的一个星期就应该开始写了,无奈又遇到一些事情,迟迟没有动笔.感觉工作后要做好一件额外的事情总是很难,每天下班后才能看代码.写文章,而如果中途遇到一些没有预料到的事情就很容易不了了之了,所以现在如果出现能静下心来看代码.写文章的时间,我都是特别珍惜.我一直不知道如何开场一篇文章,所以先用一些废话做引子-.:( 在软件开发过程中,出现bug总是在所难免:事实上,以我个人经验,即使在实际开发阶段,fix bug时间

深入Log4J源码之Log4J Core

毕业又赶上本科的同学会,还去骑车环了趟崇明岛,六月貌似就没消停过,不过终于这些事情基本上都结束了,我也可以好好的看些书.读些源码.写点博客了. Log4J将写日志功能抽象成七个核心类/接口:Logger.LoggerRepository.Level.LoggingEvent.Appender.Layout.ObjectRender.其类图如下: 更详细的,实现Log4J主要功能相关的类图: 其实Log4J最核心的也就5个类:Logger用于对日志记录行为的抽象,提供记录不同级别日志的接口:Lev

深入Log4J源码之Appender

Appender负责定义日志输出的目的地,它可以是控制台(ConsoleAppender).文件(FileAppender).JMS服务器(JmsLogAppender).以Email的形式发送出去(SMTPAppender)等.Appender是一个命名的实体,另外它还包含了对Layout.ErrorHandler.Filter等引用:  1 public interface Appender { 2     void addFilter(Filter newFilter); 3     pu

深入Log4J源码之LoggerRepository和Configurator

LoggerRepository从字面上理解,它是一个Logger的容器,它会创建并缓存Logger实例,从而具有相同名字的Logger实例不会多次创建,以提高性能.它的这种特性有点类似Spring的IOC概念.Log4J支持两种配置文件:properties文件和xml文件.Configurator解析配置文件,并将解析后的信息添加到LoggerRepository中.LogManager最终将LoggerRepository和Configurator整合在一起. LoggerReposito

深入log4j源码

 slf4j即Simple logging facade for Java,其作用类似于JDBC,作为一个日志抽象层,它允许你在后台使用任意一个日志类库,比如log4j.如果是在编写供内外部都可以使用的API或者通用类库,那么你真不会希望使用你类库的客户端必须使用你选择的日志类库.常用的日志类库有log4j.logback等,本文就来深入了解一下log4j.  开发代码中,我们只需要在src下放上log4j.xml或log4j.properties,log4j就能自动找到该配置文件,web工程在

从Chrome源码看浏览器如何layout布局

假设有以下html/css: <div style="border:1px solid #000; width:50%; height: 100px; margin: 0 auto"></div> 这在浏览器上面将显示一个框: 为了画出这个框,首先要知道从哪里开始画.画多大,其次是边缘stroke的颜色,就可以把它画出来了: void draw(SkCanvas* canvas) { SkPaint paint; paint.setStrokeWidth(1);

深入perf4j源码

前言 引用一老程序员同事的一句话:"项目做到最后就是监控了."在一天和那同事打电话聊天时他突然冒出来的一句话.后来我仔细回味这句话,越来越觉得挺有道理的.自己在现在的项目里就做了好几个监控相关的任务,而且也一直在想办法获取更多的监控数据,如每个进程内存使用情况.线程使用状态.某些方法的性能等.不过这句话只说了一半,监控是为了获取数据,但是有了数据后还要根据这些数据来做相应的决策,比如判断机器.进程的健康状况,如何改进系统等.最近对性能调优特别感兴趣,但是在性能调优前首先要收集性能数据,

Spring源码分析:实现AOP(转载)

这两天一直在读spring1.2的AOP实现源码,AOP实现原理说起来很简单,对于实现业务接口的对象使用java代理机制实现,而对于一般的类使用cglib库实现,但spring的实现还是比较复杂的,不过抓住了本质去看代码就容易多了.发现一篇04年写的<spring源码分析:实现AOP>,倒是不用自己再写了,04年的时候已经有很多人研读过spring的源码,而那时的我还在学校,对java半懂不懂的状态,就算到现在也不敢说真的懂了,继续学习.努力.文章如下:     我的问题        为了完

vhd 转换为img源码,由VS 2010 C++编译

本工程主要由vhd2img.cpp layout.h组成,C代码实现. vhd2img.cpp: // vhd2img.cpp : 定义控制台应用程序的入口点. //by www.frombyte.cn zhangyu //北亚数据恢复中心(www.sjhf.net)张宇 2012/1/6 发表于51cto #include "stdafx.h" #include <windows.h> #include "layout.h" #define VHD_O