Log4J詳解

log4j

1.1 簡介

image

Log4j是一個由Java編寫可靠、靈活的日志框架,是Apache旗下的一個開源項目;現(xiàn)如今,Log4j已經(jīng)被移植到了C、C++、Python等語言中,服務(wù)更多的Developer;

使用Log4j,我們更加方便的記錄了日志信息,它不但能控制日志輸出的目的地,也能控制日志輸出的內(nèi)容格式;通過定義不同的日志級別,可以更加精確的控制日志的生成過程,從而達(dá)到我們應(yīng)用的需求;這一切,都得益于一個靈活的配置文件,并不需要我們更改代碼。

1.2 log4j結(jié)構(gòu)

image

在Log4j中,主要由三個重要組件構(gòu)成:

  • Logger:日志對象,負(fù)責(zé)捕捉日志記錄信息;

      Logger對象是用來取代System.out或者System.err的日志輸出器,負(fù)責(zé)日志信息的輸出;其中,log4j日志框架提供了info、error、debug等API供Developer使用;
    
      與commons-logging相同,log4j也有日志等級的概念;每一個logger對象都會分配一個等級,未被分配等級的logger則繼承根logger的級別,進(jìn)行日志的輸出;每個日志對象方法的請求也有一個等級,如果方法請求的等于大于當(dāng)前l(fā)ogger對象的等級,則該請求會被處理輸出,否則該請求被忽略;
    
      log4j在Level類中定義了7個等級,關(guān)系如下:
          Level.ALL < Level.DEBUG < Level.INFO < Level.WARN < Level.ERROR < Level.FATAL < Level.OFF
    
      每個等級,具體含義如下:    
          ALL:打開所有日志;
          DEBUG:適用于代碼調(diào)試期間;
          INFO:適用于代碼運(yùn)行期間;
          WARN:適用于代碼會有潛在錯誤事件;
          ERROR:適用于代碼存在錯誤事件;
          FATAL:適用于嚴(yán)重錯誤事件;
          OFF:關(guān)閉所有日志;
    
    
  • Appender:日志輸出目的地,負(fù)責(zé)把格式好的日志信息輸出到指定地方,可以是控制臺、磁盤文件等;

      每個日志對象,都有一個對應(yīng)的appender,每個appender代表著一個日志輸出目的地;
    
      其中,log4j有以下幾種appender可供選擇:
          ConsoleAppender:控制臺;
          FileAppender:磁盤文件;
          DailyRollingFileAppender:每天產(chǎn)生一個日志磁盤文件;
          RollingFileAppender:日志磁盤文件大小達(dá)到指定尺寸時產(chǎn)生一個新的文件;
    
    
  • Layout:日志格式化器,負(fù)責(zé)發(fā)布不同風(fēng)格的日志信息;

      每個appender和一個Layout相對應(yīng),appende負(fù)責(zé)把日志信息輸出到指定的地點,而Layout則負(fù)責(zé)把日志信息按照格式化的要求展示出來;
    
      其中,log4j有以下幾種Layout可供選擇:
          HTMLLayout:以html表格形式布局展示;
          PatternLayout:自定義指定格式展示;
          SimpleLayout:包含日志信息的級別和信息字符串;
          TTCCLayout:包含日志產(chǎn)生的時間、線程、類別等等信息;
    
    

1.3 使用

首先,需要在應(yīng)用的pom.xml中添加依賴:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

其次,聲明測試代碼:

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>

通過以上步驟,log4j就可以正常的運(yùn)行了。

1.4 log4j配置文件詳解

