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中最高階的記錄器。