Java(TM) Logging API 簡介


Cheng-Yu Chang

Medical Image Processing and Neural Network Lab

EE, NCKU, Taiwan 70101, ROC.

2002/09/17


寫在前面

一般我們在撰寫程式或開發系統時,debug 往往是免不了需要的。網路上也常常有人詢問到,哪種 debug 工具會比較好。通常,在傳統上我們都會選擇使用 System.out.println() 來幫助我們顯示一些 debug 用的資訊。不過在 JavaTM 2, Standard Edition (J2SETM) 1.4 版本中,java.util.logging 這個新加入的 package 為我們提供了一些 class interface 來協助我們產生 debug 用的資訊,方便我們診斷出不管是在系統開發或是系統管理上所發生的問題。


Logging API 的基本架構

那麼到底 Java Logging API 有何神奇的功能可以協助我們 debug 呢?讓我們先來看看傳統 debug 用的一個範例。

/**
 * @(#)TraditionalDebugDemo.java 2002/09/17
 *
 * 傳統 debug 方式的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */  

import java.io.*;

public class TraditionalDebugDemo {
       public static void main (String[] args) {
              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            System.out.println(args[i]);
                    }
              }
              else {
                   System.err.println("沒有輸入參數!");
              }
       }
}

從上面的範例可以看到,我們執行程式若是沒有輸入參數的話就透過 System.err.println("沒有輸入參數!");把錯誤的訊息產生,而若是有輸入參數的話就透過 System.out.println(args[i]);依序把內容印出來。其結果如下所示:

C:\>java TraditionalDebugDemo
沒有輸入參數!

C:\>java TraditionalDebugDemo 1 2 3 4
1
2
3
4

 

接下來,讓我們使用 Logging API 將這個範例重新改寫一下。


Logging API 中的 Logger

/**
 * @(#)LoggingDebugDemo.java 2002/09/17
 *
 * 使用 Logging API debug 的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */

import java.io.*;
import java.util.logging.*;

public class LoggingDebugDemo {
       private static Logger myLogger = Logger.getLogger("LoggingDebugDemo");

       public static void main (String[] args) {
              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            myLogger.info(args[i]);
                    }
              }
              else {
                   myLogger.warning("沒有輸入參數!");
              }
       }
}

當我們要使用 Logging API 的時候,我們必須要 import java.util.logging.*;這個套件。接著可以看到我們建立了一個 Logger 物件,這個物件主要是用來紀錄我們應用程式或是系統所發出來的訊息用的。接著可以看到我們使用 myLogger.info(args[i]);來顯示我們輸入的參數,使用 myLogger.warning("沒有輸入參數!");來顯示沒有輸入參數時候的錯誤訊息。倘若我們執行這個程式的話,我們可以看到以下的輸出畫面:

C:\>java LoggingDebugDemo
2002/9/17
上午 11:54:17 LoggingDebugDemo main
警告: 沒有輸入參數!

C:\>java LoggingDebugDemo 1 2 3 4
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 1
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 2
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 3
2002/9/17
上午 11:56:10 LoggingDebugDemo main
資訊: 4

 

從上面的結果我們可以發現,當我們使用傳統的 System.out.println() 方式的話,除了程式開法者本身之外,並沒有辦法分辨目前訊息真正的涵義,也就是到底只是一般的訊息顯示,亦或是某個部分發生了錯誤。但是當我們使用 Logging API 來幫我們產生紀錄訊息的話,我們可以明顯的看到發生的時間、在哪個類別發生、在類別中的哪個 method 當中產生、以及產生什麼樣子的訊息類型都可以一目了然。

那麼,大家也許會問,為什麼我們想要顯示的訊息,前面會有「警告」或是「資訊」的字眼出現呢?其關鍵在於我們使用的 myLogger.info(args[i]);還有 myLogger.warning("沒有輸入參數!");這兩部分。在 Logging APILevel 類別當中定義了許許多多的不同程度的紀錄方式,分別如下列所示:


SEVERE 這是最高程度的紀錄方式,紀錄著非常重要的訊息(例如程式的錯誤)。
WARNING 表示一些警告訊息。
INFO 表示執行期間的一些訊息。
CONFIG 關於設定檔的一些訊息。
FINE 紀錄一些 debug 專用的詳細訊息。
FINER 再更進一步的詳細訊息。
FINEST 這是最低程度的紀錄方式,表示紀錄最詳細的訊息。
ALL 特殊情況,表示所有程度的訊息都應該被紀錄下來。
OFF 特殊情況,表示取消訊息的紀錄。

為什麼要定義這麼多種程度的訊息紀錄方式呢?其實這表示這些訊息的重要性,通常我們在開發系統,撰寫程式上,無庸置疑最重要的訊息一定就是有危險的錯誤訊息了,一旦發生錯誤訊息,可能程式執行到這個地方就因為某些原因就停止了,那麼這種程度的訊息不管如何就一定要顯示出來告訴我們。而其他例如 Level.FINE 這種程度的訊息,就表示它可有可無,並沒有代表很深刻的涵義,在這部分我們可以用下面的例子來說明:


Logging API 中的 Level

/**
 * @(#)LevelDebugDemo.java 2002/09/17
 *
 * 使用 Logging API LEVEL debug 的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */

import java.io.*;
import java.util.logging.*;

public class LevelDebugDemo {
       private static Logger myLogger = Logger.getLogger("LevelDebugDemo");

       public static void main (String[] args) {
              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            int j = i % 6;
                            switch(j) {
                                      case 0:
                                           myLogger.warning("WARNING Log!");
                                           break;
                                      case 1:
                                           myLogger.info("INFO Log!");
                                           break;
                                      case 2:
                                           myLogger.config("CONFIG Log!");
                                           break;
                                      case 3:
                                           myLogger.fine("FINE Log!");
                                           break;
                                      case 4:
                                           myLogger.finer("FINER Log!");
                                           break;
                                      case 5:
                                           myLogger.finest("FINEST Log!");
                                           break;
                                      default:
                                              break;
                            }
                    }
              }
              else {
                   myLogger.severe("沒有輸入參數!");
              }
       }
}

其執行結果如下所示:

C:\>java LevelDebugDemo
2002/9/17
下午 03:34:47 LevelDebugDemo main
嚴重的: 沒有輸入參數!

C:\>java LevelDebugDemo 1 2 3 4 5 6
2002/9/17
下午 03:35:02 LevelDebugDemo main
警告: WARNING Log!
2002/9/17
下午 03:35:03 LevelDebugDemo main
資訊: INFO Log!

 

相信大家看到上面的結果一定會覺得很奇怪,明明輸入了 6 個參數,照道理來說應該會有 6 項紀錄顯示出來才對,為什麼只有顯示兩種呢?其原因在於我們之前所提到的-「程度的不同」。在預設值當中,低於 Level.INFO 的值是不會被顯示出來的。所以我們只能看到 Level.INFO(資訊)程度的訊息。那麼,我們要怎麼改變程度呢?這個時候我們就要來看看 Logger API 實際上運作的情形了。當我們建立一個 Logger 物件之後,其最後的輸出是透過 Handler 類別來輸出的。

J2SE 提供了下列五種 Handler 類別讓我們來使用:


StreamHandler 將紀錄以 OutputStream 方式輸出。
ConsoleHandler 將紀錄以 System.err 方式輸出。
FileHandler 將紀錄導向一個檔案輸出。
SocketHandler 將紀錄傳送到遠端 TCP 連線輸出。
MemoryHandler 將紀錄暫存在記憶體當中。

現在我們將 LevelDebugDemo.java 改寫一下,加入 FileHandler 變成下面這樣:


Logging API 中的  Handler

 

/**
 * @(#)LevelDebugDemo2.java 2002/09/17
 *
 * 使用 Logging API LEVEL debug 的簡單範例
 *
 * @authot
 *         Cheng-Yu Chang
 * @license
 *         GPL(GNU GENERAL PUBLIC LICENSE)
 *         http://www.gnu.org/copyleft/gpl.html
 *
 * */