接下來,具體講解下log4j配置文件中的各個屬性:(以log4j.properties為例講解);

  • Logger

      配置根Logger,其語法為:
          log4j.rootLogger = [ level ] , appenderName, appenderName, …
    
      其中,level指的是根logger對象的日志等級,Log4j建議只使用4個級別,從高到低分別為ERROR > WARN > INFO > DEBUG;
      appenderName指的是根logger對象的日志信息輸出目的地,在此可以指定多個輸出目的地;
    
      舉例:
          log4j.rootLogger = INFO, FILE, CONSOLE
    
    
  • Appender

      配置日志信息輸出目的地Appender,其語法為:
          log4j.appender.appenderName = className
    
      其中,appenderName指的是日志信息輸出目的地的名稱,可自定義,需要與根Logger中的appenderName一致;
      className指的是日志輸出目的地處理類,必須為全限定類名;
    
      舉例1:
          log4j.appender.FILE = org.apache.log4j.FileAppender(將日志信息輸出到對應(yīng)的磁盤文件中);
          關(guān)于FileAppender的其余選項:
              log4j.appender.FILE.Threshold = DEBUG(指定日志輸出的最低級別,默認(rèn)為DEBUG;如果日志請求的級別低于此級別,則不會輸出此請求日志信息)
              log4j.appender.FILE.File=e:/mylog.log (將日志輸出到e盤的mylog.log文件中)
              log4j.appender.FILE.Encoding=UTF-8(設(shè)置日志輸出的編碼)
              log4j.appender.FILE.Append=false(將新增日志追加到文件中,默認(rèn)為true,false為覆蓋)
              log4j.appender.FILE.ImmediateFlush=true(請求的日志消息被立即輸出,默認(rèn)為true)
              log4j.appender.FILE.BufferedIO=true(請求的日志消息不會立即輸出,存儲到緩存當(dāng)中,當(dāng)緩存滿了后才輸出到磁盤文件中,默認(rèn)為false,此時ImmediateFlush應(yīng)當(dāng)設(shè)置為false)
              log4j.appender.FILE.BufferSize= 8192(緩存大小,默認(rèn)為8k)
    
      舉例2:
          log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender(將日志信息輸出到控制臺中)
          關(guān)于ConsoleAppender的其余選項:
              log4j.appender.CONSOLE.Target=System.out(將日志信息使用System.out.println輸出到控制臺)
              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)生一個日志文件,與上面FileAppender不同)    
          關(guān)于DailyRollingFileAppender的其余選項:
              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)識每天產(chǎn)生一個新的日志文件,當(dāng)然也可以指定按月、周、時、分);具體格式如下:
                  1)'.'yyyy-MM:  每月 
                  2)'.'yyyy-ww:  每周  
                  3)'.'yyyy-MM-dd:    每天 
                  4)'.'yyyy-MM-dd-a:  每天兩次 
                  5)'.'yyyy-MM-dd-HH:  每小時 
                  6)'.'yyyy-MM-dd-HH-mm:  每分鐘 
    
      舉例4:
          log4j.appender.RFILE = org.apache.log4j.RollingFileAppender(在日志文件達(dá)到指定的大小后,再產(chǎn)生新的文件繼續(xù)記錄日志)
          關(guān)于RollingFileAppender的其余選項:
              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ù)量,如果第二個文件超過了指定大小,那么第一個文件將會被刪除)
    
    
  • Layout

      配置日志信息的格式Layout,其語法為:
          log4j.appender.appenderName.layout = className
    
      其中,appenderName就是上面所講的Appender的名稱,Appender必須與Layout相互綁定;
      而className則是處理日志格式的類,也必須是全限定類名;
    
      舉例1:
          log4j.appender.FILE.layout = org.apache.log4j.HTMLLayout(以html表格形式布局)
          log4j.appender.FILE.layout.LocationInfo = true (輸出java文件名稱和行號,默認(rèn)值false)
    
      舉例2:
          log4j.appender.FILE.layout = org.apache.log4j.SimpleLayout(簡單風(fēng)格布局,只包含日志信息和級別)
    
      舉例3:
          log4j.appender.FILE.layout = org.apache.log4j.PatternLayout(自定義風(fēng)格布局,可以包含時間,日志級別,日志類)
          log4j.appender.FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n (指定怎樣格式化的消息)
          具體的格式化說明:
              %p:輸出日志信息的優(yōu)先級,即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL。
              %d:輸出日志時間點的日期或時間,默認(rèn)格式為ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}。
              %r:輸出自應(yīng)用程序啟動到輸出該log信息耗費的毫秒數(shù)。
              %t:輸出產(chǎn)生該日志事件的線程名。
              %l:輸出日志事件的發(fā)生位置,相當(dāng)于%c.%M(%F:%L)的組合,包括類全名、方法、文件名以及在代碼中的行數(shù)。例如:test.TestLog4j.main(TestLog4j.java:10)。
              %c:輸出日志信息所屬的日志對象,也就是getLogger()中的內(nèi)容。
                              %C:輸出日志信息所屬的類目;
                              %logger:log4j中沒有此格式;
              %M:輸出產(chǎn)生日志信息的方法名。
              %F:輸出日志消息產(chǎn)生時所在的文件名稱。
              %L::輸出代碼中的行號。
              %m::輸出代碼中指定的具體日志信息。
              %n:輸出一個回車換行符,Windows平臺為"rn",Unix平臺為"n"。
              %x:輸出和當(dāng)前線程相關(guān)聯(lián)的NDC(嵌套診斷環(huán)境),尤其用到像java servlets這樣的多客戶多線程的應(yīng)用中。
              %%:輸出一個"%"字符。
    
    
  • Category

      配置子logger的輸出級別,以及輸出目的地:
    
      <category name="com.jiaboyan" additivity="false">
          <level value="error"></level>
          <appender-ref ref="CONSOLE" />
      </category>
      其中,name指的是子loggger的名稱,對應(yīng)的就是我們程序中類的路徑;level指定子logger的級別,appender-ref指的是子logger的輸出目的地。
      而additivity指的是子logger在輸出完日志后,是否把輸出信息傳遞給上一層,true為傳遞,false為不傳遞;如果傳遞的話,則會輸出兩遍相同的日志信息。值得一提的是,如果將日志輸出信息傳遞給上一層,但是程序并不會在去判斷上一層的日志輸出級別,而是直接進(jìn)行輸出;
    
    

