Log4j源碼解析--核心類解析

原文出處:http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html。感謝上善若水的無私分享。html

在簡單的介紹了Log4J各個模塊類的做用後,如下將詳細的介紹各個模塊的具體做用以及代碼實現。java

Logger類

Logger是對記錄日誌動做的抽象,它提供了記錄不一樣級別日誌的接口,日誌信息能夠包含異常信息也能夠不包含:apache

 1  public   void  debug(Object message) {
 2       if (isLevelEnabled(Level.DEBUG)) {
 3          forceLog(FQCN, Level.DEBUG, message,  null );
 4      }
 5  }
 6  public   void  debug(Object message, Throwable cause) {
 7       if (isLevelEnabled(Level.DEBUG)) {
 8          forceLog(FQCN, Level.DEBUG, message, cause);
 9      }
10  }
11  protected   void  forceLog(String fqcn, Level level, Object message, Throwable t) {
12      callAppenders( new  LoggingEvent(fqcn,  this , level, message, t));
13 }

Logger類包含Level信息 ,若是當前Logger未設置Level值,它也能夠中父節點中繼承下來,該值能夠用來控制該Logger能夠記錄的日誌級別:數組

 1  protected  Level level;
 2  public  Level getEffectiveLevel() {
 3       for (Logger logger  =   this ; logger  !=   null ; logger  =  logger.parent) {
 4           if (logger.level  !=   null ) {
 5               return  logger.level;
 6          }
 7      }
 8       return   null ;
 9  }
10  public   boolean  isLevelEnabled(Level level) {
11       return  level.isGreaterOrEqual( this .getEffectiveLevel());
12  }
13  public   boolean  isDebugEnabled() {
14       return  isLevelEnabled(Level.DEBUG);
15 }

Logger是一個命名的實體,其名字通常用」.」分割以體現不一樣Logger的層次關係,其中Level和Appender信息能夠從父節點中獲取,於是Logger類中還具備name和parent屬性。服務器

1  private  String name;
2  protected Logger parent;

在某些狀況下,咱們但願某些Logger只將日誌記錄到特定的Appender中,而不想記錄在父節點中的Appender中,Log4J爲這種需求提供了additivity屬性,即對當前Logger節點,若是其additivity屬性設置爲false,則該Logger不會繼承父節點的Appender信息,可是其子節點依然會繼承該Logger的Appender信息,除非子節點的additivity屬性也設置成了false。app

 1  private   boolean  additive  =   true ;
 2  public   void  callAppenders(LoggingEvent event) {
 3       int  writes  =   0 ;
 4      
 5       for (Logger logger  =   this ; logger  !=   null ; logger  =  logger.parent) {
 6           synchronized (logger) {
 7               if (logger.appenders  !=   null ) {
 8                  writes  +=  logger.appenders.appendLoopOnAppenders(event);
 9              }
10               if ( ! logger.additive) {
11                   break ;
12              }
13          }
14      }
15      
16       if (writes  ==   0 ) {
17          System.err.println( " No Appender is configed. " );
18      }
19 }

