成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Log4j DailyRollingFileAppender源碼初探

warkiz / 2681人閱讀

摘要:是否覆蓋目標文件名是否緩沖默認緩沖區(qū)大小既然緩沖了,那意味著父類中的刷新控制為不進行同步刷新利用父類中的字節(jié)流字符流轉(zhuǎn)換方法實例化父類中的實際在上面指向了文件輸出流繼承,將文件進行日常轉(zhuǎn)存。

瞎扯

Log4j對Java開發(fā)者來說是經(jīng)常使用到的日志框架,我每次使用都對它的配置文件頭大,網(wǎng)上搜一個別人的例子自己改巴改巴,草草了事。再次使用時,又忘了怎么回事了。這次突然來了興趣,想看看它具體是怎么做的,做個筆記,加深一下印象。

目前的版本是 log4j:log4j:1.2.17

依賴結(jié)構(gòu)

Appender接口

Log4j的輸出類都需要實現(xiàn)的接口,為了用戶自定義log輸出策略,抽象出了以下幾點功能

過濾鏈

log輸出

錯誤處理

log格式

OptionHandler接口

這個接口只定義了一個方法 void activateOptions();,用于按需初始化一些配置。

AppenderSkeleton抽象類

既然是Skeleton,那它必須是最核心的骨架。這個類主要做了以下幾個事

過濾鏈(鏈表)增刪操作

protected Filter headFilter;
protected Filter tailFilter;

public void addFilter(Filter newFilter) {
if(headFilter == null) {
  headFilter = tailFilter = newFilter;
} else {
  tailFilter.setNext(newFilter);
  tailFilter = newFilter;    
}
}

public void clearFilters() {
headFilter = tailFilter = null;

* 定義了日志優(yōu)先級 `threshold` “門檻”,實現(xiàn)日志的分級輸出
protected Priority threshold;//默認為空

public boolean isAsSevereAsThreshold(Priority priority) {
    return ((threshold == null) || priority.isGreaterOrEqual(threshold));

}

* log的輸出核心邏輯
public synchronized void doAppend(LoggingEvent event) {
if(closed) {
  LogLog.error("Attempted to append to closed appender named ["+name+"].");
  return;
}
//日志級別攔截
if(!isAsSevereAsThreshold(event.getLevel())) {
  return;
}

Filter f = this.headFilter;

//結(jié)合Filter實現(xiàn)類自身的優(yōu)先級[停止輸出、立即輸出、依次過濾后輸出]進行過濾,
FILTER_LOOP:
while(f != null) {
  switch(f.decide(event)) {
  case Filter.DENY: return;
  case Filter.ACCEPT: break FILTER_LOOP;
  case Filter.NEUTRAL: f = f.getNext();
  }
}
//具體的輸出開放給子類實現(xiàn)
this.append(event);    

}

* 下放的權(quán)限
//子類只需要關(guān)心日志具體的輸出方式
abstract protected void append(LoggingEvent event);
//配置方法,子類可以按自己的需求覆寫
public void activateOptions() {}
````
WriteAppender

繼承AppenderSkeleton,用戶可選擇將log按字符流或字節(jié)流輸出。增加了以下特性

提供了寫入刷新控制

可配置編碼方式

提供了靜態(tài)字符流QuitWriter,異常不會拋出,會交給ErrorHandler去處理

//默認實時刷新,效率低但可保證每次輸出均可寫入,設(shè)為false時,若程序崩潰,尾部log可能丟失
protected boolean immediateFlush = true;
protected String encoding;

* 提供了字節(jié)流->字符流的轉(zhuǎn)換
* log輸出 官方注釋說明了在log輸出之前做的檢查或過濾操作[檢查日志級別->過濾->檢查當前輸出狀況(Appender狀態(tài)、輸出流、格式是否均具備)->輸出]
public void append(LoggingEvent event) {

// Reminder: the nesting of calls is:
//
//    doAppend()
//      - check threshold
//      - filter
//      - append();
//        - checkEntryConditions();
//        - subAppend();

if(!checkEntryConditions()) {
  return;
}
subAppend(event);

}

protected void subAppend(LoggingEvent event) {

this.qw.write(this.layout.format(event));//將日志格式化后輸出
//依次輸出異常棧
if(layout.ignoresThrowable()) {
  String[] s = event.getThrowableStrRep();
  if (s != null) {
    int len = s.length;
    for(int i = 0; i < len; i++) {
      this.qw.write(s[i]);
      this.qw.write(Layout.LINE_SEP);
    }
  }
}
//寫入刷新控制
if(shouldFlush(event)) {
  this.qw.flush();
}

}

* 還有一些Header、Footer的寫入和輸出流的關(guān)閉操作

### FileAppender ###
繼承了WriteAppender,將log輸出到文件。這個比較簡單,主要就是將父類中的輸出流封裝指向到文件。

protected boolean fileAppend = true;//是否覆蓋
protected String fileName = null;//目標文件名
protected boolean bufferedIO = false;//是否緩沖
protected int bufferSize = 8*1024;//默認緩沖區(qū)大小

public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)

                                                        throws IOException {
LogLog.debug("setFile called: "+fileName+", "+append);

// It does not make sense to have immediate flush and bufferedIO.
if(bufferedIO) {
  setImmediateFlush(false);//既然緩沖了,那意味著父類中的刷新控制為false-不進行同步刷新
}

reset();
FileOutputStream ostream = null;
try {
      ostream = new FileOutputStream(fileName, append);
} catch(FileNotFoundException ex) {
      String parentName = new File(fileName).getParent();
      if (parentName != null) {
         File parentDir = new File(parentName);
         if(!parentDir.exists() && parentDir.mkdirs()) {
            ostream = new FileOutputStream(fileName, append);
         } else {
            throw ex;
         }
      } else {
         throw ex;
      }
}
Writer fw = createWriter(ostream);//利用父類中的字節(jié)流->字符流轉(zhuǎn)換方法
if(bufferedIO) {
  fw = new BufferedWriter(fw, bufferSize);
}
this.setQWForFiles(fw);//實例化父類中的QuitWriter(實際在上面指向了文件輸出流)
this.fileName = fileName;
this.fileAppend = append;
this.bufferedIO = bufferedIO;
this.bufferSize = bufferSize;
writeHeader();
LogLog.debug("setFile ended");

}

