深入Log4J源码之Layout

深入探究Apache Log4j API及源码 Logger组件是Log4j框架中负责记录日志的核心部分。它是开发者与日志系统交互的接口,用于输出不同级别(如DEBUG、INFO、WARN、ERROR等)的日志消息。Log4j支持多层次的日志记录,Logger可以被组织成树状的层级结构。通过合理配置Logger,开发者可以控制日志输出的粒度和范围,这对于调试程序和事后分析问题具有重要意义。Logger组件通过继承关系实现父子结构,若未明确指定Logger的父级,则默认继承自根Logger(root Logger)。 阅读详情

源自:http://www.blogjava.net/DLevin/archive/2012/07/04/382131.html


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



深入理解Log4j源码与日志系统设计 在信息技术飞速发展的今天,日志管理已成为开发和运维工作中不可或缺的一部分。Log4j作为 Apache 软件基金会旗下一个开源的日志处理库,在Java社区中占据着举足轻重的地位。它不仅能够帮助开发者记录应用运行时的各种信息,还能在性能监控和问题诊断中发挥关键作用。本章将对Log4j项目做一个基础介绍,让我们从宏观上了解它的功能、版本演进以及在行业中的应用情况。随着章节的深入,我们将逐步探索Log4j的核心组件及其设计哲学,为深入理解其源码与高级用法打下坚实的基础。 阅读详情

相关推荐

log4j-jsonlayout

适用于log4j1的jsonlayout,可以将日志信息输出为json字符串格式。

log4j Layout简介说明

log4j layout输出配置简介说明

qq_25073223的博客 507

Unity游戏开发客户端面经——设计模式(初级)

Unity游戏开发客户端面经 之 设计模式 篇

Sea3752的博客 9426

Log4j详细介绍(七)----日志格式化器Layout

日志格式化器Layout负责格式化日志信息。方法log.error()的参数只包含日志信息,利用Layout可以附加其他信息,以输出更多的信息或者布局显示。          PatternLayout布局          PatternLayout是最常用的格式化器,用户可以自定义输出信息,例如日期,时间,所在的线程,类名,文件名,方法名,信息级别,文件级别,文件行数,例如: 1...

GarfieldEr007的专栏 2298

三菱JET伺服限位、原点信号通过PLC刷新设置方法

RWw及RWr软元件的初始设定值,可通过 [Pr. PA01.0 控制模式选择] 及 [Pr. PN22.0 默认映射模式选择] 的值进行切换。JET伺服参数用于CC-Link IE现场网络Basic。循环通信的请求报文与响应报文的收发数据被换读为伺服放大器的对象数据 (RWwn、RWrn)。接下来看映射字2D05具体的内容: 也就是2D05操作字的第九位、第十位、第十一位。响应报文的设定值可进行变更。变更初始设定值时,应变更PDO映射。不同的模式,对应不同的刷新映射内容。GXWORKS3程序。

qq_51166033的博客 8048

PowerDesigner16.5下载和安装教程

一、软件下载 1.下载地址(不用复制,直接点击下载链接即可):链接:https://pan.baidu.com/s/1MSnWHWt3SO2Uz5dt9AANAg提取码:z9kk 二、软件安装 1.下载安装包(包含安装文件、汉化包、注册文件) 2.下载后文件内容如下 3....

cicong2374的博客 10万+

Java日志框架 -- LOG4J(Log4j入门案例、日志级别、Log4j组件(Loggers、Appenders、Layouts)、配置文件、内置日志记录、自定义Logger)

Log4j是Apache下的一款开源的日志框架,通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程。方便项目的调试。官方网站: http://logging.apache.org/log4j/1.2/ Log4jTest.java 出现这个的原因是因为我们没有正确的初始化 一般只使用4个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUGLog4J 主要

天行健 君子以自强不息,地势坤 君子以厚德载物。 5469

ST MotorControl Workbench 6.2.1 自定义硬件配置避坑指南

本文详细介绍了ST MotorControl Workbench 6.2.1在自定义硬件配置中的常见问题与解决方案,涵盖功率板配置、采样电路校准、电机参数设置及代码生成避坑指南,帮助开发者高效完成电机控制项目开发。

red88的博客 1023

Log4j扩展使用--日志格式化器Layout

Log4j扩展使用--日志格式化器Layout

qq_44947859的博客 580

【异常】Warp终端AI功能403封禁报错I‘m sorry, I couldn‘t complete that request.Request failed with error: ErrorSta

本文针对Warp终端出现的AI功能定向封禁报错,完整还原脱敏后的报错信息、深度解读报错本质,并提供分优先级的全流程可落地解决方案,可直接用于技术排查、内部知识库归档。

奶爸的编程之路,也就一周冷个三天 265

论文分栏前后内容不连续?教你word如何删除分节符

问题 当我们在写论文的时候,很多期刊的模板都是两栏的格式,但有的时候我们插入的图像尺寸可能太大,两栏格式放不下,因此需要将其以一栏的格式出入,如下: 但当我们把图片删除后,再将其转换成两栏格式,前后内容就出现不连续的情况: 正常情况下应该是如下: 正确处理方式 里面有个分页符,把分页符去掉,或者把你的文字全部复制到记事本里,然后重新复制回word就可以取消原有的格式了。 删除分节符的方法(以word 2019为例) -点击菜单栏上的“视图”,点击“大纲视图”。 可以看到word文档以整列的形式展开:

左手の明天的博客 1万+

log4j 源码解析_Log4j源码解析--Layout类解析