1.5 性能優(yōu)化

在我們的應(yīng)用中,日志操作幾乎是每個方法中必備的行為,不管是記錄請求的信息,還是輔助問題的定位,日志信息都起著重要的作用,極大的方便了程序開發(fā)。

但與之俱來的是,由于頻繁的IO和磁盤的讀寫,應(yīng)用的性能也隨之降低。并且,java的IO是阻塞式,加鎖后導(dǎo)致也同樣降低性能。因此對于日志的調(diào)優(yōu),就成了必備功課。

首先,拋開頻繁的IO和磁盤讀寫不談,就單純討論log4j的性能而言,在高并發(fā)的情況下,log4j的鎖也會導(dǎo)致應(yīng)用的性能下降,究其原因,還是看以下的代碼:

Category類的callAppenders方法:

//日志對象喚起日志輸出目的地Appender:進(jìn)行日志打印
public void callAppenders(LoggingEvent event) {
    int writes = 0;
    //遍歷日志對象集合
    for(Category c = this; c != null; c=c.parent) {
        //Category是Logger的父類,此處的c就是請求的日志對象本身:
        synchronized(c) {
            //此處加鎖了:
            if(c.aai != null) {
              writes += c.aai.appendLoopOnAppenders(event);
            }
            if(!c.additive) {
              break;
            }
        }
    }
    if(writes == 0) {
      repository.emitNoAppenderWarning(this);
    }
}

通過以上代碼,我們可以發(fā)現(xiàn),為了獲得對應(yīng)日志對象的Appender,會在每次獲取之前都加上synchronized同步鎖。無論多少個線程進(jìn)行請求,到此處都需要進(jìn)行獲取鎖的操作,才可以進(jìn)行日志的打印。這也就是說,線程越多,并發(fā)越大,此處的鎖的競爭越激烈,進(jìn)而導(dǎo)致系統(tǒng)性能的降低。

其次,我們再回過頭來看下IO和磁盤讀寫的問題。在實際的生產(chǎn)環(huán)境下,系統(tǒng)所產(chǎn)生的日志信息需要保存在磁盤文件中,以便日后進(jìn)行系統(tǒng)分析,或者系統(tǒng)問題的查找。

之前,我們說過Java的IO是阻塞式的,下面就來看下實際的代碼:

JDK1.7中的sun.nio.cs.StreamEncoder類:

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同步鎖。這也就是我們所說的java阻塞式IO。

1.5.1 log4j性能測試