protected void setQWForFiles(Writer writer) {

 this.qw = new QuietWriter(writer, errorHandler);

}

### DailyRollingFileAppender ###
繼承FileAppender,將log文件進行日常轉(zhuǎn)存。我們常用的日志處理類,官方注釋里說已證實有`并發(fā)和數(shù)據(jù)丟失`的問題,可惜我看不出來...
可以自定義轉(zhuǎn)存日期表達式datePattern(格式需遵循SimpleDateFormat的約定),如

"."yyyy-MM
"."yyyy-ww
"."yyyy-MM-dd
"."yyyy-MM-dd-a
"."yyyy-MM-dd-HH
"."yyyy-MM-dd-HH-mm

注意不要包含任何冒號

它根據(jù)用戶提供的日期表達式datePattern,通過內(nèi)部類RollingCalendar計算得到對應(yīng)的`日期檢查周期rc.type`,每次log輸出之前,計算`下次檢查時間nextCheck`,對比當前時間,判斷是否進行文件轉(zhuǎn)存。

主要方法有

//各級檢查周期對應(yīng)的常量
// The code assumes that the following constants are in a increasing sequence.
static final int TOP_OF_TROUBLE=-1;
static final int TOP_OF_MINUTE = 0;
static final int TOP_OF_HOUR = 1;
static final int HALF_DAY = 2;
static final int TOP_OF_DAY = 3;
static final int TOP_OF_WEEK = 4;
static final int TOP_OF_MONTH = 5;

//初始化配置項
public void activateOptions() {

super.activateOptions();
if(datePattern != null && fileName != null) {
  now.setTime(System.currentTimeMillis());
  sdf = new SimpleDateFormat(datePattern);
  int type = computeCheckPeriod();//計算datePattern對應(yīng)的檢查周期
  printPeriodicity(type);//打印當前檢查周期
  rc.setType(type);//內(nèi)部RollingCalendar會在log輸出之前根據(jù)type計算出下次檢查時間
  File file = new File(fileName);//log輸出文件名
  scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));//log轉(zhuǎn)存文件名

} else {
  LogLog.error("Either File or DatePattern options are not set for appender ["
       +name+"].");
}

}

//初始化配置時,計算檢查周期
int computeCheckPeriod() {

RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault());
// set sate to 1970-01-01 00:00:00 GMT
Date epoch = new Date(0);
if(datePattern != null) {
  for(int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
    simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in GMT
    String r0 = simpleDateFormat.format(epoch);
    rollingCalendar.setType(i);
    Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
    String r1 =  simpleDateFormat.format(next);
    //r0、r1均以datePattern格式來轉(zhuǎn)換日期,若type小于datePattern表示的最小范圍,對應(yīng)日期next的變化不會影響格式化后的r1的值
    //每循環(huán)一次,type(也就是i) 增1,最終得到的type就是datePattern表示的最小范圍
    if(r0 != null && r1 != null && !r0.equals(r1)) {
      return i;
    }
  }
}
return TOP_OF_TROUBLE; // Deliberately head for trouble...

}

