Tomcat源碼分析(六)–日志記錄器和國際化 – JAVA編程語言程序開發技術文章

      日志記錄器挺簡單的,沒有很多東西,最主要的就是一個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

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。