Layout抽象类Layout类是所有Log4J中Layout的基类,它是一个抽象类,定义了Layout的接口。1.format()方法:将LoggingEvent类中的信息格式化成一行日志。2.getContentType():定义日志文件的内容类型,目前在Log4J中只是在SMTPAppender中用到,用于设置发送邮件的邮件内容类型。而Layout本身也只有HTMLLayout实现了它。3....

weixin_39633437的博客 351

RKNN 端侧部署必看:YOLOv11 Pose 姿态估计模型转换与性能优化实战

随着人工智能技术在边缘计算领域的快速发展,姿态估计作为计算机视觉的重要分支,在智能监控、人机交互、体感游戏等场景中展现出巨大的应用潜力。本文深入研究了YOLOv11 Pose模型在Rockchip RKNN平台上的部署方案,从理论基础、模型转换、平台优化到实际部署进行了全面阐述。通过详细的技术分析和实验验证,证明了RKNN平台能够有效提升YOLOv11 Pose的推理性能,在RK3588硬件平台上实现了高达100 FPS的实时姿态检测,为边缘计算场景下的姿态估计应用提供了高效的解决方案。

m0_71589036的博客 183

纯java代码实现log4j_深入Log4J源码之Layout

Layout抽象类Layout类是所有Log4J中Layout的基类,它是一个抽象类,定义了Layout的接口。1.format()方法:将LoggingEvent类中的信息格式化成一行日志。2.getContentType():定义日志文件的内容类型,目前在Log4J中只是在SMTPAppender中用到,用于设置发送邮件的邮件内容类型。而Layout本身也只有HTMLLayout实现了它。3....

weixin_32051661的博客 242

16、二阶差分功耗分析(DPA)的再审视

本文深入探讨了二阶差分功耗分析(DPA)中的关键问题,包括不同组合函数的性能、信息论分析以及在有轮廓和无轮廓攻击场景下的实验结果。通过对掩码AES和DES的模拟和实际攻击实验,研究了归一化乘积、求和和绝对差值等组合函数在不同噪声水平下的效率,并强调了信息论分析在评估侧信道对抗措施中的重要性。

leaf8的博客 117

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Log4j源码解析--Layout类解析

本文转载上善若水的博客,原文出处:http://www.blogjava.net/DLevin/archive/2012/07/04/382131.html。感谢作者的分享。 Layout负责将LoggingEvent中的信息格式化成一行日志信息。对不同格式的日志可能还需要提供头和尾等信息。另外有些Layout不会处理异常信息,此时ignoresThrowable()方法返回false,

LinkinPark的博客 1109

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

log4j源码解析

(一)几个基本概念 Logger - 日志写出器,供程序员输出日志信息 Appender - 日志目的地,把格式化好的日志信息输出到指定的地方去 ConsoleAppender - 目的地为控制台的Appender FileAppender - 目的地为文件的Appender RollingFileAppender - 目的地为大小受限的文件的Appender Layout

jidubaozou的专栏 5980

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

log4j 源码解析_log4j2源码分析

目录1.概述1.1.组件概览1.2.灵活的配置1.2.1.插件发现机制1.2.2.插件装配机制1.2.3.配置文件基本元素与对象的映射关系2.属性占位符2.1.概述2.2.Interpolator插值器2.3.默认属性配置3.Logger3.1.配置示例3.2.配置详解3.3.Logger继承机制4.Appender4.1.概述4.2.框架支持的Appender实现4.3.常用Appender详解...

weixin_39995280的博客 1206

Log4j源码分析

Log4j作为java领域最早的日志工具,其出现填补了行业空白,也为JUL的出现提供了参考,JUL的设计大多参考了Log4j的思路。Logger作为日志记录器,其定义了日志级别,绑定了多个Appender,所有的Logger都交给LoggerRepository管理,在内存中所有的Logger构建成以RootLogger为根节点的单根树形结构,通过RootLogger能方便的找到所有的Logger。

yangzsirr的博客 1105

Apache log4j-1.2.17源码学习笔记

1.log4j-1.2.17介绍 断点调试和记录日志,是程序员排查问题的2个有效手段,断点调试需要对全盘代码熟门熟路,费时费力,如果代码不开源那么此种方法就不能使用,相对于断点调试,记录日志提供了另外一种更有效的排错方法,预先植入了有效的日志信息,后期只需通过配置文件即可管理日志,借助工具扫描日志文件内容可以有效的监测当前运行的系统是否运行正常。记录日志作为一个通用的重

猿类崛起 5004

log4j源码解析及一个log4j:ERROR Attempted to append to closed appender named 的问题

一、log4j源码解析 最近出现一个问题,弄得我不得不把log4j的源代码读了一篇。 如果自己不想写代码,可以下载此代码:https://github.com/lwwcl1314/atlantis/tree/master/common/src/main/java/com/atlantis/study/log 先讲下log4j的设计. log4j主要有元素:logger leve

一条宝鱼的专栏 3068

log4j日志框架源码——总览(一)

研究了许久,作为半路出家的码农,还是记下学习的成果。(本文针对的版本是1.2.17)初学者在记录某段程序结果或者某段记录时,最常用的就是:System.out.println("test String...");但是这个输出方法是系统控制台的输出,在实际生产环境会被埋没在海量的日志中。所以有了很多日志框架,这里结合自己学习的Apache的log4j日志框架,从源码阶段分享下该框架的实现和使用!(第...

m0_37652164的博客 963

读log4j源码一点体会

读log4j源码一点体会 文 : 熊红阳   一.前言  

EastBear的专栏 2030

Log4j & Log4j2~从入门到入坑。

Log4j & Log4j2~从入门到入坑。

lyfGeek的博客 229
上一篇: 写博文的why和what
下一篇: Java学习资源
架构师的清醒
博客等级 码龄14年 291粉丝 397原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值