<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0

    畢業(yè)又趕上本科的同學(xué)會(huì),還去騎車(chē)環(huán)了趟崇明島,六月貌似就沒(méi)消停過(guò),不過(guò)終于這些事情基本上都結(jié)束了,我也可以好好的看些書(shū)、讀些源碼、寫(xiě)點(diǎn)博客了。

    Log4J將寫(xiě)日志功能抽象成七個(gè)核心類/接口:LoggerLoggerRepositoryLevelLoggingEventAppenderLayoutObjectRender。其類圖如下:


    更詳細(xì)的,實(shí)現(xiàn)Log4J主要功能相關(guān)的類圖:

    其實(shí)Log4J最核心的也就5個(gè)類:Logger用于對(duì)日志記錄行為的抽象,提供記錄不同級(jí)別日志的接口;Level對(duì)日志級(jí)別的抽象;Appender是對(duì)記錄日志形式的抽象;Layout是對(duì)日志行格式的抽象;而LoggingEvent是對(duì)一次日志記錄過(guò)程中所能取到信息的抽象。另外兩個(gè)LoggerRepositoryLogger實(shí)例的容器,而ObjectRender是對(duì)日志實(shí)例的解析接口,它們主要提供了一種擴(kuò)展支持。

    簡(jiǎn)單的一次記錄日志過(guò)程的序列圖如下:


    即獲取Logger實(shí)例->判斷Logger實(shí)例對(duì)應(yīng)的日志記錄級(jí)別是否要比請(qǐng)求的級(jí)別低->若是調(diào)用forceLog記錄日志->創(chuàng)建LoggingEvent實(shí)例->LoggingEvent實(shí)例傳遞給Appender->Appender調(diào)用Layout實(shí)例格式化日志消息->Appender將格式化后的日志信息寫(xiě)入該Appender對(duì)應(yīng)的日志輸出中。

    包含Log4J其他模塊類的更詳細(xì)序列圖如下:

    在簡(jiǎn)單的介紹了Log4J各個(gè)模塊類的作用后,以下將詳細(xì)的介紹各個(gè)模塊的具體作用以及代碼實(shí)現(xiàn)。

    Logger

    Logger是對(duì)記錄日志動(dòng)作的抽象,它提供了記錄不同級(jí)別日志的接口,日志信息可以包含異常信息也可以不包含:

     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信息 ,如果當(dāng)前Logger未設(shè)置Level值,它也可以中父節(jié)點(diǎn)中繼承下來(lái),該值可以用來(lái)控制該Logger可以記錄的日志級(jí)別:

     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是一個(gè)命名的實(shí)體,其名字一般用”.”分割以體現(xiàn)不同Logger的層次關(guān)系,其中LevelAppender信息可以從父節(jié)點(diǎn)中獲取,因而Logger類中還具有nameparent屬性。

    1 private String name;
    2 protected Logger parent;

    在某些情況下,我們希望某些Logger只將日志記錄到特定的Appender中,而不想記錄在父節(jié)點(diǎn)中的Appender中,Log4J為這種需求提供了additivity屬性,即對(duì)當(dāng)前Logger節(jié)點(diǎn),如果其additivity屬性設(shè)置為false,則該Logger不會(huì)繼承父節(jié)點(diǎn)的Appender信息,但是其子節(jié)點(diǎn)依然會(huì)繼承該LoggerAppender信息,除非子節(jié)點(diǎn)的additivity屬性也設(shè)置成了false

     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 }

    最后,為了支持國(guó)際化,Log4J還提供了兩個(gè)l7dlog()方法,通過(guò)指定的key,以從資源文件中獲取消息內(nèi)容。為了使用這兩個(gè)方法,需要設(shè)置資源文件。同樣,資源文件也是可以從父節(jié)點(diǎn)中繼承的。

     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 }

    另外,在實(shí)際開(kāi)發(fā)中經(jīng)常會(huì)遇到要把日志信息同時(shí)寫(xiě)到不同地方,如同時(shí)寫(xiě)入文件和控制臺(tái),因而一個(gè)Logger實(shí)例中可以包含多個(gè)Appender,為了管理多個(gè)AppenderLog4J抽象出了AppenderAttachable接口,它定義了幾個(gè)用于管理多個(gè)Appender實(shí)例的方法,這些方法由AppenderAttachableImpl類實(shí)現(xiàn),而Logger會(huì)實(shí)例化AppenderAttachableImpl,并將這些方法代理給該實(shí)例:

     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實(shí)例組成一個(gè)單根的樹(shù)狀結(jié)構(gòu),由于Logger實(shí)例的根節(jié)點(diǎn)有一點(diǎn)特殊:它的名字為“root”,它沒(méi)有父節(jié)點(diǎn),它的Level字段必須設(shè)值以防止其他Logger實(shí)例都沒(méi)有設(shè)置Level值的情況。基于這些考慮,Log4J通過(guò)繼承Logger類實(shí)現(xiàn)了RootLogger類,它用于表達(dá)所有Logger實(shí)例的根節(jié)點(diǎn):
     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

    有時(shí)候,為了測(cè)試等其他需求,我們希望Logger本身不做什么事情,Log4J為這種需求提供了NOPLogger類,它繼承自Logger,但是基本上的方法都為空。

    Level

    Level是對(duì)日志級(jí)別的抽象,目前Log4J支持的級(jí)別有FATALERRORWARNINFODEBUGTRACE,從頭到尾一次級(jí)別遞減,另外Log4J還支持兩種特殊的級(jí)別:ALLOFF,它們分別表示打開(kāi)和關(guān)閉日志功能。

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

    每個(gè)Level實(shí)例包含了該Level代表的int值(一般是從級(jí)別低到級(jí)別高一次增大)、該LevelString表達(dá)、該Level和系統(tǒng)Level的對(duì)應(yīng)值。

    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類主要提供了判斷哪個(gè)Level級(jí)別更高的方法isGreaterOrEqual()以及將int值或String值轉(zhuǎn)換成Level實(shí)例的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 }

    另外,由于對(duì)相同級(jí)別的Level實(shí)例來(lái)說(shuō),它必須是單例的,因而Log4J對(duì)序列化和反序列化做了一些處理。即它的三個(gè)成員都是transient,真正序列化和反序列化的代碼自己寫(xiě),并且加入readResolve()方法的支持,以保證反序列化出來(lái)的相同級(jí)別的Level實(shí)例是相同的實(shí)例。

     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 }

    如果要實(shí)現(xiàn)自己的Level類,可以繼承自Level,并且實(shí)現(xiàn)相應(yīng)的靜態(tài)toLevel()方法即可。關(guān)于如何實(shí)現(xiàn)自己的Level類將會(huì)在配置文件相關(guān)小節(jié)中詳細(xì)討論。

    LoggerRepository

    LoggerRepository從概念以及字面上來(lái)說(shuō)它就是一個(gè)Logger實(shí)例的容器:一方面相同名字的Logger實(shí)例只需要?jiǎng)?chuàng)建一次,在后面的使用中,只需要從這個(gè)容器中取即可;另一方面,Logger容器可以存放從配置文件中解析出來(lái)的信息,從而使配置信息可以無(wú)縫的應(yīng)用到Log4J內(nèi)部系統(tǒng)中;最后Logger容器還為維護(hù)Logger的樹(shù)狀層次結(jié)構(gòu)提供了方面,每個(gè)Logger只維護(hù)父節(jié)點(diǎn)的信息,有了Logger容器的存在則可以很容易的找到一個(gè)新的Logger實(shí)例的父節(jié)點(diǎn);關(guān)于Logger容器將在下一節(jié)中詳細(xì)講解。

    LoggingEvent

    LoggingEvent個(gè)人感覺(jué)用LoggingContext更合適一些,它是對(duì)一次日志記錄時(shí)哪能獲取到的數(shù)據(jù)的封裝。它包含了以下信息以提供Layoutformat()方法中使用:

    1.       fqnOfCategoryClass:日志記錄接口(默認(rèn)為Logger)的類全名,該信息主要用于計(jì)算日志記錄點(diǎn)的源文件、調(diào)用方法以及行號(hào)等位置信息。

    2.       locationInfo:通過(guò)fqnOfCategoryClass計(jì)算位置信息,位置信息的計(jì)算由LocationInfo類實(shí)現(xiàn),這些信息可以提供給Layout使用。

    3.       logger:目前來(lái)看主要是通過(guò)Logger實(shí)例取得LogRepository實(shí)例,并通過(guò)LogRepository取得注冊(cè)的ObjectRender實(shí)例,如果有的話。

    4.       loggerName:當(dāng)前日志記錄的Logger名稱,提供給Layout使用。

    5.       threadName:當(dāng)前線程名,提供給Layout使用。

    6.       level:當(dāng)前日志的級(jí)別,提供給Layout使用。

    7.       message:當(dāng)前日志類,一般是String類型,但是也可以通過(guò)注冊(cè)ObjectRender,然后傳入相應(yīng)的其他對(duì)象類型。

    8.       renderedMessage:經(jīng)過(guò)ObjectRender處理后的日志信息,提供給Layout使用。

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

    10.   timestamp:創(chuàng)建LoggingEvent實(shí)例的時(shí)間,提供給Layout使用。

    11.   其他相對(duì)不常用的信息將會(huì)在后面小節(jié)中講解。

    LoggingEvent只是一個(gè)簡(jiǎn)單的數(shù)據(jù)對(duì)象(DO),因而其實(shí)現(xiàn)還是比較簡(jiǎn)單的,即在創(chuàng)建實(shí)例時(shí)將數(shù)據(jù)提供給它,在其他類(Layout等)使用它時(shí)通過(guò)getXXX()方法取數(shù)據(jù)。不過(guò)還是有幾個(gè)方法可以簡(jiǎn)單的講解一下。

    LocationInfo類計(jì)算位置信息

    LocationInfo所指的位置信息主要包括記錄日志所在的源文件名、類名、方法名、所在源文件的行號(hào)。

    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;

    我們知道在異常棧中每一條記錄都包含了方法調(diào)用對(duì)應(yīng)的這些信息,Log4J的這些信息正是利用了這個(gè)原理,即通過(guò)構(gòu)建一個(gè)Throwable實(shí)例,而后在該Throwable的棧信息中解析出來(lái)的:

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

    以上Throwable一般會(huì)產(chǎn)生如下異常棧:

    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 

    因而我們就可以通過(guò)callers.fully.qualified.className信息來(lái)找到改行信息,這個(gè)className信息即是傳入的fqnOfCategoryClass

    如果當(dāng)前JDK版本是1.4以上,我們就可以通過(guò)JDK提供的一些方法來(lái)查找:

     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 }

    否則,則需要我們通過(guò)字符串查找的方式來(lái)查找:

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

    對(duì)于通過(guò)字符串查找到的fullInfo值,在獲取其他單個(gè)值時(shí)還需要做相應(yīng)的字符串解析:
    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中,對(duì)傳入的message實(shí)例,如果是非String類型,會(huì)先使用注冊(cè)的ObjectRender(在LogRepository中查找注冊(cè)的ObjectRender信息)處理成String后返回,若沒(méi)有找到相應(yīng)的ObjectRender,則使用默認(rèn)的ObjectRender,它只是調(diào)用該消息實(shí)例的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類用以處理異常棧信息,即通過(guò)Throwable實(shí)例獲取異常棧字符串?dāng)?shù)組。同時(shí)還支持自定義的ThrowableRender(在LogRepository中設(shè)置),默認(rèn)的ThrowableRender通過(guò)系統(tǒng)printStackTrace()方法來(lái)獲取信息:

     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負(fù)責(zé)將LoggingEvent中的信息格式化成一行日志信息。對(duì)不同格式的日志可能還需要提供頭和尾等信息。另外有些Layout不會(huì)處理異常信息,此時(shí)ignoresThrowable()方法返回false,并且異常信息需要Appender來(lái)處理,如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的實(shí)現(xiàn)比較簡(jiǎn)單,如SimpleLayout對(duì)一行日志信息只是打印日志級(jí)別信息以及日志信息。

     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 }

    關(guān)于Layout更詳細(xì)的信息將會(huì)在以后小節(jié)中介紹。

    Appender接口

    Appender負(fù)責(zé)定義日志輸出的目的地,它可以是控制臺(tái)(ConsoleAppender)、文件(FileAppender)、JMS服務(wù)器(JmsLogAppender)、以Email的形式發(fā)送出去(SMTPAppender)等。Appender是一個(gè)命名的實(shí)體,另外它還包含了對(duì)LayoutErrorHandlerFilter等引用:

     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 }

    簡(jiǎn)單的,在配置文件中,Appender會(huì)注冊(cè)到Logger中,Logger在寫(xiě)日志時(shí),通過(guò)繼承機(jī)制遍歷所有注冊(cè)到它本身和其父節(jié)點(diǎn)的Appender(在additivitytrue的情況下),調(diào)用doAppend()方法,實(shí)現(xiàn)日志的寫(xiě)入。在doAppend方法中,若當(dāng)前Appender注冊(cè)了Filter,則doAppend還會(huì)判斷當(dāng)前日志時(shí)候通過(guò)了Filter的過(guò)濾,通過(guò)了Filter的過(guò)濾后,如果當(dāng)前Appender繼承自SkeletonAppender,還會(huì)檢查當(dāng)前日志級(jí)別時(shí)候要比當(dāng)前Appender本身的日志級(jí)別閥門(mén)要打,所有這些都通過(guò)后,才會(huì)將LoggingEvent實(shí)例傳遞給Layout實(shí)例以格式化成一行日志信息,最后寫(xiě)入相應(yīng)的目的地,在這些操作中,任何出現(xiàn)的錯(cuò)誤都由ErrorHandler字段來(lái)處理。

    SkeletonAppender

    目前Log4J實(shí)現(xiàn)的Appender都繼承自SkeletonAppender類,該類對(duì)Appender接口提供了最基本的實(shí)現(xiàn),并且引入了Threshold的概念,即所有的比當(dāng)前Appender定義的日志級(jí)別閥指要大的日志才會(huì)記錄下來(lái)。

     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實(shí)現(xiàn)了doAppend()方法,它首先檢查日志級(jí)別是否要比threshold要大;然后如果注冊(cè)了Filter,則使用Filter對(duì)LoggingEvent實(shí)例進(jìn)行過(guò)濾,如果Filter返回Filter.DENYdoAppend()退出,否則執(zhí)行append()方法,該方法由子類實(shí)現(xiàn)。

    Log4J中,Filter組成一條鏈,它定了以decide()方法,由子類實(shí)現(xiàn),若返回DENY則日志不會(huì)被記錄、NEUTRAL則繼續(xù)檢查下一個(gè)Filter實(shí)例、ACCEPTFilter通過(guò),繼續(xù)執(zhí)行后面的寫(xiě)日志操作。使用Filter可以為Appender加入一些出了threshold以外的其他邏輯,由于它本身是鏈狀的,而且它的執(zhí)行是橫跨在AppenderdoAppend方法中,因而這也是一個(gè)典型的AOP的概念。Filter的實(shí)現(xiàn)將會(huì)在下一小節(jié)中講解。

    SkeletonAppender還重寫(xiě)了finalize()方法,這是因?yàn)?/span>Log4J本身作為一個(gè)組件,它可能還是通過(guò)其他組件如commons-loggingslf4j組件間接的引入,因而使用它的程序不應(yīng)該對(duì)它存在依賴的,然而在程序退出之前所有的Appender需要調(diào)用close()方法以釋放它所占據(jù)的資源,為了不在使用Log4J的程序手動(dòng)的close()的方法,以減少Log4J代碼的侵入性,因而Log4Jclose()的方法調(diào)用加入到finalize()方法中,即在垃圾回收器回收Appender實(shí)例時(shí)就會(huì)調(diào)用它的close()方法。

    WriterAppender類和ConsoleAppender

    WriterAppender將日志寫(xiě)入Java IO中,它繼承自SkeletonAppender類。它引入了三個(gè)字段:immediateFlush,指定沒(méi)寫(xiě)完一條日志后,即將日志內(nèi)容刷新到設(shè)備中,雖然這么做會(huì)有一點(diǎn)性能上的損失,但是如果不怎么做,則會(huì)出現(xiàn)在程序異常終止的時(shí)候無(wú)法看到部分日志信息,而經(jīng)常這些丟失的日志信息要用于分析為什么會(huì)出現(xiàn)異常終止的情況,因而一般推薦將該值設(shè)置為true,即默認(rèn)值;econding用于定義日志文本的編碼方式;qw定義寫(xiě)日志的writer,它可以是文件或是控制臺(tái)等Java IO支持的流。

    在寫(xiě)日志文本前,WriterAppender還會(huì)做其他檢查,如該Appender不能已經(jīng)closedqwlayout必須有值等,而后才可以將layout格式化后的日志行寫(xiě)入設(shè)備中。若layout本身不處理異常問(wèn)題,則有Appender處理異常問(wèn)題。最后如果每行日志需要刷新,則調(diào)用刷新操作。

     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,它只是簡(jiǎn)單的將System.outSystem.err實(shí)例傳遞給WriterAppender以構(gòu)建相應(yīng)的writer,最后實(shí)現(xiàn)將日志寫(xiě)入到控制臺(tái)中。

    Filter

    Log4J中,Filter組成一條鏈,它定了以decide()方法,由子類實(shí)現(xiàn),若返回DENY則日志不會(huì)被記錄、NEUTRAL則繼續(xù)檢查下一個(gè)Filter實(shí)例、ACCEPTFilter通過(guò),繼續(xù)執(zhí)行后面的寫(xiě)日志操作。使用Filter可以為Appender加入一些出了threshold以外的其他邏輯,由于它本身是鏈狀的,而且它的執(zhí)行是橫跨在AppenderdoAppend方法中,因而這也是一個(gè)典型的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本身提供了四個(gè)FilterDenyAllFilterLevelMatchFilterLevelRangeFilterStringMatchFilter

    DenyAllFilter只是簡(jiǎn)單的在decide()中返回DENY值,可以將其應(yīng)用在Filter鏈尾,實(shí)現(xiàn)如果之前的Filter都沒(méi)有通過(guò),則該LoggingEvent沒(méi)有通過(guò),類似或的操作:

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

    StringMatchFilter通過(guò)日志消息中的字符串來(lái)判斷Filter后的狀態(tài):

     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判斷日志級(jí)別是否和設(shè)置的級(jí)別匹配以決定Filter后的狀態(tài):

     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判斷日志級(jí)別是否在設(shè)置的級(jí)別范圍內(nèi)以決定Filter后的狀態(tài):

     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 }

    總結(jié)

    這一系列終于是結(jié)束了。本文主要介紹了Log4J核心類的實(shí)現(xiàn)和他們之間的交互關(guān)系。涉及到各個(gè)模塊本身的其他詳細(xì)信息將會(huì)在接下來(lái)的小節(jié)中詳細(xì)介紹,如LogRepository與配置信息、Appender類結(jié)構(gòu)的詳細(xì)信息、Layout類結(jié)構(gòu)的詳細(xì)信息以及部分LoggingEvent提供的高級(jí)功能。而像LevelLogger本身,由于內(nèi)容不多,已經(jīng)在這一小節(jié)中全部介紹完了。

    posted on 2012-06-28 02:09 DLevin 閱讀(23981) 評(píng)論(5)  編輯  收藏 所屬分類: Logging

    FeedBack:
    # re: 深入Log4J源碼之Log4J Core
    2014-01-10 11:51 | 小梁
    大神,真心很佩服你啊,我最佩服的人就是看源碼和對(duì)開(kāi)源碼進(jìn)行二次開(kāi)發(fā)的人,從根本上理解問(wèn)題,能不能給點(diǎn)意見(jiàn),說(shuō)說(shuō)java之路啊?  回復(fù)  更多評(píng)論
      
    # re: 深入Log4J源碼之Log4J Core
    2014-01-14 22:52 | DLevin
    其一,我不是大神,其二,不是Java之路,語(yǔ)言是工具,重要的是在解決方法的思路和模式,其三,多學(xué)習(xí)、思考設(shè)計(jì)模式,要變通,多看源碼,理解整個(gè)框架思想,重要的是把路走通,不是把代碼看完。。。。。@小梁
      回復(fù)  更多評(píng)論
      
    # re: 深入Log4J源碼之Log4J Core
    2014-03-21 17:21 | 地里
    樓主,你畫(huà)這個(gè)圖是用啥工具  回復(fù)  更多評(píng)論
      
    # re: 深入Log4J源碼之Log4J Core
    2014-11-18 11:24 | 小蔥
    @DLevin
    我也喜歡看源碼,也喜歡思考,但是畢業(yè)快半年了,還沒(méi)你這水平  回復(fù)  更多評(píng)論
      
    # re: 深入Log4J源碼之Log4J Core
    2015-12-21 14:50 | wen
    問(wèn)題是
    1 很多方法不知道,邊看邊查
    2 類之間的關(guān)系如果復(fù)雜的話,該怎么看
    3看不出來(lái)設(shè)計(jì)模式。。。
    4 怎么開(kāi)始看  回復(fù)  更多評(píng)論
      
    主站蜘蛛池模板: 久久免费看少妇高潮V片特黄| 精品国产香蕉伊思人在线在线亚洲一区二区| 亚洲一区二区高清| 亚洲小视频在线播放| 久久久久久噜噜精品免费直播 | 日韩亚洲国产高清免费视频| 巨胸喷奶水视频www免费视频| 天天天欲色欲色WWW免费| 亚洲人成在线观看| fc2免费人成为视频| 色吊丝最新永久免费观看网站| 97se亚洲综合在线| 国产精品一区二区三区免费| 亚洲综合久久夜AV | www在线观看免费视频| 国产青草视频免费观看97| 亚洲国产中文在线视频| 99精品视频在线免费观看| 亚洲综合精品香蕉久久网| 国产亚洲精品美女| 日韩欧美一区二区三区免费观看| 亚洲国产综合专区在线电影 | 日韩色日韩视频亚洲网站| 国产福利在线观看免费第一福利| 亚洲AV无码久久| fc2免费人成在线| 亚洲精品乱码久久久久久中文字幕 | 久久久亚洲欧洲日产国码二区| 99久久婷婷免费国产综合精品| 亚洲成aⅴ人片久青草影院| 亚洲国产精品日韩av不卡在线| 无码区日韩特区永久免费系列| 青青草原亚洲视频| 国产午夜精品免费一区二区三区 | 两个人看的www高清免费观看| 久久久久亚洲精品影视| 97无码人妻福利免费公开在线视频| 国产亚洲日韩在线三区| 久久狠狠躁免费观看2020| 亚洲一区二区三区91| 无码国产精品一区二区免费式直播|