在2.3節(jié)中,筆者提到了FileAppender,該類主要功能就是將日志信輸出到磁盤文件中。其中,有ImmediateFlush、BufferedIO、BufferSize這三個屬性尤為值得關(guān)注;

當(dāng)ImmediateFlush=true時候,表示每一條打印日志請求都會被立即輸出,也就是立刻同步到磁盤中去。在高并發(fā)下,系統(tǒng)性能受到很大的影響,IO和磁盤讀寫數(shù)大大提升。

當(dāng)ImmediateFlush=false時候,與上面正好相反,表示每一條打印日志請求不會被立即輸出,會使用java.io.OutputStreamWriter的緩存,緩存大小為1024字節(jié)。

當(dāng)ImmediateFlush=false、BufferedIO=true、BufferSize=8192時候,表示使用java.io.BufferedWriter緩存,緩存大小為默認(rèn)8192字節(jié),每一條打印請求不會立即輸出,當(dāng)緩存達(dá)到8192字節(jié)后才會落盤操作。這樣一來,大大減少了IO和磁盤讀寫操作,提升了系統(tǒng)的性能。

測試代碼:

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時

配置文件:
<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測試結(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時

配置文件:
<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測試結(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時

配置文件:
<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測試結(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測試結(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 

通過以上4個例子,我們可以看出,性能最差的是ImmediateFlush=true的時候,而性能最好的就是開啟日志異步AsyncAppender處理的時候;

1.5.2 log4j鉤子程序

上一小節(jié),我們提到了log4j的緩存,通過測試結(jié)果來看,在開啟緩存的情況下,log4j的性能得到了大幅度提升。既然緩存的優(yōu)勢這么明顯,為什么log4j不默認(rèn)開啟緩存呢?

緩存的存在,有利有弊。利,提升系統(tǒng)響應(yīng)性能;弊,當(dāng)系統(tǒng)因為異常而崩潰,又或者jvm被強(qiáng)行關(guān)閉,從而導(dǎo)致緩存中的數(shù)據(jù)丟失,日志不存在,無法及時確定異常原因。我想,這個才是log4j并沒有默認(rèn)開啟緩存的原因!

日志的存在,一方面為了記錄系統(tǒng)請求的信息;另一方面,幫助develpoer及時發(fā)現(xiàn)、排除錯誤原因。如果連日志的完整性都不能保留,那么日志存在的意義又是什么?所以,log4j并沒有將緩存設(shè)置為默認(rèn)開啟,只是提供了一個選項;

那么,我們?nèi)绾问刽~和熊掌可以兼得呢?在log4j提供的api中暫時無法實現(xiàn)此需求,不過jvm向我們提供了一個方法,可以幫助我們實現(xiàn),這就是jvm關(guān)閉鉤子程序;

在jvm中注冊一個鉤子程序,當(dāng)jvm關(guān)閉的時候,會執(zhí)行系統(tǒng)中已經(jīng)設(shè)置的所有通過方法addShutdownHook添加的鉤子,當(dāng)系統(tǒng)執(zhí)行完這些鉤子后,jvm才會關(guān)閉。

那么,在我們的日志中,如何實現(xiàn)鉤子程序呢?請看下面的實現(xiàn):

測試代碼:

public class log4jDemo {
    Logger log = Logger.getLogger(log4jDemo.class);
    @Test
    public void test() throws InterruptedException {
        log.info("Info Message!");
    }
}

配置文件:(沒有開啟緩存,立即刷入磁盤)

<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é)束后,生成日志文件信息)

image

立刻刷入,日志信息落盤;

接下來,修改配置文件信息,開啟緩存,不立刻刷入磁盤;

<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>

結(jié)果:(程序運(yùn)行結(jié)束后,生成日志文件信息)

image

jvm運(yùn)行結(jié)束,日志信息沒有保存到磁盤中來,日志丟失;

最后,我們添加鉤子程序,看看結(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操作,將日志落盤;
            if(qw != null){
                qw.flush();
            }
        }
    }

}

配置文件修改:(新的appender,開啟緩存)

<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é)果為:

image

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

作者:賈博巖
鏈接:http://www.itdecent.cn/p/f2d4a54f9c41
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容