最後,爲了支持國際化,Log4J還提供了兩個l7dlog()方法,經過指定的key,以從資源文件中獲取消息內容。爲了使用這兩個方法,須要設置資源文件。一樣,資源文件也是能夠從父節點中繼承的。ide

 1  private  ResourceBundle resourceBundle;
 2  public   void  l7dlog(Level level, String key, Throwable cause) {
 3       if (isLevelEnabled(level)) {
 4          String message  =  getResourceBundleString(key);
 5           if (message  ==   null ) {
 6              message  =  key;
 7          }
 8          forceLog(FQCN, level, message, cause);
 9      }
10  }
11 
12  public   void  l7dlog(Level level, String key, Object[] params, Throwable cause) {
13      
14           if (pattern  ==   null ) {
15              message  =  key;
16          }  else  {
17              message  =  MessageFormat.format(pattern, params);
18          }
19      
20  }
21 
22  protected  String getResourceBundleString(String key) {
23      ResourceBundle rb  =  getResourceBundle();
24      
25       return  rb.getString(key);
26 
27  public  ResourceBundle getResourceBundle() {
28       for (Logger logger  =   this ; logger  !=   null ; logger  =  logger.parent) {
29           if (logger.resourceBundle  !=   null ) {
30               return  logger.resourceBundle;
31          }
32      }
33       return   null ;    
34 }

另外,在實際開發中常常會遇到要把日誌信息同時寫到不一樣地方,如同時寫入文件和控制檯,於是一個Logger實例中能夠包含多個Appender,爲了管理多個Appender,Log4J抽象出了AppenderAttachable接口,它定義了幾個用於管理多個Appender實例的方法,這些方法由AppenderAttachableImpl類實現,而Logger會實例化AppenderAttachableImpl,並將這些方法代理給該實例:oop

 1  public   interface  AppenderAttachable {
 2       public   void  addAppender(Appender newAppender);
 3       public  Enumeration getAllAppenders();
 4       public  Appender getAppender(String name);
 5       public   boolean  isAttached(Appender appender);
 6       void  removeAllAppenders();
 7       void  removeAppender(Appender appender);
 8       void  removeAppender(String name);
 9 }

RootLogger類

Log4J 中,全部 Logger 實例組成一個單根的樹狀結構,因爲 Logger 實例的根節點有一點特殊:它的名字爲「 root 」,它沒有父節點,它的 Level 字段必須設值以防止其餘 Logger 實例都沒有設置 Level 值的狀況。基於這些考慮, Log4J 經過繼承 Logger 類實現了 RootLogger 類,它用於表達全部 Logger 實例的根節點:
 1  public   final   class  RootLogger  extends  Logger {
 2       public  RootLogger(Level level) {
 3           super ( " root " );
 4          setLevel(level);
 5      }
 6       public   final  Level getChainedLevel() {
 7           return  level;
 8      }
 9       public   final   void  setLevel(Level level) {
10           if  (level  ==   null ) {
11              LogLog.error( " You have tried to set a null level to root. " ,
12                       new  Throwable());
13          }  else  {
14               this .level  =  level;
15          }
16      }
17  }

NOPLogger類

有時候,爲了測試等其餘需求,咱們但願Logger自己不作什麼事情,Log4J爲這種需求提供了NOPLogger類,它繼承自Logger,可是基本上的方法都爲空。性能

Level類

Level是對日誌級別的抽象,目前Log4J支持的級別有FATAL、ERROR、WARN、INFO、DEBUG、TRACE,從頭至尾一次級別遞減,另外Log4J還支持兩種特殊的級別:ALL和OFF,它們分別表示打開和關閉日誌功能。測試

 1  public   static   final   int  OFF_INT  =  Integer.MAX_VALUE;
 2  public   static   final   int  FATAL_INT  =   50000 ;
 3  public   static   final   int  ERROR_INT  =   40000 ;
 4  public   static   final   int  WARN_INT   =   30000 ;
 5  public   static   final   int  INFO_INT   =   20000 ;
 6  public   static   final   int  DEBUG_INT  =   10000 ;
 7  public   static   final   int  TRACE_INT  =   5000 ;
 8  public   static   final   int  ALL_INT  =  Integer.MIN_VALUE;
 9 
10  public   static   final  Level OFF  =   new  Level(OFF_INT,  " OFF " 0 );
11  public   static   final  Level FATAL  =   new  Level(FATAL_INT,  " FATAL " 0 );
12  public   static   final  Level ERROR  =   new  Level(ERROR_INT,  " ERROR " 3 );
13  public   static   final  Level WARN  =   new  Level(WARN_INT,  " WARN " 4 );
14  public   static   final  Level INFO  =   new  Level(INFO_INT,  " INFO " 6 );
15  public   static   final  Level DEBUG  =   new  Level(DEBUG_INT,  " DEBUG " 7 );
16  public   static   final  Level TRACE  =   new  Level(TRACE_INT,  " TRACE " 7 );
17  public   static   final  Level ALL  =   new  Level(ALL_INT,  " ALL " 7 );

每一個Level實例包含了該Level表明的int值(通常是從級別低到級別高一次增大)、該Level的String表達、該Level和系統Level的對應值。

1  protected   transient   int  level;
2  protected   transient  String levelStr;
3  protected   transient   int  syslogEquivalent;
4  protected  Level( int  level, String levelStr,  int  syslogEquivalent) {
5       this .level  =  level;
6       this .levelStr  =  levelStr;
7       this .syslogEquivalent  =  syslogEquivalent;
8  }

Level類主要提供了判斷哪一個Level級別更高的方法isGreaterOrEqual()以及將int值或String值轉換成Level實例的toLevel()方法:

 1  public   boolean  isGreaterOrEqual(Level level) {
 2       return   this .level  >=  level.level;
 3  }
 4  public   static  Level toLevel( int  level) {
 5       return  toLevel(level, DEBUG);
 6  }
 7  public   static  Level toLevel( int  level, Level defaultLevel) {
 8       switch (level) {
 9           case  OFF_INT:  return  OFF;
10           case  FATAL_INT:  return  FATAL;
11           case  ERROR_INT:  return  ERROR;
12           case  WARN_INT:  return  WARN;
13           case  INFO_INT:  return  INFO;
14           case  DEBUG_INT:  return  DEBUG;
15           case  TRACE_INT:  return  TRACE;
16           case  ALL_INT:  return  ALL;
17      }
18       return  defaultLevel;
19  }

另外,因爲對相同級別的Level實例來講,它必須是單例的,於是Log4J對序列化和反序列化作了一些處理。即它的三個成員都是transient,真正序列化和反序列化的代碼本身寫,而且加入readResolve()方法的支持,以保證反序列化出來的相同級別的Level實例是相同的實例。

 1  private   void  readObject( final  ObjectInputStream input)  throws  IOException, ClassNotFoundException {
 2      input.defaultReadObject();
 3      level  =  input.readInt();
 4      syslogEquivalent  =  input.readInt();
 5      levelStr  =  input.readUTF();
 6       if (levelStr  ==   null ) {
 7          levelStr  =   "" ;
 8      }
 9  }
10  private   void  writeObject( final  ObjectOutputStream output)  throws  IOException {
11      output.defaultWriteObject();
12      output.writeInt(level);
13      output.writeInt(syslogEquivalent);
14      output.writeUTF(levelStr);
15  }
16  private  Object readResolve()  throws  ObjectStreamException {
17       if ( this .getClass()  ==  Level. class ) {
18           return  toLevel(level);
19      }
20       return   this ;
21 }

若是要實現本身的Level類,能夠繼承自Level,而且實現相應的靜態toLevel()方法便可。關於如何實現本身的Level類將會在配置文件相關小節中詳細討論。

LoggerRepository類

LoggerRepository從概念以及字面上來講它就是一個Logger實例的容器:一方面相同名字的Logger實例只須要建立一次,在後面的使用中,只須要從這個容器中取便可;另外一方面,Logger容器能夠存放從配置文件中解析出來的信息,從而使配置信息能夠無縫的應用到Log4J內部系統中;最後Logger容器還爲維護Logger的樹狀層次結構提供了方面,每一個Logger只維護父節點的信息,有了Logger容器的存在則能夠很容易的找到一個新的Logger實例的父節點;關於Logger容器將在下一節中詳細講解。

LoggingEvent類

LoggingEvent我的感受用LoggingContext更合適一些,它是對一第二天志記錄時哪能獲取到的數據的封裝。它包含了如下信息以提供Layout在format()方法中使用:

1.       fqnOfCategoryClass:日誌記錄接口(默認爲Logger)的類全名,該信息主要用於計算日誌記錄點的源文件、調用方法以及行號等位置信息。

2.       locationInfo:經過fqnOfCategoryClass計算位置信息,位置信息的計算由LocationInfo類實現,這些信息能夠提供給Layout使用。

3.       logger:目前來看主要是經過Logger實例取得LogRepository實例,並經過LogRepository取得註冊的ObjectRender實例,若是有的話。

4.       loggerName:當前日誌記錄的Logger名稱,提供給Layout使用。

5.       threadName:當前線程名,提供給Layout使用。

6.       level:當前日誌的級別,提供給Layout使用。

7.       message:當前日誌類,通常是String類型,可是也能夠經過註冊ObjectRender,而後傳入相應的其餘對象類型。

8.       renderedMessage:通過ObjectRender處理後的日誌信息,提供給Layout使用。

9.       throwableInfo:異常信息,若是存在的話,提供給Layout使用。

10.   timestamp:建立LoggingEvent實例的時間,提供給Layout使用。

11.   其餘相對不經常使用的信息將會在後面小節中講解。

LoggingEvent只是一個簡單的數據對象(DO),於是其實現仍是比較簡單的,即在建立實例時將數據提供給它,在其餘類(Layout等)使用它時經過getXXX()方法取數據。不過仍是有幾個方法能夠簡單的講解一下。

LocationInfo類計算位置信息

LocationInfo所指的位置信息主要包括記錄日誌所在的源文件名、類名、方法名、所在源文件的行號。

1       transient  String lineNumber;
2       transient  String fileName;
3       transient  String className;
4       transient  String methodName;
5      // fully.qualified.classname.of.caller.methodName(Filename.java:line)
6       public  String fullInfo;

咱們知道在異常棧中每一條記錄都包含了方法調用對應的這些信息,Log4J的這些信息正是利用了這個原理,即經過構建一個Throwable實例,然後在該Throwable的棧信息中解析出來的:

1  public  LocationInfo getLocationInformation() {
2       if  (locationInfo  ==   null ) {
3          locationInfo  =   new  LocationInfo( new  Throwable(), 
4  fqnOfCategoryClass);
5      }
6       return  locationInfo;
7  }

以上Throwable通常會產生以下異常棧:

1  java.lang.Throwable
2 
3  at org.apache.log4j.PatternLayout.format(PatternLayout.java: 413 )
4  at org.apache.log4j.FileAppender.doAppend(FileAppender.java: 183 )
5  at org.apache.log4j.Category.callAppenders(Category.java: 131 )
6  at org.apache.log4j.Category.log(Category.java: 512 )
7  at callers.fully.qualified.className.methodName(FileName.java: 74 )
8 

於是咱們就能夠經過callers.fully.qualified.className信息來找到改行信息,這個className信息便是傳入的fqnOfCategoryClass。

若是當前JDK版本是1.4以上,咱們就能夠經過JDK提供的一些方法來查找:

 1  getStackTraceMethod  =  Throwable. class .getMethod( " getStackTrace " ,
 2          noArgs);
 3  Class stackTraceElementClass  =  Class
 4          .forName( " java.lang.StackTraceElement " );
 5  getClassNameMethod  =  stackTraceElementClass.getMethod(
 6           " getClassName " , noArgs);
 7  getMethodNameMethod  =  stackTraceElementClass.getMethod(
 8           " getMethodName " , noArgs);
 9  getFileNameMethod  =  stackTraceElementClass.getMethod( " getFileName " ,
10          noArgs);
11  getLineNumberMethod  =  stackTraceElementClass.getMethod(
12           " getLineNumber " , noArgs);
13 
14  Object[] noArgs  =   null ;
15  Object[] elements  =  (Object[]) getStackTraceMethod.invoke(t,
16          noArgs);
17  String prevClass  =  NA;
18  for  ( int  i  =  elements.length  -   1 ; i  >=   0 ; i -- ) {
19      String thisClass  =  (String) getClassNameMethod.invoke(
20              elements[i], noArgs);
21       if  (fqnOfCallingClass.equals(thisClass)) {
22           int  caller  =  i  +   1 ;
23           if  (caller  <  elements.length) {
24              className  =  prevClass;
25              methodName  =  (String) getMethodNameMethod.invoke(
26                      elements[caller], noArgs);
27              fileName  =  (String) getFileNameMethod.invoke(
28                      elements[caller], noArgs);
29               if  (fileName  ==   null ) {
30                  fileName  =  NA;
31              }
32               int  line  =  ((Integer) getLineNumberMethod.invoke(
33                      elements[caller], noArgs)).intValue();
34               if  (line  <   0 ) {
35                  lineNumber  =  NA;
36              }  else  {
37                  lineNumber  =  String.valueOf(line);
38              }
39              StringBuffer buf  =   new  StringBuffer();
40              buf.append(className);
41              buf.append( " . " );
42              buf.append(methodName);
43              buf.append( " ( " );
44              buf.append(fileName);
45              buf.append( " : " );
46              buf.append(lineNumber);
47              buf.append( " ) " );
48               this .fullInfo  =  buf.toString();
49          }
50           return ;
51      }
52      prevClass  =  thisClass;
53  }

不然,則須要咱們經過字符串查找的方式來查找:

 1  String s;
 2  //  Protect against multiple access to sw.
 3  synchronized  (sw) {
 4      t.printStackTrace(pw);
 5      s  =  sw.toString();
 6      sw.getBuffer().setLength( 0 );
 7  }
 8  int  ibegin, iend;
 9  ibegin  =  s.lastIndexOf(fqnOfCallingClass);
10  if  (ibegin  ==   - 1 )
11       return ;
12  //  See bug 44888.
13  if  (ibegin  +  fqnOfCallingClass.length()  <  s.length()
14           &&  s.charAt(ibegin  +  fqnOfCallingClass.length())  !=   ' . ' ) {
15       int  i  =  s.lastIndexOf(fqnOfCallingClass  +   " . " );
16       if  (i  !=   - 1 ) {
17          ibegin  =  i;
18      }
19  }
20 
21  ibegin  =  s.indexOf(Layout.LINE_SEP, ibegin);
22  if  (ibegin  ==   - 1 )
23       return ;
24  ibegin  +=  Layout.LINE_SEP_LEN;
25 
26  //  determine end of line
27  iend  =  s.indexOf(Layout.LINE_SEP, ibegin);
28  if  (iend  ==   - 1 )
29       return ;
30 
31  //  VA has a different stack trace format which doesn't
32  //  need to skip the inital 'at'
33  if  ( ! inVisualAge) {
34       //  back up to first blank character
35      ibegin  =  s.lastIndexOf( " at  " , iend);
36       if  (ibegin  ==   - 1 )
37           return ;
38       //  Add 3 to skip "at ";
39      ibegin  +=   3 ;
40  }
41  //  everything between is the requested stack item
42  this .fullInfo  =  s.substring(ibegin, iend);

對於經過字符串查找到的fullInfo值,在獲取其餘單個值時還須要作相應的字符串解析:
className:

 1  //  Starting the search from '(' is safer because there is
 2  //  potentially a dot between the parentheses.
 3  int  iend  =  fullInfo.lastIndexOf( ' ( ' );
 4  if  (iend  ==   - 1 )
 5      className  =  NA;
 6  else  {
 7      iend  =  fullInfo.lastIndexOf( ' . ' , iend);
 8 
 9       //  This is because a stack trace in VisualAge looks like:
10 
11       //  java.lang.RuntimeException
12       //  java.lang.Throwable()
13       //  java.lang.Exception()
14       //  java.lang.RuntimeException()
15       //  void test.test.B.print()
16       //  void test.test.A.printIndirect()
17       //  void test.test.Run.main(java.lang.String [])
18       int  ibegin  =   0 ;
19       if  (inVisualAge) {
20          ibegin  =  fullInfo.lastIndexOf( '   ' , iend)  +   1 ;
21      }
22 
23       if  (iend  ==   - 1 )
24          className  =  NA;
25       else
26          className  =   this .fullInfo.substring(ibegin, iend);

 

fileName:
1 
2  int  iend  =  fullInfo.lastIndexOf( ' : ' );
3  if  (iend  ==   - 1 )
4      fileName  =  NA;
5  else  {
6       int  ibegin  =  fullInfo.lastIndexOf( ' ( ' , iend  -   1 );
7      fileName  =   this .fullInfo.substring(ibegin  +   1 , iend);
8  }
lineNumber:
1  int  iend  =  fullInfo.lastIndexOf( ' ) ' );
2  int  ibegin  =  fullInfo.lastIndexOf( ' : ' , iend  -   1 );
3  if  (ibegin  ==   - 1 )
4      lineNumber  =  NA;
5  else
6      lineNumber  =   this .fullInfo.substring(ibegin  +   1 , iend);
methodName:
1  int  iend  =  fullInfo.lastIndexOf( ' ( ' );
2  int  ibegin  =  fullInfo.lastIndexOf( ' . ' , iend);
3  if  (ibegin  ==   - 1 )
4      methodName  =  NA;
5  else
6      methodName  =   this .fullInfo.substring(ibegin  +   1 , iend);

ObjectRender接口

Log4J中,對傳入的message實例,若是是非String類型,會先使用註冊的ObjectRender(在LogRepository中查找註冊的ObjectRender信息)處理成String後返回,若沒有找到相應的ObjectRender,則使用默認的ObjectRender,它只是調用該消息實例的toString()方法。

 1  public  Object getMessage() {
 2       if  (message  !=   null ) {
 3           return  message;
 4      }  else  {
 5           return  getRenderedMessage();
 6      }
 7  }
 8  public  String getRenderedMessage() {
 9       if  (renderedMessage  ==   null   &&  message  !=   null ) {
10           if  (message  instanceof  String)
11              renderedMessage  =  (String) message;
12           else  {
13              LoggerRepository repository  =  logger.getLoggerRepository();
14 
15               if  (repository  instanceof  RendererSupport) {
16                  RendererSupport rs  =  (RendererSupport) repository;
17                  renderedMessage  =  rs.getRendererMap()
18                          .findAndRender(message);
19              }  else  {
20                  renderedMessage  =  message.toString();
21              }
22          }
23      }
24       return  renderedMessage;
25  }

ThrowableInformation類

ThrowableInformation類用以處理異常棧信息,即經過Throwable實例獲取異常棧字符串數組。同時還支持自定義的ThrowableRender(在LogRepository中設置),默認的ThrowableRender經過系統printStackTrace()方法來獲取信息:

 1  if  (throwable  !=   null ) {
 2       this .throwableInfo  =   new  ThrowableInformation(throwable, logger);
 3  }
 4  ThrowableRenderer renderer  =   null ;
 5  if  (category  !=   null ) {
 6      LoggerRepository repo  =  category.getLoggerRepository();
 7       if  (repo  instanceof  ThrowableRendererSupport) {
 8          renderer  =  ((ThrowableRendererSupport) repo)
 9                  .getThrowableRenderer();
10      }
11  }
12  if  (renderer  ==   null ) {
13      rep  =  DefaultThrowableRenderer.render(throwable);
14  else  {
15      rep  =  renderer.doRender(throwable);
16  }
17  public   static  String[] render( final  Throwable throwable) {
18      StringWriter sw  =   new  StringWriter();
19      PrintWriter pw  =   new  PrintWriter(sw);
20       try  {
21          throwable.printStackTrace(pw);
22      }  catch  (RuntimeException ex) {
23      }
24      pw.flush();
25      LineNumberReader reader  =   new  LineNumberReader( new  StringReader(
26              sw.toString()));
27      ArrayList lines  =   new  ArrayList();
28       try  {
29          String line  =  reader.readLine();
30           while  (line  !=   null ) {
31              lines.add(line);
32              line  =  reader.readLine();
33          }
34      }  catch  (IOException ex) {
35           if  (ex  instanceof  InterruptedIOException) {
36              Thread.currentThread().interrupt();
37          }
38          lines.add(ex.toString());
39      }
40      String[] tempRep  =   new  String[lines.size()];
41      lines.toArray(tempRep);
42       return  tempRep;
43  }

Layout類

Layout負責將LoggingEvent中的信息格式化成一行日誌信息。對不一樣格式的日誌可能還須要提供頭和尾等信息。另外有些Layout不會處理異常信息,此時ignoresThrowable()方法返回false,而且異常信息須要Appender來處理,如PatternLayout。

 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  }

Layout的實現比較簡單,如SimpleLayout對一行日誌信息只是打印日誌級別信息以及日誌信息。

 1  public   class  SimpleLayout  extends  Layout {
 2      StringBuffer sbuf  =   new  StringBuffer( 128 );
 3       public  SimpleLayout() {
 4      }
 5       public   void  activateOptions() {
 6      }
 7       public  String format(LoggingEvent event) {
 8          sbuf.setLength( 0 );
 9          sbuf.append(event.getLevel().toString());
10          sbuf.append( "  -  " );
11          sbuf.append(event.getRenderedMessage());
12          sbuf.append(LINE_SEP);
13           return  sbuf.toString();
14      }
15       public   boolean  ignoresThrowable() {
16           return   true ;
17      }
18  }

關於Layout更詳細的信息將會在之後小節中介紹。

Appender接口

Appender負責定義日誌輸出的目的地,它能夠是控制檯(ConsoleAppender)、文件(FileAppender)、JMS服務器(JmsLogAppender)、以Email的形式發送出去(SMTPAppender)等。Appender是一個命名的實體,另外它還包含了對Layout、ErrorHandler、Filter等引用:

 1  public   interface  Appender {
 2       void  addFilter(Filter newFilter);
 3       public  Filter getFilter();
 4       public   void  clearFilters();
 5       public   void  close();
 6       public   void  doAppend(LoggingEvent event);
 7       public  String getName();
 8       public   void  setErrorHandler(ErrorHandler errorHandler);
 9       public  ErrorHandler getErrorHandler();
10       public   void  setLayout(Layout layout);
11       public  Layout getLayout();
12       public   void  setName(String name);
13       public   boolean  requiresLayout();
14  }

簡單的,在配置文件中,Appender會註冊到Logger中,Logger在寫日誌時,經過繼承機制遍歷全部註冊到它自己和其父節點的Appender(在additivity爲true的狀況下),調用doAppend()方法,實現日誌的寫入。在doAppend方法中,若當前Appender註冊了Filter,則doAppend還會判斷當前日誌時候經過了Filter的過濾,經過了Filter的過濾後,若是當前Appender繼承自SkeletonAppender,還會檢查當前日誌級別時候要比當前Appender自己的日誌級別閥門要打,全部這些都經過後,纔會將LoggingEvent實例傳遞給Layout實例以格式化成一行日誌信息,最後寫入相應的目的地,在這些操做中,任何出現的錯誤都由ErrorHandler字段來處理。

SkeletonAppender類

目前Log4J實現的Appender都繼承自SkeletonAppender類,該類對Appender接口提供了最基本的實現,而且引入了Threshold的概念,即全部的比當前Appender定義的日誌級別閥指要大的日誌纔會記錄下來。

 1  public   abstract   class  AppenderSkeleton  implements  Appender, OptionHandler {
 2       protected  Layout layout;
 3       protected  String name;
 4       protected  Priority threshold;
 5       protected  ErrorHandler errorHandler  =   new  OnlyOnceErrorHandler();
 6       protected  Filter headFilter;
 7       protected  Filter tailFilter;
 8       protected   boolean  closed  =   false ;
 9       public  AppenderSkeleton() {
10           super ();
11      }
12       public   void  activateOptions() {
13      }
14       abstract   protected   void  append(LoggingEvent event);
15       public   boolean  isAsSevereAsThreshold(Priority priority) {
16           return  ((threshold  ==   null ||  priority.isGreaterOrEqual(threshold));
17      }
18       public   synchronized   void  doAppend(LoggingEvent event) {
19           if  (closed) {
20              LogLog.error( " Attempted to append to closed appender named [ "
21                       +  name  +   " ]. " );
22               return ;
23          }
24           if  ( ! isAsSevereAsThreshold(event.getLevel())) {
25               return ;
26          }
27          Filter f  =   this .headFilter;
28          FILTER_LOOP:  while  (f  !=   null ) {
29               switch  (f.decide(event)) {
30               case  Filter.DENY:
31                   return ;
32               case  Filter.ACCEPT:
33                   break  FILTER_LOOP;
34               case  Filter.NEUTRAL:
35                  f  =  f.getNext();
36              }
37          }
38           this .append(event);
39      }
40  public   void  finalize() {
41           if  ( this .closed)
42               return ;
43          LogLog.debug( " Finalizing appender named [ "   +  name  +   " ]. " );
44          close();
45      }
46  }

SkeletonAppender實現了doAppend()方法,它首先檢查日誌級別是否要比threshold要大;而後若是註冊了Filter,則使用Filter對LoggingEvent實例進行過濾,若是Filter返回Filter.DENY則doAppend()退出,不然執行append()方法,該方法由子類實現。

在Log4J中,Filter組成一條鏈,它定了以decide()方法,由子類實現,若返回DENY則日誌不會被記錄、NEUTRAL則繼續檢查下一個Filter實例、ACCEPT則Filter經過,繼續執行後面的寫日誌操做。使用Filter能夠爲Appender加入一些出了threshold之外的其餘邏輯,因爲它自己是鏈狀的,並且它的執行是橫跨在Appender的doAppend方法中,於是這也是一個典型的AOP的概念。Filter的實現將會在下一小節中講解。

SkeletonAppender還重寫了finalize()方法,這是由於Log4J自己做爲一個組件,它可能仍是經過其餘組件如commons-logging或slf4j組件間接的引入,於是使用它的程序不該該對它存在依賴的,然而在程序退出以前全部的Appender須要調用close()方法以釋放它所佔據的資源,爲了避免在使用Log4J的程序手動的close()的方法,以減小Log4J代碼的侵入性,於是Log4J將close()的方法調用加入到finalize()方法中,即在垃圾回收器回收Appender實例時就會調用它的close()方法。

WriterAppender類和ConsoleAppender類

WriterAppender將日誌寫入Java IO中,它繼承自SkeletonAppender類。它引入了三個字段:immediateFlush,指定沒寫完一條日誌後,即將日誌內容刷新到設備中,雖然這麼作會有一點性能上的損失,可是若是不怎麼作,則會出如今程序異常終止的時候沒法看到部分日誌信息,而常常這些丟失的日誌信息要用於分析爲何會出現異常終止的狀況,於是通常推薦將該值設置爲true,即默認值;econding用於定義日誌文本的編碼方式;qw定義寫日誌的writer,它能夠是文件或是控制檯等Java IO支持的流。

在寫日誌文本前,WriterAppender還會作其餘檢查,如該Appender不能已經closed、qw和layout必須有值等,然後才能夠將layout格式化後的日誌行寫入設備中。若layout自己不處理異常問題,則有Appender處理異常問題。最後若是每行日誌須要刷新,則調用刷新操做。

 1  public   class  WriterAppender  extends  AppenderSkeleton {
 2       protected   boolean  immediateFlush  =   true ;
 3       protected  String encoding;
 4       protected  QuietWriter qw;
 5       public  WriterAppender() {
 6      }
 7       public  WriterAppender(Layout layout, OutputStream os) {
 8           this (layout,  new  OutputStreamWriter(os));
 9      }
10       public  WriterAppender(Layout layout, Writer writer) {
11           this .layout  =  layout;
12           this .setWriter(writer);
13      }
14       public   void  append(LoggingEvent event) {
15           if  ( ! checkEntryConditions()) {
16               return ;
17          }
18          subAppend(event);
19      }
20       protected   boolean  checkEntryConditions() {
21           if  ( this .closed) {
22              LogLog.warn( " Not allowed to write to a closed appender. " );
23               return   false ;
24          }
25           if  ( this .qw  ==   null ) {
26              errorHandler
27                      .error( " No output stream or file set for the appender named [ "
28                               +  name  +   " ]. " );
29               return   false ;
30          }
31           if  ( this .layout  ==   null ) {
32              errorHandler.error( " No layout set for the appender named [ "   +  name
33                       +   " ]. " );
34               return   false ;
35          }
36           return   true ;
37      }
38       protected   void  subAppend(LoggingEvent event) {
39           this .qw.write( this .layout.format(event));
40           if  (layout.ignoresThrowable()) {
41              String[] s  =  event.getThrowableStrRep();
42               if  (s  !=   null ) {
43                   int  len  =  s.length;
44                   for  ( int  i  =   0 ; i  <  len; i ++ ) {
45                       this .qw.write(s[i]);
46                       this .qw.write(Layout.LINE_SEP);
47                  }
48              }
49          }
50           if  (shouldFlush(event)) {
51               this .qw.flush();
52          }
53      }
54       public   boolean  requiresLayout() {
55           return   true ;
56      }
57  }

ConsoleAppender繼承自WriterAppender,它只是簡單的將System.out或System.err實例傳遞給WriterAppender以構建相應的writer,最後實現將日誌寫入到控制檯中。

Filter類

在Log4J中,Filter組成一條鏈,它定了以decide()方法,由子類實現,若返回DENY則日誌不會被記錄、NEUTRAL則繼續檢查下一個Filter實例、ACCEPT則Filter經過,繼續執行後面的寫日誌操做。使用Filter能夠爲Appender加入一些出了threshold之外的其餘邏輯,因爲它自己是鏈狀的,並且它的執行是橫跨在Appender的doAppend方法中,於是這也是一個典型的AOP的概念。

 1  public   abstract   class  Filter  implements  OptionHandler {
 2       public  Filter next;
 3       public   static   final   int  DENY  =   - 1 ;
 4       public   static   final   int  NEUTRAL  =   0 ;
 5       public   static   final   int  ACCEPT  =   1 ;
 6       public   void  activateOptions() {
 7      }
 8       abstract   public   int  decide(LoggingEvent event);
 9       public   void  setNext(Filter next) {
10           this .next  =  next;
11      }
12       public  Filter getNext() {
13           return  next;
14      }
15  }

Log4J自己提供了四個Filter:DenyAllFilter、LevelMatchFilter、LevelRangeFilter、StringMatchFilter。

DenyAllFilter只是簡單的在decide()中返回DENY值,能夠將其應用在Filter鏈尾,實現若是以前的Filter都沒有經過,則該LoggingEvent沒有經過,相似或的操做:

1  public   class  DenyAllFilter  extends  Filter {
2       public   int  decide(LoggingEvent event) {
3           return  Filter.DENY;
4      }
5  }

StringMatchFilter經過日誌消息中的字符串來判斷Filter後的狀態:

 1  public   class  StringMatchFilter  extends  Filter {
 2       boolean  acceptOnMatch  =   true ;
 3      String stringToMatch;
 4       public   int  decide(LoggingEvent event) {
 5          String msg  =  event.getRenderedMessage();
 6           if  (msg  ==   null   ||  stringToMatch  ==   null )
 7               return  Filter.NEUTRAL;
 8           if  (msg.indexOf(stringToMatch)  ==   - 1 ) {
 9               return  Filter.NEUTRAL;
10          }  else  {  //  we've got a match
11               if  (acceptOnMatch) {
12                   return  Filter.ACCEPT;
13              }  else  {
14                   return  Filter.DENY;
15              }
16          }
17      }
18  }

LevelMatchFilter判斷日誌級別是否和設置的級別匹配以決定Filter後的狀態:

 1  public   class  LevelMatchFilter  extends  Filter {
 2       boolean  acceptOnMatch  =   true ;    
 3  Level levelToMatch;
 4       public   int  decide(LoggingEvent event) {
 5           if  ( this .levelToMatch  ==   null ) {
 6               return  Filter.NEUTRAL;
 7          }
 8           boolean  matchOccured  =   false ;
 9           if  ( this .levelToMatch.equals(event.getLevel())) {
10              matchOccured  =   true ;
11          }
12           if  (matchOccured) {
13               if  ( this .acceptOnMatch)
14                   return  Filter.ACCEPT;
15               else
16                   return  Filter.DENY;
17          }  else  {
18               return  Filter.NEUTRAL;
19          }
20      }
21  }

LevelRangeFilter判斷日誌級別是否在設置的級別範圍內以決定Filter後的狀態:

 1  public   class  LevelRangeFilter  extends  Filter {
 2       boolean  acceptOnMatch  =   false ;
 3      Level levelMin;
 4      Level levelMax;
 5       public   int  decide(LoggingEvent event) {
 6           if  ( this .levelMin  !=   null ) {
 7               if  (event.getLevel().isGreaterOrEqual(levelMin)  ==   false ) {
 8                   return  Filter.DENY;
 9              }
10          }
11           if  ( this .levelMax  !=   null ) {
12               if  (event.getLevel().toInt()  >  levelMax.toInt()) {
13                   return  Filter.DENY;
14              }
15          }
16           if  (acceptOnMatch) {
17               return  Filter.ACCEPT;
18          }  else  {
19               return  Filter.NEUTRAL;
20          }
21      }
22  }

總結

這一系列終因而結束了。本文主要介紹了Log4J核心類的實現和他們之間的交互關係。涉及到各個模塊自己的其餘詳細信息將會在接下來的小節中詳細介紹,如LogRepository與配置信息、Appender類結構的詳細信息、Layout類結構的詳細信息以及部分LoggingEvent提供的高級功能。而像Level、Logger自己,因爲內容很少,已經在這一小節中所有介紹完了。

相關文章
相關標籤/搜索