log4j
1.1 簡(jiǎn)介

Log4j是一個(gè)由Java編寫(xiě)可靠、靈活的日志框架,是Apache旗下的一個(gè)開(kāi)源項(xiàng)目;現(xiàn)如今,Log4j已經(jīng)被移植到了C、C++、Python等語(yǔ)言中,服務(wù)更多的Developer;
使用Log4j,我們更加方便的記錄了日志信息,它不但能控制日志輸出的目的地,也能控制日志輸出的內(nèi)容格式;通過(guò)定義不同的日志級(jí)別,可以更加精確的控制日志的生成過(guò)程,從而達(dá)到我們應(yīng)用的需求;這一切,都得益于一個(gè)靈活的配置文件,并不需要我們更改代碼。
1.2 log4j結(jié)構(gòu)

在Log4j中,主要由三個(gè)重要組件構(gòu)成:
-
Logger:日志對(duì)象,負(fù)責(zé)捕捉日志記錄信息;
Logger對(duì)象是用來(lái)取代System.out或者System.err的日志輸出器,負(fù)責(zé)日志信息的輸出;其中,log4j日志框架提供了info、error、debug等API供Developer使用; 與commons-logging相同,log4j也有日志等級(jí)的概念;每一個(gè)logger對(duì)象都會(huì)分配一個(gè)等級(jí),未被分配等級(jí)的logger則繼承根logger的級(jí)別,進(jìn)行日志的輸出;每個(gè)日志對(duì)象方法的請(qǐng)求也有一個(gè)等級(jí),如果方法請(qǐng)求的等于大于當(dāng)前l(fā)ogger對(duì)象的等級(jí),則該請(qǐng)求會(huì)被處理輸出,否則該請(qǐng)求被忽略; log4j在Level類(lèi)中定義了7個(gè)等級(jí),關(guān)系如下: Level.ALL < Level.DEBUG < Level.INFO < Level.WARN < Level.ERROR < Level.FATAL < Level.OFF 每個(gè)等級(jí),具體含義如下: ALL:打開(kāi)所有日志; DEBUG:適用于代碼調(diào)試期間; INFO:適用于代碼運(yùn)行期間; WARN:適用于代碼會(huì)有潛在錯(cuò)誤事件; ERROR:適用于代碼存在錯(cuò)誤事件; FATAL:適用于嚴(yán)重錯(cuò)誤事件; OFF:關(guān)閉所有日志; -
Appender:日志輸出目的地,負(fù)責(zé)把格式好的日志信息輸出到指定地方,可以是控制臺(tái)、磁盤(pán)文件等;
每個(gè)日志對(duì)象,都有一個(gè)對(duì)應(yīng)的appender,每個(gè)appender代表著一個(gè)日志輸出目的地; 其中,log4j有以下幾種appender可供選擇: ConsoleAppender:控制臺(tái); FileAppender:磁盤(pán)文件; DailyRollingFileAppender:每天產(chǎn)生一個(gè)日志磁盤(pán)文件; RollingFileAppender:日志磁盤(pán)文件大小達(dá)到指定尺寸時(shí)產(chǎn)生一個(gè)新的文件; -
Layout:日志格式化器,負(fù)責(zé)發(fā)布不同風(fēng)格的日志信息;
每個(gè)appender和一個(gè)Layout相對(duì)應(yīng),appende負(fù)責(zé)把日志信息輸出到指定的地點(diǎn),而Layout則負(fù)責(zé)把日志信息按照格式化的要求展示出來(lái); 其中,log4j有以下幾種Layout可供選擇: HTMLLayout:以html表格形式布局展示; PatternLayout:自定義指定格式展示; SimpleLayout:包含日志信息的級(jí)別和信息字符串; TTCCLayout:包含日志產(chǎn)生的時(shí)間、線(xiàn)程、類(lèi)別等等信息;
1.3 使用
首先,需要在應(yīng)用的pom.xml中添加依賴(lài):
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
其次,聲明測(cè)試代碼:
public class log4jDemo {
Logger log= Logger.getLogger(log4jDemo.class);
@Test
public void test(){
log.trace("Trace Message!");
log.debug("Debug Message!");
log.info("Info Message!");
log.warn("Warn Message!");
log.error("Error nihao 你好!");
log.fatal("Fatal Message!");
}
}
最后,在classpath下聲明配置文件:log4j.properties 或者 log4j.xml;
例1:log4j.properties:
log4j.rootLogger = INFO, FILE, CONSOLE
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=e:/log.out
log4j.appender.FILE.ImmediateFlush=true
log4j.appender.FILE.Threshold = DEBUG
log4j.appender.FILE.Append=true
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.conversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.ImmediateFlush=true
log4j.appender.CONSOLE.Threshold = DEBUG
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.encoding=UTF-8
log4j.appender.CONSOLE.layout.conversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
例2:log4j.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out"/>
<param name="immediateFlush" value="true"/>
<param name="threshold" value="DEBUG"/>
<param name="append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d - %c -%-4r [%t] %-5p %x - %m%n" />
</layout>
</appender>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="e:/log.out" />
<param name="ImmediateFlush" value="true"/>
<param name="Threshold" value="DEBUG"/>
<param name="Append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
<category name="com.jiaboyan" additivity="false">
<level value="error"></level>
<appender-ref ref="CONSOLE" />
</category>
<root>
<priority value="info" />
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>
通過(guò)以上步驟,log4j就可以正常的運(yùn)行了。
1.4 log4j配置文件詳解
接下來(lái),具體講解下log4j配置文件中的各個(gè)屬性:(以log4j.properties為例講解);
-
Logger
配置根Logger,其語(yǔ)法為: log4j.rootLogger = [ level ] , appenderName, appenderName, … 其中,level指的是根logger對(duì)象的日志等級(jí),Log4j建議只使用4個(gè)級(jí)別,從高到低分別為ERROR > WARN > INFO > DEBUG; appenderName指的是根logger對(duì)象的日志信息輸出目的地,在此可以指定多個(gè)輸出目的地; 舉例: log4j.rootLogger = INFO, FILE, CONSOLE -
Appender
配置日志信息輸出目的地Appender,其語(yǔ)法為: log4j.appender.appenderName = className 其中,appenderName指的是日志信息輸出目的地的名稱(chēng),可自定義,需要與根Logger中的appenderName一致; className指的是日志輸出目的地處理類(lèi),必須為全限定類(lèi)名; 舉例1: log4j.appender.FILE = org.apache.log4j.FileAppender(將日志信息輸出到對(duì)應(yīng)的磁盤(pán)文件中); 關(guān)于FileAppender的其余選項(xiàng): log4j.appender.FILE.Threshold = DEBUG(指定日志輸出的最低級(jí)別,默認(rèn)為DEBUG;如果日志請(qǐng)求的級(jí)別低于此級(jí)別,則不會(huì)輸出此請(qǐng)求日志信息) log4j.appender.FILE.File=e:/mylog.log (將日志輸出到e盤(pán)的mylog.log文件中) log4j.appender.FILE.Encoding=UTF-8(設(shè)置日志輸出的編碼) log4j.appender.FILE.Append=false(將新增日志追加到文件中,默認(rèn)為true,false為覆蓋) log4j.appender.FILE.ImmediateFlush=true(請(qǐng)求的日志消息被立即輸出,默認(rèn)為true) log4j.appender.FILE.BufferedIO=true(請(qǐng)求的日志消息不會(huì)立即輸出,存儲(chǔ)到緩存當(dāng)中,當(dāng)緩存滿(mǎn)了后才輸出到磁盤(pán)文件中,默認(rèn)為false,此時(shí)ImmediateFlush應(yīng)當(dāng)設(shè)置為false) log4j.appender.FILE.BufferSize= 8192(緩存大小,默認(rèn)為8k) 舉例2: log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender(將日志信息輸出到控制臺(tái)中) 關(guān)于ConsoleAppender的其余選項(xiàng): log4j.appender.CONSOLE.Target=System.out(將日志信息使用System.out.println輸出到控制臺(tái)) log4j.appender.CONSOLE.ImmediateFlush=true(同上) log4j.appender.CONSOLE.Threshold = DEBUG(同上) log4j.appender.CONSOLE.encoding=UTF-8(設(shè)置日志信息輸出編碼) 舉例3: log4j.appender.DRFILE = org.apache.log4j.DailyRollingFileAppender(將輸出的日志信息,每天產(chǎn)生一個(gè)日志文件,與上面FileAppender不同) 關(guān)于DailyRollingFileAppender的其余選項(xiàng): log4j.appender.DRFILE.File=e:/log.out(同上); log4j.appender.DRFILE.ImmediateFlush=true(同上); log4j.appender.DRFILE.Threshold = DEBUG(同上); log4j.appender.DRFILE.Append=true(同上); log4j.appender.DRFILE.DatePattern='.'yyyy-MM-dd(標(biāo)識(shí)每天產(chǎn)生一個(gè)新的日志文件,當(dāng)然也可以指定按月、周、時(shí)、分);具體格式如下: 1)'.'yyyy-MM: 每月 2)'.'yyyy-ww: 每周 3)'.'yyyy-MM-dd: 每天 4)'.'yyyy-MM-dd-a: 每天兩次 5)'.'yyyy-MM-dd-HH: 每小時(shí) 6)'.'yyyy-MM-dd-HH-mm: 每分鐘 舉例4: log4j.appender.RFILE = org.apache.log4j.RollingFileAppender(在日志文件達(dá)到指定的大小后,再產(chǎn)生新的文件繼續(xù)記錄日志) 關(guān)于RollingFileAppender的其余選項(xiàng): log4j.appender.RFILE.Threshold = DEBUG(同上) log4j.appender.RFILE.File=e:/mylog.log (同上) log4j.appender.RFILE.encoding=UTF-8(同上) log4j.appender.RFILE.Append=false(同上) log4j.appender.RFILE.ImmediateFlush=true(同上) log4j.appender.RFILE.MaxFileSize=100KB(指定日志文件切割大小,默認(rèn)10MB,單位KB/MB/GB;當(dāng)日志文件達(dá)到指定大小后,將當(dāng)前日志文件內(nèi)容剪切到新的日志文件中,新的文件默認(rèn)以“原文件名+.1”、“原文件名+.2”的形式命名) log4j.appender.RFILE.MaxBackupIndex=2(產(chǎn)生的切割文件最大數(shù)量,如果第二個(gè)文件超過(guò)了指定大小,那么第一個(gè)文件將會(huì)被刪除) -
Layout
配置日志信息的格式Layout,其語(yǔ)法為: log4j.appender.appenderName.layout = className 其中,appenderName就是上面所講的Appender的名稱(chēng),Appender必須與Layout相互綁定; 而className則是處理日志格式的類(lèi),也必須是全限定類(lèi)名; 舉例1: log4j.appender.FILE.layout = org.apache.log4j.HTMLLayout(以html表格形式布局) log4j.appender.FILE.layout.LocationInfo = true (輸出java文件名稱(chēng)和行號(hào),默認(rèn)值false) 舉例2: log4j.appender.FILE.layout = org.apache.log4j.SimpleLayout(簡(jiǎn)單風(fēng)格布局,只包含日志信息和級(jí)別) 舉例3: log4j.appender.FILE.layout = org.apache.log4j.PatternLayout(自定義風(fēng)格布局,可以包含時(shí)間,日志級(jí)別,日志類(lèi)) log4j.appender.FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n (指定怎樣格式化的消息) 具體的格式化說(shuō)明: %p:輸出日志信息的優(yōu)先級(jí),即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL。 %d:輸出日志時(shí)間點(diǎn)的日期或時(shí)間,默認(rèn)格式為ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}。 %r:輸出自應(yīng)用程序啟動(dòng)到輸出該log信息耗費(fèi)的毫秒數(shù)。 %t:輸出產(chǎn)生該日志事件的線(xiàn)程名。 %l:輸出日志事件的發(fā)生位置,相當(dāng)于%c.%M(%F:%L)的組合,包括類(lèi)全名、方法、文件名以及在代碼中的行數(shù)。例如:test.TestLog4j.main(TestLog4j.java:10)。 %c:輸出日志信息所屬的日志對(duì)象,也就是getLogger()中的內(nèi)容。 %C:輸出日志信息所屬的類(lèi)目; %logger:log4j中沒(méi)有此格式; %M:輸出產(chǎn)生日志信息的方法名。 %F:輸出日志消息產(chǎn)生時(shí)所在的文件名稱(chēng)。 %L::輸出代碼中的行號(hào)。 %m::輸出代碼中指定的具體日志信息。 %n:輸出一個(gè)回車(chē)換行符,Windows平臺(tái)為"rn",Unix平臺(tái)為"n"。 %x:輸出和當(dāng)前線(xiàn)程相關(guān)聯(lián)的NDC(嵌套診斷環(huán)境),尤其用到像java servlets這樣的多客戶(hù)多線(xiàn)程的應(yīng)用中。 %%:輸出一個(gè)"%"字符。 -
Category
配置子logger的輸出級(jí)別,以及輸出目的地: <category name="com.jiaboyan" additivity="false"> <level value="error"></level> <appender-ref ref="CONSOLE" /> </category> 其中,name指的是子loggger的名稱(chēng),對(duì)應(yīng)的就是我們程序中類(lèi)的路徑;level指定子logger的級(jí)別,appender-ref指的是子logger的輸出目的地。 而additivity指的是子logger在輸出完日志后,是否把輸出信息傳遞給上一層,true為傳遞,false為不傳遞;如果傳遞的話(huà),則會(huì)輸出兩遍相同的日志信息。值得一提的是,如果將日志輸出信息傳遞給上一層,但是程序并不會(huì)在去判斷上一層的日志輸出級(jí)別,而是直接進(jìn)行輸出;
1.5 性能優(yōu)化
在我們的應(yīng)用中,日志操作幾乎是每個(gè)方法中必備的行為,不管是記錄請(qǐng)求的信息,還是輔助問(wèn)題的定位,日志信息都起著重要的作用,極大的方便了程序開(kāi)發(fā)。
但與之俱來(lái)的是,由于頻繁的IO和磁盤(pán)的讀寫(xiě),應(yīng)用的性能也隨之降低。并且,java的IO是阻塞式,加鎖后導(dǎo)致也同樣降低性能。因此對(duì)于日志的調(diào)優(yōu),就成了必備功課。
首先,拋開(kāi)頻繁的IO和磁盤(pán)讀寫(xiě)不談,就單純討論log4j的性能而言,在高并發(fā)的情況下,log4j的鎖也會(huì)導(dǎo)致應(yīng)用的性能下降,究其原因,還是看以下的代碼:
Category類(lèi)的callAppenders方法:
//日志對(duì)象喚起日志輸出目的地Appender:進(jìn)行日志打印
public void callAppenders(LoggingEvent event) {
int writes = 0;
//遍歷日志對(duì)象集合
for(Category c = this; c != null; c=c.parent) {
//Category是Logger的父類(lèi),此處的c就是請(qǐng)求的日志對(duì)象本身:
synchronized(c) {
//此處加鎖了:
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
通過(guò)以上代碼,我們可以發(fā)現(xiàn),為了獲得對(duì)應(yīng)日志對(duì)象的Appender,會(huì)在每次獲取之前都加上synchronized同步鎖。無(wú)論多少個(gè)線(xiàn)程進(jìn)行請(qǐng)求,到此處都需要進(jìn)行獲取鎖的操作,才可以進(jìn)行日志的打印。這也就是說(shuō),線(xiàn)程越多,并發(fā)越大,此處的鎖的競(jìng)爭(zhēng)越激烈,進(jìn)而導(dǎo)致系統(tǒng)性能的降低。
其次,我們?cè)倩剡^(guò)頭來(lái)看下IO和磁盤(pán)讀寫(xiě)的問(wèn)題。在實(shí)際的生產(chǎn)環(huán)境下,系統(tǒng)所產(chǎn)生的日志信息需要保存在磁盤(pán)文件中,以便日后進(jìn)行系統(tǒng)分析,或者系統(tǒng)問(wèn)題的查找。
之前,我們說(shuō)過(guò)Java的IO是阻塞式的,下面就來(lái)看下實(shí)際的代碼:
JDK1.7中的sun.nio.cs.StreamEncoder類(lèi):
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
implWrite(cbuf, off, len);
}
}
可以看到,在java-IO流最終輸出階段,也同樣加了synchronized同步鎖。這也就是我們所說(shuō)的java阻塞式IO。
1.5.1 log4j性能測(cè)試
在2.3節(jié)中,筆者提到了FileAppender,該類(lèi)主要功能就是將日志信輸出到磁盤(pán)文件中。其中,有ImmediateFlush、BufferedIO、BufferSize這三個(gè)屬性尤為值得關(guān)注;
當(dāng)ImmediateFlush=true時(shí)候,表示每一條打印日志請(qǐng)求都會(huì)被立即輸出,也就是立刻同步到磁盤(pán)中去。在高并發(fā)下,系統(tǒng)性能受到很大的影響,IO和磁盤(pán)讀寫(xiě)數(shù)大大提升。
當(dāng)ImmediateFlush=false時(shí)候,與上面正好相反,表示每一條打印日志請(qǐng)求不會(huì)被立即輸出,會(huì)使用java.io.OutputStreamWriter的緩存,緩存大小為1024字節(jié)。
當(dāng)ImmediateFlush=false、BufferedIO=true、BufferSize=8192時(shí)候,表示使用java.io.BufferedWriter緩存,緩存大小為默認(rèn)8192字節(jié),每一條打印請(qǐng)求不會(huì)立即輸出,當(dāng)緩存達(dá)到8192字節(jié)后才會(huì)落盤(pán)操作。這樣一來(lái),大大減少了IO和磁盤(pán)讀寫(xiě)操作,提升了系統(tǒng)的性能。
測(cè)試代碼:
public class log4jDemo {
Logger log = Logger.getLogger(log4jDemo.class);
@Test
public void test() throws InterruptedException {
for(int x=0;x<20;x++) {
long start = System.currentTimeMillis();
for (int y = 0; y < 50; y++) {
log.info("Info Message!");
}
long time = System.currentTimeMillis() - start;
System.out.println(time);
}
}
}
例子1:當(dāng)ImmediateFlush=true時(shí)
配置文件:
<log4j:configuration>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="e:/log.out" />
<param name="ImmediateFlush" value="false"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
<root>
<priority value="info" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>
例子1測(cè)試結(jié)果:(單位毫秒)
931 631 372 371 374 371 371 383 376 439 376 383 372 416 393 368 368 366 376 376 394 384 373 396 371 380 368 382 373 369 373 379 374 370 381 367 371 379 372 385 381 379 375 398 409 415 392 371 403 406
例子2:當(dāng)ImmediateFlush=false時(shí)
配置文件:
<log4j:configuration>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="e:/log.out" />
<param name="ImmediateFlush" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
<root>
<priority value="info" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>
例子2測(cè)試結(jié)果:(單位毫秒)
845 693 344 338 353 340 372 373 345 337 332 341 345 352 346 332 336 333 379 359 333 330 356 338 333 341 346 331 341 337 339 329 341 339 339 334 341 328 331 329 328 330 329 336 334 332 332 331 333 330
例子3:當(dāng)ImmediateFlush=false、BufferedIO=true、BufferSize=8192時(shí)
配置文件:
<log4j:configuration>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="e:/log.out" />
<param name="ImmediateFlush" value="false"/>
<param name="bufferedIO" value="true"/>
<param name="bufferSize" value="8192"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
<root>
<priority value="info" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>
例子3測(cè)試結(jié)果:(單位毫秒)
731 853 356 332 336 334 334 334 334 331 332 344 331 330 332 332 330 331 340 334 329 333 331 335 334 334 332 331 336 335 331 354 334 333 334 354 331 333 334 332 333 331 347 332 333 330 332 330 333 331
例子4:使用AsyncAppender異步處理
配置文件:
<log4j:configuration>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="e:/log.out" />
<param name="bufferedIO" value="true"/>
<param name="bufferSize" value="8192"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
<appender name="AsyncAppender" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="FILE"/>
</appender>
<root>
<priority value="info" />
<appender-ref ref="AsyncAppender" />
</root>
</log4j:configuration>
例子4測(cè)試結(jié)果:(單位毫秒)
292 178 146 177 216 278 215 147 136 102 92 96 97 93 93 95 93 94 92 93 97 93 94 93 95 94 96 107 94 91 93 94 99 98 96 95 95 98 102 95 93 92 91 107 155 137 110 98 93 93
通過(guò)以上4個(gè)例子,我們可以看出,性能最差的是ImmediateFlush=true的時(shí)候,而性能最好的就是開(kāi)啟日志異步AsyncAppender處理的時(shí)候;
1.5.2 log4j鉤子程序
上一小節(jié),我們提到了log4j的緩存,通過(guò)測(cè)試結(jié)果來(lái)看,在開(kāi)啟緩存的情況下,log4j的性能得到了大幅度提升。既然緩存的優(yōu)勢(shì)這么明顯,為什么log4j不默認(rèn)開(kāi)啟緩存呢?
緩存的存在,有利有弊。利,提升系統(tǒng)響應(yīng)性能;弊,當(dāng)系統(tǒng)因?yàn)楫惓6罎?,又或者jvm被強(qiáng)行關(guān)閉,從而導(dǎo)致緩存中的數(shù)據(jù)丟失,日志不存在,無(wú)法及時(shí)確定異常原因。我想,這個(gè)才是log4j并沒(méi)有默認(rèn)開(kāi)啟緩存的原因!
日志的存在,一方面為了記錄系統(tǒng)請(qǐng)求的信息;另一方面,幫助develpoer及時(shí)發(fā)現(xiàn)、排除錯(cuò)誤原因。如果連日志的完整性都不能保留,那么日志存在的意義又是什么?所以,log4j并沒(méi)有將緩存設(shè)置為默認(rèn)開(kāi)啟,只是提供了一個(gè)選項(xiàng);
那么,我們?nèi)绾问刽~(yú)和熊掌可以兼得呢?在log4j提供的api中暫時(shí)無(wú)法實(shí)現(xiàn)此需求,不過(guò)jvm向我們提供了一個(gè)方法,可以幫助我們實(shí)現(xiàn),這就是jvm關(guān)閉鉤子程序;
在jvm中注冊(cè)一個(gè)鉤子程序,當(dāng)jvm關(guān)閉的時(shí)候,會(huì)執(zhí)行系統(tǒng)中已經(jīng)設(shè)置的所有通過(guò)方法addShutdownHook添加的鉤子,當(dāng)系統(tǒng)執(zhí)行完這些鉤子后,jvm才會(huì)關(guān)閉。
那么,在我們的日志中,如何實(shí)現(xiàn)鉤子程序呢?請(qǐng)看下面的實(shí)現(xiàn):
測(cè)試代碼:
public class log4jDemo {
Logger log = Logger.getLogger(log4jDemo.class);
@Test
public void test() throws InterruptedException {
log.info("Info Message!");
}
}
配置文件:(沒(méi)有開(kāi)啟緩存,立即刷入磁盤(pán))
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="e:/log.out" />
<param name="ImmediateFlush" value="true"/> 關(guān)閉緩存
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
結(jié)果:(程序運(yùn)行結(jié)束后,生成日志文件信息)