//log輸出
protected void subAppend(LoggingEvent event) {

//在每次調(diào)用父類subAppend方法輸出文件之前,進行周期計算
//若當前時間晚于"檢查點時間",調(diào)用rollOver()方法進行日志轉(zhuǎn)存,將當前l(fā)og文件轉(zhuǎn)存為指定日期結(jié)尾的文件,然后將父類的QuietWriter指向新的log文件
//當然在轉(zhuǎn)存之前,需要再次計算并刷新"檢查點時間",rc內(nèi)部type會影響計算結(jié)果(在初始化配置時已根據(jù)datePattern計算得到)
long n = System.currentTimeMillis();
if (n >= nextCheck) {
  now.setTime(n);
  nextCheck = rc.getNextCheckMillis(now);
  try {
rollOver();
  }
  catch(IOException ioe) {
      if (ioe instanceof InterruptedIOException) {
          Thread.currentThread().interrupt();
      }
      LogLog.error("rollOver() failed.", ioe);
  }
}
super.subAppend(event);

}

### RollingFileAppender ###
同樣繼承于FileAppender,由文件大小來轉(zhuǎn)存log文件

### ExternallyRolledFileAppender ###
繼承于RollingFileAppender,通過Socket監(jiān)聽轉(zhuǎn)存消息來進行轉(zhuǎn)存操作,后臺運行著一個Socket監(jiān)聽線程,每次收到轉(zhuǎn)存消息,會新起一個線程進行日志轉(zhuǎn)存,并將轉(zhuǎn)存結(jié)果信息返回。



## 不足 ##
只是介紹了關(guān)鍵的一些類,但他們的生命周期,相關(guān)的屬性類和輔助類還沒提到,主要是Filter和Layout,下次再更新。
還有上面幾個關(guān)鍵方法中的同步關(guān)鍵字,我還沒搞懂應(yīng)該怎么解釋。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/65203.html

相關(guān)文章

  • LOG4J和SLF4J的使用和原理

    摘要:和在通常系統(tǒng)中,日志功能使用了,對于這兩者的區(qū)別不甚了解,通過實踐,并深入源代碼層次的分析,希望能夠講的清晰一些?;驹碓陧椖恐惺褂玫降陌饕泻腿齻€。 LOG4J和SLF4J 在通常系統(tǒng)中,日志功能使用了log4j+slf4j,對于這兩者的區(qū)別不甚了解,通過實踐,并深入源代碼層次的分析,希望能夠講的清晰一些。 基本原理 在項目中使用到的jar包主要有l(wèi)og4j.jar , slf4...

    1treeS 評論0 收藏0
  • spring進步 -- log4j的學習

    摘要:建議只使用四個級別,優(yōu)先級從高到低分別是。比如在這里定義了級別,只有等于及高于這個級別的才進行處理,則應(yīng)用程序中所有級別的日志信息將不被打印出來??赏瑫r指定多個輸出目的地。 一直感覺到log4j是使用比較混亂,今天抽空整理一下,以后方便使用 一、引用apache.log4j 使用maven進行l(wèi)o4j的引用 log4j log4j 1.2.17 其他版本也...

    edgardeng 評論0 收藏0
  • LogBack與Log4j配置與日志分模塊打印

    摘要:如果日志級別等于配置級別,過濾器會根據(jù)和接收或拒絕日志。例如過濾掉所有低于級別的日志。有個子標簽,用于配置求值條件。 沒時間解釋了,快上車,老司機先看代碼 LogBack.xml DEBUG ${MESSAGE_FILE_PATTERN} ...

    kycool 評論0 收藏0
  • spring-springmvc-mybatis-shiro項目介紹

    摘要:項目介紹在之前的整合項目之后,新增日志簡單集成,之前的代碼不予展示與介紹,想了解的請參考整合項目項目代碼獲取項目結(jié)構(gòu)代碼控制層,,主要包含登錄及幾個頁面跳轉(zhuǎn)會跳到我們自定義的中登錄用戶名或密碼錯誤業(yè)務(wù)處理層,包含一個包,以接口類型存在 spring-springmvc-mybatis-shiro項目介紹 在之前的mybatis整合項目之后,新增日志、簡單集成shiro,之前的代碼不予展...

    fanux 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<