1. 程式人生 > >How tomcat works——7 日誌記錄器

How tomcat works——7 日誌記錄器

概述

日誌系統是一個記錄資訊的元件。在 Catalina 中,日誌系統是一個跟容器相關聯且相對簡單的元件。Tomcat 在 org.apache.catalina.logger 包中提供了多個不同的日誌系統。本章的應用程式在 ex07.pyrmont 包中。SimpleContext 和Bootstrap 是從第六章中修改得到。

本章有三節組成,第一節介紹了所有日誌系統都要實現的 org.apache.catalina.Logger 介面。第二節介紹了 Tomcat 中的日誌系統,第三節詳細講解了本章的例子,該例子基於 Tomcat 的日誌系統。

7.1 Logger介面

一個日誌系統必須實現 org.apache.catalina.Logger 介面,該介面如Listing7.1 所示

Listing 7.1: Logger 介面

package org.apache.catalina;
import java.beans.PropertyChangeListener;
public interface Logger {
    public static final int FATAL = Integer.MIN_VALUE;
    public static final int ERROR = 1;
    public static final int WARNING = 2;
    public static final int INFORMATION = 3
; public static final int DEBUG = 4; public Container getContainer(); public void setContainer(Container container); public String getInfo(); public int getVerbosity(); public void setVerbosity(int verbosity); public void addPropertyChangeListener(PropertyChangeListener listener); public
void log(String message); public void log(Exception exception, String msg); public void log(String message, Throwable throwable); public void log(String message, int verbosity); public void log(String message, Throwable throwable, int verbosity); public void removePropertyChangeListener(PropertyChangeListener listener); }

日誌介面提供了日誌系統要實現的方法,最簡單的方法是接受一個字串並將其記錄,最後兩個方法會接受一個冗餘級別(verbosity level),如果傳遞的數字低於該類的例項設定的冗餘級別,就將資訊記錄下來,否則就忽略資訊。使用靜態變數定義了五個冗餘級別:FATAL, ERROR, WARNING, INFORMATION,和 DEBUG。getVerbosity() 和 setVerbosity() 分別用來獲得和設定冗餘級別。

另外,日誌介面還有 getContainer() 和 setContainer() 方法用來將日誌系統跟容器關聯起來。還有 addPropertyChangeListener() 和removePropertyChangeListener() 方法新增和刪除PropertyChangeListener。

我們看了 Tomcat 中日誌系統實現之後就會清楚這些方法了。

7.2 Tomcat的日誌記錄器

Tomcat 提供了3種日誌系統,它們分別是 FileLogger, SystemErrLogger和SystemOutLogger。這些類可以在 org.apache.catalina.logger 包中找到,它們都繼承了 org.apache.catalina.logger.LoggerBase 類。在 Tomcat 4 中LoggerBase 實現了 org.apache.catalina.Logger 介面,在 Tomcat 5 中,它還實現了 Lifecycle 介面和 MBeanRegistration 介面(20 章介紹)。

它們的 UML 結構圖如圖 7.1 所示:
這裡寫圖片描述
圖7.1: Tomcat’s Loggers

7.2.1 LoggerBase類

在 Tomcat5 中,LoggerBase 類由於集成了 MBeans 而比較複雜,所以本章看的是Tomcat4 中的 LoggerBase 類。等討論完第 20 章後,就好理解 Tomcat5 中的LoggerBase 類了。在 Tomcat 4 中,LoggerBase 類是一個抽象類,它實現了 Logger 介面中除log(String msg)之外的所有方法:

public abstract void log(String msg);

該方法需要在子類進行覆蓋(overload),所有的其它的 log() 方法都呼叫了該方法。因為每一個子類都將資訊記錄到不同的地方,所以該方法在LoggerBase 中北留空。

現在來看該類的冗餘級別。它被定義為一個protected修飾的verbosity的變數,預設值為 ERROR。

protected int verbosity = ERROR;

冗餘級別可以使用 setVerbosity() 方法改變,傳遞這些字串給方法即可 FATAL,ERROR, WARNING, INFORMATION或 DEBUG。Listing7.2 展示了 LoggerBase 類setVerbosity()方法:

Listing 7.2: The setVerbosity method

public void setVerbosityLevel(String verbosity) {
    if ("FATAL".equalsIgnoreCase(verbosity))
        this.verbosity = FATAL;
    else if ("ERROR".egualsIgnoreCase(verbosity))
        this.verbosity = ERROR;
    else if ("WARNING".equalsIgnoreCase(verbosity))
        this.verbosity = WARNING;
    else if ("INFORMATION".equalsIgnoreCase(verbosity))
        this.verbosity = INFORMATION;
    else if ("DEBUG".equalsIgnoreCase(verbosity))
        this.verbosity = DEBUG;
}

兩個日誌方法接受整數作為詳細程度級別。 在這些過載方法,只有在傳遞的詳細級別較低於例項的詳細級別時才呼叫log(String message)過載方法。 Listing 7.3提供了這些方法過載:

Listing 7.3: The log method overloads that accept verbosity

public void log(String message, int verbosity) {
    if (this.verbosity >= verbosity)
        log(message);
}

public void log(String message, Throwable throwable, int verbosity) {
    if (this.verbosity >= verbosity)
        log(message, throwable);
}

在後邊小節介紹的 LoggerBase 的3個子類中,可以看到 log(String message)方法過載實現。

7.2.2 SystemOutLogger類

SystemOutLogger 作為 LoggerBase 的子類提供了 log(String message)方法的實現。每一次收到的資訊都被傳遞給 System.out.println()方法,SystemOutLogger類如 Listing7.4 所示。

Listing 7.4: The SystemOutLogger Class

package org.apache.catalina.logger;

public class SystemOutLogger extends LoggerBase {
    protected static final String info ="org.apache.catalina.logger.SystemOutLogger/1.0";

    public void log(String msg) {
        System.out.println(msg);
    }
}

7.2.3 SystemErrLogger類

SystemErrLogger 類跟 SystemOutLogger 類十分相似,只是它覆蓋 log(String message)方法時使用是 System.erro.println()方法。SystemErrLogger類如 Listing7.5。

Listing 7.5: The SystemErrLogger class

package org.apache.catalina.logger;

public class SystemErrLogger extends LoggerBase {
    protected static final String info = "org.apache.catalina.logger.SystemErrLogger/1.0";

    public void log(String msg) {
        System.err.println(msg);
    }
}

7.2.4 FileLogger類

FileLogger 是 LoggerBase 類的子類中最複雜而精緻的。它將從關聯容器收到的資訊寫到檔案中,每個資訊可以選擇性的加上時間戳。在第一次例項化時,該類的例項會建立一個檔案,該檔案的名字帶有日期資訊。如果日期改變了,它會建立一個新的檔案並把資訊寫在裡面。類的例項允許在日誌檔案的名字上新增字首和字尾。

在 Tomcat4 中,FileLogger 類實現了 Lifecycle 介面,所以它可以跟其它實現org.apache.catalina.Lifecycle 介面的元件一樣啟動和停止。在 Tomcat5 中,它是實現了 Lifecycle 介面的 LoggerBase 類的子類。

Tomcat 4 中 LoggerBase 類的 start() 和stop() 方法僅僅實現觸發了監聽器“感興趣的”檔案日誌的開始和停止事件。這兩個方法如 Listing7.6 所示,注意 stop()方法呼叫了該類的關閉日誌檔案的私有方法close()方法。關閉方法會在本節後邊的內容中介紹。

Listing 7.6: The start and stop methods

public void start() throws LifecycleException {
    // Validate and update our current component state
    if (started)
        throw new LifecycleException (sm.getString("fileLogger.alreadyStarted"));
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;
}

public void stop() throws LifecycleException {
    // Validate and update our current component state
    if (!started) 
        throw new LifecycleException(sm.getString("fileLogger.notStarted"));
    lifecycle.fireLifecycleEvent(STOP__EVENT, null);
    started = false;
    close ();
}

FileLogger 類中最重要的方法是 log()方法,如 Listing7.7 所示。

Listing 7.7: The log method

public void log(String msg) {

        // Construct the timestamp we will use, if requested
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        String tsString = ts.toString().substring(0, 19);
        String tsDate = tsString.substring(0, 10);

        // If the date has changed, switch log files
        if (!date.equals(tsDate)) {
            synchronized (this) {
                if (!date.equals(tsDate)) {
                    close();
                    date = tsDate;
                    open();
                }
            }
        }

        // Log this message, timestamped if necessary
        if (writer != null) {
            if (timestamp) {
                writer.println(tsString + " " + msg);
            } else {
                writer.println(msg);
            }
        }

    }

log()方法接受一個訊息並把訊息寫到日誌檔案中。在 FileLogger 例項的生命週期中,log() 方法可以開啟或關閉多個日誌檔案。典型的如,如果日期改變了的話,log()方法則關閉當前檔案並開啟一個新檔案。接下來,讓我們看看 open()、close() 和 log() 這些方法是如何工作的。

7.2.4.1 open()方法

如 Listing7.8 所示,open()方法在指定目錄中建立一個新日誌檔案。

Listing 7.8: The open method

private void open() {

        // Create the directory if necessary
        File dir = new File(directory);
        if (!dir.isAbsolute())
            dir = new File(System.getProperty("catalina.base"), directory);
        dir.mkdirs();

        // Open the current log file
        try {
            String pathname = dir.getAbsolutePath() + File.separator +
                prefix + date + suffix;
            writer = new PrintWriter(new FileWriter(pathname, true), true);
        } catch (IOException e) {
            writer = null;
        }

    }

open()方法首先檢查假設它應該在其中建立日誌的目錄檔案已存在。 如果目錄不存在,該方法也會建立目錄。 目錄地址儲存在類變數directory中。