立刻刷入,日志信息落盤(pán);
接下來(lái),修改配置文件信息,開(kāi)啟緩存,不立刻刷入磁盤(pán);
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="e:/log.out" />
<param name="ImmediateFlush" value="false"/> 開(kāi)啟緩存
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
結(jié)果:(程序運(yùn)行結(jié)束后,生成日志文件信息)

jvm運(yùn)行結(jié)束,日志信息沒(méi)有保存到磁盤(pán)中來(lái),日志丟失;
最后,我們添加鉤子程序,看看結(jié)果如何?
創(chuàng)建新的Appender,繼承FileAppender,在構(gòu)造中添加鉤子程序代碼:
public class HookFileAppender extends FileAppender {
public HookFileAppender(){
super();
//添加鉤子程序:
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
public HookFileAppender(Layout layout, String filename) throws IOException {
super(layout,filename);
//添加鉤子程序:
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
public HookFileAppender(Layout layout, String filename, boolean append) throws IOException {
super(layout,filename,append);
//添加鉤子程序:
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
public HookFileAppender(Layout layout, String filename, boolean append, boolean bufferedIO,
int bufferSize) throws IOException {
super(layout,filename,append,bufferedIO,bufferSize);
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
class Log4jHockThread extends Thread{
@Override
public void run() {
//jvm結(jié)束之前,運(yùn)行flush操作,將日志落盤(pán);
if(qw != null){
qw.flush();
}
}
}
}
配置文件修改:(新的appender,開(kāi)啟緩存)
<appender name="HOOKFILE" class="com.jiaboyan.logDemo.HookFileAppender">
<param name="File" value="e:/log.out" />
<param name="ImmediateFlush" value="false"/>
<!--<param name="bufferedIO" value="true"/>-->
<!--<param name="bufferSize" value="8192"/>-->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" />
</layout>
</appender>
<root>
<priority value="info" />
<appender-ref ref="HOOKFILE" />
</root>
結(jié)果為:

在jvm結(jié)束之前,開(kāi)啟了Log4jHockThread線(xiàn)程,將緩存中的日志進(jìn)行落盤(pán)操作,避免了日志的丟失。