日志記錄器挺簡單的,沒有很多東西,最主要的就是一個Logger接口:
[java]
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);
}
隻要實現Logger就能有一個自己的日志記錄器,其中setContainer是把日志記錄器跟具體的容器關聯,setVerbosity是設置日志的級別,log是具體的日志記錄函數。FATAL,ERROR,WARNING,INFORMATION,DEBUG代表日志記錄的五個級別,看單詞就能明白意思。這裡主要講解一下FileLogger類,這是Tomcat的其中一個日志記錄器,它把日志記錄在一個文件中,FileLogger的啟動方法和關閉僅僅是出發一個生命周期事件,並不做其他的事情:
[java]
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;
}
這裡有一行代碼sm.getString("fileLogger.alreadyStarted"),牽涉到國際化的問題,等下再說這個問題。現在先看日志記錄器,FileLogger的open方法打開一個文件用來記錄日志:
[java]
private void open() {
// Create the directory if necessary
File dir = new File(directory);//directory等於logs,即在文件夾logs下新建日志文件
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;
}
}
這裡得到一個PrintWriter的輸出流writer,在log方法記錄日志的時候會用到,下面看log方法:
[java]
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);
}
}
}
writer.println把時間和日志信息寫進日志文件中。當然,這個日志記錄器一般是給Tomcat自己用的,我們也可以實現Logger接口,然後重寫它的open方法(打開我們自己的日志文件)和log方法(用來在我們自己的日志文件中記錄日志信息)。
現在再來看剛才提到的代碼sm.getString("fileLogger.alreadyStarted")。在理解這句代碼前先看一個jdk的類ResourceBundle,這個類提供瞭國際化的方便。這個類的作用就是讀取.properties文件,但是會根據文件名來獲取當前系統的語言信息,然後讀取對應文件的屬性值。當然首先要有各國不同的屬性文件,才能國際化,Tomcat的每個包下都有幾個不同的屬性文件,org.apache.catalina.logger包下有如下三個屬性文件:
[java] www.aiwalls.com
LocalStrings_es.properties
LocalStrings_ja.properties
LocalStrings.properties
分別表示三種語言的屬性文件。LocalStrings.properties屬性文件是默認的屬性文件。看LocalStrings.properties屬性中的內容:
[java]
fileLogger.alreadyStarted=File Logger has already been started
fileLogger.notStarted=File Logger has not yet been started
tomcatLogger.alreadyStarted=Tomcat Logger has already been started
tomcatLogger.notStarted=Tomcat Logger has not yet been started
ResourceBundle類通過getBundle方法獲取,參數是屬性文件的包名傢名字前綴,上面就是包名加LocalStrings。通過getString(String key)方法獲取屬性文件中的參數:
現在來看代碼sm.getString("fileLogger.alreadyStarted"),sm是StringManager的實例,在FileLogger中已經初始化:
[java]
private StringManager sm =StringManager.getManager(Constants.Package);
Constants.Package得到包的名字,getManager方法代碼如下:
[java]
public synchronized static StringManager getManager(String packageName) {
StringManager mgr = (StringManager)managers.get(packageName);
if (mgr == null) {
mgr = new StringManager(packageName);//新建一個StringManager
managers.put(packageName, mgr);
}
return mgr;
}
代碼很好理解,如果已經有StringManager實例瞭就直接從managers(這是一個Hashtable)中拿,沒有就新建一個。看StringManager的構造方法:
[java]
private StringManager(String packageName) {
String bundleName = packageName + ".LocalStrings";
bundle = ResourceBundle.getBundle(bundleName);
}
看到我們熟悉的ResourceBundle類瞭,根據上面的講解,ResourceBundle.getBundle(bundleName)能拿到默認的屬性文件,也就是上面的LocalStrings.properties文件。再回到sm.getString("fileLogger.alreadyStarted"),看sm的getString方法:
[java]
public String getString(String key) {
if (key == null) {
String msg = "key is null";
throw new NullPointerException(msg);
}
String str = null;
try {
str = bundle.getString(key);
} catch (MissingResourceException mre) {
str = "Cannot find message associated with key '" + key + "'";
}
return str;
}
重點是budle.getString(key),這句代碼能拿到LocalStrings.properties文件的key屬性(這裡是fileLogger.alreadyStarted)的值,即File Logger has already been started。這樣我們便能定義多個屬性文件,一個表示英文,一個表示漢語,一個屬性文件表示一個語言,就能實現應用的國際化瞭。
作者:haitao111313