import java.io.*;
import java.util.logging.*;

public class LevelDebugDemo2 {
       private static Logger myLogger = Logger.getLogger("LevelDebugDemo2");       

       public static void main (String[] args) {
              try {
                  FileHandler h = new FileHandler("%h/myLogger.txt");
                  myLogger.addHandler(h);
                  h.setFormatter(new SimpleFormatter());
              }
              catch(IOException e) {myLogger.severe("FileHandler Error!");}

              myLogger.setLevel(Level.ALL);

              if(args.length != 0) {
                    for(int i = 0;i < args.length;i++) {
                            int j = i % 6;
                            switch(j) {
                                      case 0:
                                           myLogger.warning("WARNING Log!");
                                           break;
                                      case 1:
                                           myLogger.info("INFO Log!");
                                           break;
                                      case 2:
                                           myLogger.config("CONFIG Log!");
                                           break;
                                      case 3:
                                           myLogger.fine("FINE Log!");
                                           break;
                                      case 4:
                                           myLogger.finer("FINER Log!");
                                           break;
                                      case 5:
                                           myLogger.finest("FINEST Log!");
                                           break;
                                      default:
                                              break;
                            }
                    }
              }
              else {
                   myLogger.severe("沒有輸入參數!");
              }
       }
}

其中 FileHandler %h 參數是表示 user.home(使用者根目錄)這項系統項目,其他常用的參數還有 %t%g 等等,分別表示「系統暫存目錄」與「檔案依序編號」的意思。舉例來說 "%t/myLogger%g.txt" 的話就表示會依序產生 myLogger0.txt, myLogger1.txt等檔案,其位置位於系統暫存目錄當中。當執行完這個程式之後,會發現在我們使用者的根目錄之下會多出一個 myLogger.txt的檔案,其內容如下所示:

2002/9/17 下午 04:38:31 LevelDebugDemo2 main
警告: WARNING Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
資訊: INFO Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
配置: CONFIG Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
細緻: FINE Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
更細緻: FINER Log!
2002/9/17
下午 04:38:31 LevelDebugDemo2 main
最細緻: FINEST Log!

這時候我們可以發現所有程度的訊息都完整的顯示出來了,這是因為我們把訊息顯示程度設定成為全部顯示myLogger.setLevel(Level.ALL);的緣故。在這邊我們要另外注意到的一件事情是,若是我們把 h.setFormatter(new SimpleFormatter()); 這行註解掉的話,我們可以發現檔案輸出變成下面這個樣子:

<?xml version="1.0" encoding="MS950" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2002-09-17T16:51:19</date>
<millis>1032252679457</millis>
<sequence>0</sequence>
<logger>LoggingDebugDemo</logger>
<level>WARNING</level>
<class>LevelDebugDemo2</class>
<method>main</method>
<thread>10</thread>
<message>WARNING Log!</message>
</record>
˙

˙[中間省略]

˙

<date>2002-09-17T16:51:19</date>
<millis>1032252679697</millis>
<sequence>5</sequence>
<logger>LoggingDebugDemo</logger>
<level>FINEST</level>
<class>LevelDebugDemo2</class>
<method>main</method>
<thread>10</thread>
<message>FINEST Log!</message>
</record>
</log>

這是因為 FileHandler 預設的輸出模式是 XML 的格式,J2SE Logging API 提供了兩種輸出模式,一種是 SimpleFormatter,也就是之前我們看到的那種模式。另外一種 XMLFotmatter的話就是像我們後來看到的這種 XML 架構樣式了。


總結

到目前為止我們簡單介紹了如何使用 JavaTM 2, Standard Edition (J2SETM) 1.4 版本中新增加的 Logging API,明白 Logging API 當中 Logger, Handler, Formatter所代表的意義(如下圖所示)。往後我們要紀錄系統開發資訊的話,可以紀錄的更加明確、有意義。


评论

该日志第一篇评论

发表评论

评论也有版权!