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![](http://static.javashuo.com/static/loading.gif)
INFO
-
Executing![](http://static.javashuo.com/static/loading.gif)
ERROR
-
Catching an Exception
java.lang.Exception: Deliberately
throw
an Exception![](http://static.javashuo.com/static/loading.gif)
at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:
48
)
at levin.log4j.layout.LayoutTest.testSimpleLayout(LayoutTest.java:
25
)
![](http://static.javashuo.com/static/loading.gif)
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![](http://static.javashuo.com/static/loading.gif)
<
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
)
![](http://static.javashuo.com/static/loading.gif)
</
td
></
tr
>
以上全部HTML內容信息都要通過轉義,即: ’<’ => < ‘>’ => > ‘&’ => & ‘」’ => "從上信息能夠看到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
;
<
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![](http://static.javashuo.com/static/loading.gif)
at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:
54
)
at levin.log4j.layout.LayoutTest.testXMLLayout(LayoutTest.java:
43
)
![](http://static.javashuo.com/static/loading.gif)
]]
></
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![](http://static.javashuo.com/static/loading.gif)
2012
-
07
-
02
23
:
07
:
34
,
018
[main] INFO levin.log4j.test.TestBasic
-
Executing![](http://static.javashuo.com/static/loading.gif)
2012
-
07
-
02
23
:
07
:
34
,
019
[main] ERROR levin.log4j.test.TestBasic
-
Catching an Exception
java.lang.Exception: Deliberately
throw
an Exception![](http://static.javashuo.com/static/loading.gif)
at levin.log4j.layout.LayoutTest.logTest(LayoutTest.java:
63
)
![](http://static.javashuo.com/static/loading.gif)
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
}