File dir = new File(directory);
if (!dir.isAbsolute())
    dir = new File(System.getProperty("catalina.base"), directory);
dir.mkdirs();

然後組成該檔案的路徑名,由目錄路徑、字首、日期和字尾組成:

try (
    String pathname = dir.getAbsolutePath() + File.separator +prefix + date + suffix;

接下來,它構造一個java.io.PrintWriter的例項,該例項引數writer是一個含寫入路徑名的java.io.FileWriter物件。 然後PrintWriter例項分配給類變數writer。 log()方法使用writer來記錄訊息。

writer = new PrintWriter(new FileWriter(pathname, true), true);

7.2.4.2 close()方法

close()方法清空 PrintWriter 變數 writer,然後關閉 PrintWriter 並將 writer設定為 null,並將 date 設定為空字串。該方法如 Listing7.9。

Listing 7.9: The close method

private void close() {
    if (writer == null)
        return;
    writer.flush();
    writer.close();
    writer = null;
    date = "";
}

7.2.4.3 log()方法

log()方法首先建立一java.sql.Timestamp 類例項,該類是 java.util.Date的瘦包裝器 (Date物件比較重)。初始化Timestamp的目的是為了更容易得到當前時間。在該方法中,將當前時間的 long 格式傳遞給 Timestamp 類並構建 Timestamp 類例項:

Timestamp ts = new Timestamp(System.currentTimeMillis());

使用 Timestamp 類的 toString() 方法,可以得到當前時間的字串表示形式,字串輸出形式如下格式:

yyyy-mm-dd hh:mm: SS.fffffffff

其中 fffffffff 表示從 00:00:00 開始的毫微秒。在方法中使用 subString()方法得到日期和小時。

String tsString = ts.toString().substring(0, 19);

接下來使用如下語句得到日期:

String tsDate = tsString.substring(0, 10);

接下來比較 tsData 和 String 變數 date 的值,如果 tsDate 和 date 值不同,它關閉當前日誌檔案,將tsDate值賦給date並開啟一個新日誌檔案。

// If the date has changed, switch log files
if (!date.equals(tsDate)) {
    synchronized (this) {
        if (!date.equals(tsDate)) {
            close();
            date = tsDate;
            open();
        }
    }
}

最後,log()方法將 PrintWriter 例項的輸出流寫入到日誌檔案中。如果布林變數timestamp 的值為true,將 timestamp(tsString)的值作為字首,否則不使用字首:

// Log this message, timestamped if necessary
if (writer != null) {
    if (timestamp) {
        writer.println(tsString + " " + msg);
    }else {
        writer.println(msg);
    }
}

7.3 應用Demo

該章的應用程式跟第6章的程式很相似,只是多了個跟 SimpleContext 物件相關聯的 FileLogger。這個改變可以在 ex07.pyrmont.startup.Bootstrap 類的main()方法裡找到,如 Listing7.10 所示。注意要仔細看高亮的程式碼。

Listing 7.10: The Bootstrap class

package ex07.pyrmont.startup;

import ex07.pyrmont.core.SimpleContext;
import ex07.pyrmont.core.SimpleContextLifecycleListener;
import ex07.pyrmont.core.SimpleContextMapper;
import ex07.pyrmont.core.SimpleLoader;
import ex07.pyrmont.core.SimpleWrapper;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.logger.FileLogger;
import org.apache.catalina.Mapper;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;

public final class Bootstrap {
  public static void main(String[] args) {
    Connector connector = new HttpConnector();

    Wrapper wrapper1 = new SimpleWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");

    Wrapper wrapper2 = new SimpleWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");
    Loader loader = new SimpleLoader();

    Context context = new SimpleContext();
    context.addChild(wrapper1);
    context.addChild(wrapper2);

    Mapper mapper = new SimpleContextMapper();
    mapper.setProtocol("http");

    LifecycleListener listener = new SimpleContextLifecycleListener();
    ((Lifecycle) context).addLifecycleListener(listener);

    context.addMapper(mapper);
    context.setLoader(loader);

    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");

    // ------ add logger --------
    System.setProperty("catalina.base",   System.getProperty("user.dir"));
    FileLogger logger = new FileLogger();
    logger.setPrefix("FileLog_");
    logger.setSuffix(".txt");
    logger.setTimestamp(true);
    logger.setDirectory("webroot");
    context.setLogger(logger);

    //---------------------------

    connector.setContainer(context);
    try {
      connector.initialize();
      ((Lifecycle) connector).start();
      ((Lifecycle) context).start();

      // make the application wait until we press a key.
      System.in.read();
      ((Lifecycle) context).stop();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

7.4 小結

在本章中,我們瞭解學習了記錄器元件,審查介紹了org.apache.catalina.Logger介面,並仔細查看了Tomcat中3個對此介面的實現類。 另外,本章應用Demo演示使用了FileLogger類——在Tomcat中最高階的記錄器。