1. 程式人生 > >Log4J學習【二十七】常用Appender使用例子

Log4J學習【二十七】常用Appender使用例子

看完WriterAppender的程式碼之後,我們就隨便挑選兩個稍微簡單的ConsoleAppender和FilterAppender來看看,我們真正平時在使用的Appender是怎麼實現的。首先是ConsoleAppender:
public void activateOptions() {
        if (follow) {
            if (target.equals(SYSTEM_ERR)) {
               setWriter(createWriter(new SystemErrStream()));
            } else {
               setWriter(createWriter(new SystemOutStream()));
            }
        } else {
            if (target.equals(SYSTEM_ERR)) {
               setWriter(createWriter(System.err));
            } else {
               setWriter(createWriter(System.out));
            }
        }
        super.activateOptions();
  }

    在這段程式碼中,我們可以看到另一個我們之前並沒有介紹的屬性follow,這個屬性的作用非常簡單,如果follow為true,那麼就根據我們設定的target屬性(還記得ConsoleAppender的target屬性麼?)來選擇重新建立一個SystemErrStream或者SystemOutStream,如果follow為false,就是使用當前的System.out或System.err來輸出。在這個createWriter方法中,我們就只看一句程式碼就夠了:
retval = new OutputStreamWriter(os, enc);
    非常簡單,就是把傳進來的System.out或者System.err,和encoding(還記得WriterAppender上面的encoding屬性麼?)包裝成一個OutputStreamWriter。

    另外,這個類也沒有再去實現append方法或者subAppend方法,而直接是使用的WriterAppender去執行的。同時,看完程式碼之後,我們在使用ConsoleAppender設定的那些屬性到底是怎麼起作用的,也就非常明顯了。

    再來個例子,看看FileAppender,同理,還是先找acitivateOption再找append或者subAppend:
public void activateOptions() {
    if (fileName != null) {
        try {
            setFile(fileName, fileAppend, bufferedIO, bufferSize);
        } catch (java.io.IOException e) {
            errorHandler.error("setFile(" + fileName + "," + fileAppend + ") call failed.", e,
            ErrorCode.FILE_OPEN_FAILURE);
        }
    } else {
        LogLog.warn("File option not set for appender [" + name + "].");
        LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
    }
}

很簡單,首先檢查必須設定檔名,這裡的fileName即是我們設定的file屬性得到的;如果沒有問題,則使用setFile方法建立檔案和Writer,我們來看看setFile的程式碼:
public synchronized void setFile(String fileName, boolean append, boolean bufferedIO,
int bufferSize) throws IOException {
    LogLog.debug("setFile called: " + fileName + ", " + append);
    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);
    if (bufferedIO) {
        fw = new BufferedWriter(fw, bufferSize);
    }
}

    首先注意呼叫方法傳入的引數值,分別是fileName,append,bufferedIO和bufferSize。怎麼樣,這4個屬性名字都很熟悉吧。首先,根據fileName和append建立一個FileOutputStream;如果檔案不存在則建立這個檔案,再把這個檔案包裝成FileOutputStream;緊接著呼叫WriterAppender的createWriter方法,把FileOutputStream包裝成一個Writer(這個Writer就設定好了encoding了);接著,如果需要快取,則再把Writer用BufferedWriter包裝一次,即可使用。
    同樣,這個類也沒有再去實現append方法或者subAppend方法,而直接是使用的WriterAppender去執行的。其實ConsoleAppender和FileAppender都只是對用什麼東西來寫做了規定,其他的比如怎麼寫,就都是WriterAppender來規範的。

    程式碼先看到這裡,我們來思考一個問題,我們已經看了FileAppender的實現方式,那麼,我們自己來猜想一下RollingFileAppender的實現呢?那下面我們就來簡單猜想一下,請大家注意一下,可能我們下面的思路不一定就是RollingFileAppender的實現方式,但是我們先去猜,再看程式碼,自己的提升會很大。
    首先我們考慮,RollingFileAppender應該不需要去覆寫activateOption方法,因為RollingFileAppender的初始化動作和FileAppender是相同的,都是根據情況建立好日誌檔案而已。但是,在RollingFileAppender中,就必須要去修改subAppend方法了。因為在日誌記錄的過程中,涉及到檔案備份/新開操作和當檔案索引號達到規定最大號碼的時候,刪除備份檔案的動作。這些都只有在subAppend方法中實現。首先,當記錄完成一條日誌後,就需要去判斷當前日誌檔案的大小,如果檔案大小滿足了最大檔案上限,就需要執行檔案處理動作。首先把當前日誌檔案之前的所有日誌檔案備份名+1,即log.log.N變成log.log.N+1,然後再幫當前日誌檔案重新命名為log.log.1,如果當前的備份日誌檔名稱數量已經超過了maxBackupIndex大小,則直接刪除log.log.maxBackupIndex+1這個檔案即可,所以,在RollingFileAppender中,又應該保持一個屬性來記錄當前已經備份了多少檔案號,處理完備份檔案之後,再重新建立log.log,並把FileOutputStream重新定向到這個新的日誌檔案即可。
    那麼真正的程式碼是否如我們所設想呢?這個大家就下去自己看看RollingFileAppender的實現,保證能學到別人更好的設計思想和規範的編碼方式。