在 Web 應用程式中使用 Jakarta Commons Logging 與 Log4j
蔡煥麟 Mar-25-2005 http://140.138.148.144/~s882515/blog/log4j.htm
摘要
簡單介紹 Jakarta Commons Logging 與 log4j 的用法,以範例說明如何在 Web 應用程式中使用 logging。
1. 簡介
在開發 Java 或 JSP 應用程式時,最簡單的除錯技巧就是呼叫 System.out.println() 將訊息印出來,或者將訊息顯示在網頁上,這類方式很簡單,但卻有以下缺點:
- 若將除錯資訊顯示在網頁上,會破壞應用程式的輸出畫面。
- 除錯時埋了一堆 println() 呼叫,除錯完畢或者產品交付之前必須一一找出來刪除。
- 有些問題不易在開發時期發現,或者臭蟲出現的時機不是固定的,這類問題如果用單純的 println() 方法來找,由於輸出的訊息可能非常多,又沒有分類,要花比較多時間去過濾跟尋找。
比較好的 logging 技術便可以解決以上的問題,而且讓我們在程式的任何地方都能輸出除錯訊息,輸出的裝置可以是螢幕、檔案、甚至是電子郵件。如果把除錯訊息記錄到檔案中,經由檢視客戶執行時的記錄檔,就可以了解使用者在執行程式時所發生的內部細節,以便找出不容易在開發時期發現的臭蟲。
本文先簡單介紹 Jakarta Commons Logging 與 log4j 這兩個 logging 套件,並以 Web 應用程式的範例來說明如何利用這兩個套件輸出 log 訊息,包括安裝、設定、以及如何撰寫輸出 log 訊息的程式碼。
1.1 Jakarta Commons Logging 套件
Commons Logging 套件是一套通用的 logging API,可以讓開發人員隨意切換 logging 的實作產品,也就是說,如果你發現了一個更好用 logging 元件,你可以經由修改組態檔來切換欲使用的 logging 元件,而不用修改程式碼。
Commons Logging 套件支援的 logging 實作產品有:
- Log4j
- JDK 1.4 Logging
- Avalon LogKit
- SimpleLog(把日誌訊息寫到 stdout 和 stderr)
- NoOpLog(忽略日誌訊息)
其中 SimpleLog 和 NoOpLog 是 Commons Logging 內建的實作品。Log4j 這個實作品已經提供了很多功能,但如果你覺得現有的實作品仍無法滿足你的需求,你也可以自行實作,並為此實作品提供一個配接器(adapter),這樣你在程式中一樣還是使用 Commons Logging API 來撰寫 logging 程式碼。
1.2 Jakarta Log4j 套件
Log4j 是 Jakarta 開放原始碼計劃的其中一項專案,它是一個用來輸出日誌訊息的 Java 套件,它擁有豐富的功能,而且在設計上同時考慮到彈性與效率,使用也很容易。
如果你單獨使用 log4j,在程式裡面呼叫的日誌輸出函式就是 log4j 提供的專屬函式,但為了降低程式與特定 logging 元件的耦合度,比較好的方式還是使用 Commons Logging 搭配 log4j。
2. 安裝
以下是 Commons Logging 與 log4j 的安裝與設定步驟,主要是針對 Web 應用程式來說明:
- 安裝 Commons Logging 套件。到 http://jakarta.apache.org/site/downloads/downloads_commons.html 下載 Commons Logging 套件。我下載的檔案名稱是 commons-logging-1.0.4.zip。把壓縮檔裡面的所有 .jar 檔放到你的 Web 應用程式的 WEB-INF/lib 目錄下。
- 安裝 Log4j 套件。到 http://logging.apache.org/site/binindex.html 下載,我下載的檔案是 logging-log4j-1.2.9.zip。把 .jar 檔放到你的 Web 應用程式的 WEB-INF/lib 目錄下。
如果是一般的 Java 應用程式,可以把 .jar 檔案複製到 JDK 的 jrelibext 目錄下,或者將 .jar 檔的路徑加入 CLASSATH。 |
3. 使用
如前面提到的,Commons Logging 支援多種 logging 實作產品,這裡我們使用 Log4j 作為 logging 元件。
3.1 設定 Commons Logging 與 Log4j
在撰寫程式前,必須先建立一些設定檔,步驟如下:
- 在你的 Web 應用程式的 WEB-INFclasses 目錄下建立一個 commons-logging.properties 屬性檔。內容只有下面這行:
1org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
這行的作用是指定 Commons Logging 使用的 logging 元件,這裡使用的是 log4j 套件的 Log4JLogger。
註: 有些文件是使用 org.apache.commons.logging.impl.Log4JCategoryLog,但此類別已經過時,請改用 Log4JLogger。
其它支援的 log 實作元件:
- org.apache.commons.logging.impl.SimpleLog – 這是 Commons Logging 內建的簡單 logger。
- org.apache.commons.logging.impl.Jdk14Logger – 這是 JDK 1.4 的 logger。
- 在你的 Web 應用程式的 WEB-INFclasses 目錄下建立一個 log4j.properties,內容如下:
1log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
此設定檔指定使用 console appender 來記錄日誌,以便將日誌訊息輸出到 sdtout(System.out)。你可以在這個檔案裡面定義多個 appender,也就是說,你在程式中輸出的 log 訊息都會輸出到這些 appender(或者說,都會使用這些 appender 來輸出訊息)。第 3 行是指定 layout 樣式,你可以自己指定輸出的訊息樣式,至於其格式和其中各參數代表的意義,請參考 log4j 的說明文件。
註:Log4j 在輸出日誌訊息時,通常是以附加的方式(例如:附加到檔尾),因此用來輸出的類別,就叫做附加器(appender)。
注意在建置 Web 專案時,編譯 Java 原始檔所產生的 .classes 檔會輸出到 WEB-INFclass 目錄下,而有些開發工具,例如:Eclipse,在建置 Web 專案時會先清空 WEB-INFclasses 目錄,並且自動將 Java 原始碼目錄下的非 .java 檔案複製一份到 WEB-INFclasses 目錄下,因此前面提到的 commons-logging.properties 和 og4j.properties 屬性檔就要放在 Java 原始碼目錄下。 |
3.2 撰寫程式
若要在 JSP 程式中輸出日誌訊息,主要有三個步驟:
- 引入(import)兩個必要的類別:Log 和 LogFactory。
- 透過 LogFactory 取得 log 物件。
- 使用 log 物件輸出日誌訊息。
以下是一個簡單的 JSP 範例程式:
1 <HTML> <HEAD> <%@ page language="java" contentType="text/html; charset=BIG5" pageEncoding="BIG5" <span style="color: #ff0000;"> import="org.apache.commons.logging.Log" import="org.apache.commons.logging.LogFactory"</span> %> <% <span style="color: #ff0000;"> Log logger = LogFactory.getLog(this.getClass());</span> %> <META http-equiv="Content-Type" content="text/html; charset=BIG5"> <META name="GENERATOR" content="IBM WebSphere Studio"> <TITLE>TestLog4j.jsp</TITLE> </HEAD> <BODY> <% <span style="color: #ff0000;"> logger.info(logger.getClass().getName()); logger.debug("Debug message."); logger.info("Info message."); logger.warn("Warn message."); logger.error("Error message."); </span><span style="color: #0000ff;"> </span>%> </BODY> </HTML>
執行這個範例程式,你應該會在 console 視窗或者你的應用程式伺服器的 log 檔裡面看到像這樣的訊息:
1 INFO [Servlet.Engine.Transports : 0] (_TestLog4j.java:77) - org.apache.commons.logging.impl.Log4JLogger INFO [Servlet.Engine.Transports : 0] (_TestLog4j.java:81) - Info message. WARN [Servlet.Engine.Transports : 0] (_TestLog4j.java:82) - Warn message. ERROR [Servlet.Engine.Transports : 0] (_TestLog4j.java:83) - Error message.
其中第一個訊息是用來顯示目前使用的 logging 實作類別。
這個範例程式很簡單,但是有幾個地方值得注意一下:
- 在程式裡我們呼叫了輸出了五個日誌訊息,但是其中 logger.debug() 的訊息並沒有輸出。這是因為在前面的 log4j.properties 屬性檔中,我們指定了日誌輸出的層級是 INFO(log4j.rootLogger=INFO),而 DEBUG 訊息的層級比 INFO 更高,因此 log4j 不會輸出 DEBUG 訊息。Log4j 日誌訊息的層級有下列幾個(從高到低排列):
- TRACE(最高層級)
- DEBUG
- INFO
- WARN
- ERROR
- FATAL(最低層級,最嚴重的錯誤才會輸出到 log)
在程式中輸出 log 訊息時,應該根據該訊息的嚴重程度來呼叫對應的輸出函式,例如:非常嚴重的錯誤,可使用 fatal(),這樣的話,以後可以隨時視需要調整 log4j.properties 屬性檔的 log 層級,來過濾掉一些不重要的訊息 ,同時也可以提升程式執行的效能。
註:早期版本還有 ALL 跟 OFF,但已經廢棄不用,應避免使用它們。因為最高層級 TRACE 跟 ALL 的作用是一樣的;如果你想關閉日誌功能,只要在屬性檔中不指定 appender 就行了。
- 我們在 JSP 網頁中引入的類別是 Jakarta Commons Logging 的類別,可是實際上卻是透過 log4j 來執行輸出訊息的工作,這就是在第 1 節裡面提到的,使用 Jakarta Commons Logging 的好處:它讓你不用綁死在特定的 logging 實作產品上。當你想要更換成別的 logging 元件,原有的程式都不用修改,只要更改設定檔就行了。
如果要在一般 Java 應用程式輸出 log 訊息,基本的步驟也跟前面描述的差不多。
3.3. 一些實務建議
在程式中使用 logging 時,比較需要注意的地方主要是跟效能有關,以下是一些使用 logging 的建議。
3.3.1. 選擇正確的日誌層級
你應該根據輸出訊息的嚴重程度,呼叫適當的輸出方法,例如:Log.debug()、Log.info()、Log.error()….等,這樣的話,以後只要調整屬性檔就可以控制哪些訊息要輸出,有助於篩選及尋找特定的訊息。至於怎樣區分嚴重的程度,並沒有一個通用的規則,主要還是由程式設計師自行判斷。
3.3.2 使用 StringBuffer 取代 String
要輸出的 log 訊息有時候必須串接多個字串,此時最好採用 StringBuffer,以提升一點效能。例如:
1 String s := "登入失敗 :" + ex.getMessage() + " - 使用者 ID: " + userID + ", 密碼: " + password); log.error(s);
可以改成:
1 StringBuffer sb = new StringBuffer(); sb.append("登入失敗:"); sb.append(ex.getMessage()); sb.append(" - 使用者 ID: "); sb.append(userID); sb.append(", 密碼: "); sb.append(password); log.error(sb.toString());
3.3.3. 輸出 log 之前先檢查日誌層級是否有效
在某些場合,輸出 log 訊息之前,可以先檢查某種 logging 層級是否有效,以改善應用程式的效能。例如,你有一段要輸出的 log 訊息,前面的程式碼可以改成:
12 if (<span style="color: #ff0000;">log.isDebugEnabled()</span>) {StringBuffer sb = new StringBuffer(); sb.append("登入失敗:"); sb.append(ex.getMessage()); sb.append(" - 使用者 ID: "); sb.append(userID); sb.append(", 密碼: "); sb.append(password); log.error(sb.toString()); }
這樣的話,當 log4j 的屬性檔設定的層級低於 DEBUG 時,整段組成 StringBuffer 的程式碼就不必執行,可以節省一些時間。如果你需要在迴圈裡面輸出 log 訊息,此技巧能節省的時間就更可觀了。
3.3.4. 注意同步的問題
如果有多個執行緒使用相同的 appender 來輸出訊息,Log4j 不會有問題,因為所有執行緒都是同步化的(synchronized)。但是如果你有好幾個不同的 appender 要同時寫入訊息到同一個檔案或輸出裝置,就可能會發生無法預期的結果。
3.3.5. 選擇適當的 appender
Log4j 提供了多種 appender,除了 Console Appender、File Appender 之外,還有 Socket Appender、SMTP appender、NT Event Logger Appender….等等,你可以參考 log4j 的說明文件以了解這些 appender 的使用方式。以下提供一個 log4j.properties 屬性檔範例,這個屬性檔使用了兩個 log4j 的附加器:ConsoleAppender 以及 RollingFileAppender。
1 <span style="font-family: Verdana;">log4j.rootLogger=INFO, stdout, logfile log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n log4j.appender.logfile=org.apache.log4j.RollingFileAppender log4j.appender.logfile.File=c:/myweb.log <span style="color: #800000;"># 每個 log 檔最大 size 限制</span> log4j.appender.logfile.MaxFileSize=512KB <span style="color: #800000;"># 保留幾個備份檔</span> log4j.appender.logfile.MaxBackupIndex=3 log4j.appender.logfile.layout=org.apache.log4j.PatternLayout <span style="color: #800000;"># 輸出型樣: date priority [category] - <message>line_separator</span> log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - <%m>%n </span>
其中 RollingFileAppender 用來將日誌訊息輸出到指定的檔案,並且可限定檔案大小的限制,當日誌檔大小出過這個限制,就將檔案備份出去,你還可以指定備份檔最多要有幾個,超過時會循環使用。此範例指定日誌檔案為 c:/myweb.log,如果 myweb.log 超過 512KB,就備份出去,且備份檔最多為 3 個,備份檔會以 myweb.long.# 的方式命名,其中 # 是備份檔的編號,例如:myweb.log.1。
此外,log4j 還有另外一個能夠自動備份與翻新(rollover)日誌檔的 appender:org.apache.log4j.DailyRollingFileAppender,它除了能夠被備份日誌檔,也可以根據使用者設定的期間翻新日誌檔,避免日誌檔毫無節制的成長。
4. JDK 的 Logger 類別
JDK 從 1.4 版開始也提供了 logging 元件,提供這項功能的類別是 java.util.logging.Logger。這裡也附上一個簡單的使用範例:
1 |
<span style="color: #008000;">/* * TestLogging.java * * Requires JDK 1.4 */</span> import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.FileHandler; public class TestLogging { private static Logger logger = Logger.getLogger(TestLogging.class.getName()); public TestLogging() { try { FileHandler fileHandler1 = new FileHandler("log.txt"); logger.addHandler(fileHandler1); } catch(IOException ioe) { logger.log(Level.SEVERE, "Unable to create file handler.", ioe); } logger.log(Level.SEVERE, "This is a severe level message!"); logger.log(Level.SEVERE, "This is a second severe level message!"); } public void testLog() { logger.info("Message sent from method call."); } public static void main(String[] args) throws java.io.IOException { TestLogging example = new TestLogging(); example.testLog(); } } |
執行此範例程式時,可以在畫面看到 log 訊息,而且訊息也會以 XML 的格式寫入 log.txt 檔案裡面。
你可以跟 log4j 的使用方式比較一下,兩者在使用上的概念其實是差不多的,只是 log4j 功能更豐富,而且 JDK logging 只有 1.4 版以後才有,而 log4j 可支援比較早期的 JDK 1.3,對某些人來說,這可能是蠻重要的考量。
5. 總結
在開發應用程式時,logging 機制除了讓你將事件記錄下來供日後核對,對應用程式的除錯也很有幫助。本文簡短的說明了如何將 logging 機制 加入 Web 應用程式,一些 log4j 的進階功能並沒有提到,主要是希望從來沒接觸過 log4j 的人,能夠透過本文盡快上手,將 logging 機制加入自己的程式裡。如您所見,Jakarta Commons Logging 搭配 log4j 的方式是非常簡單易用的,除了功能強大之外,它還有一個最大的優點,就是只要調整屬性檔的設定就能控制日誌檔的輸出方式,不需要老是去修改程式。當基本的功能大致了解後,可參考相關的書籍或 網站上的說明文件,以了解其他進階的應用,http://logging.apache.org/log4j/docs/download.html 裡面也有許多輔助的套件或工具,也建議您花點時間看看。
參考資料
1 | http://jakarta.apache.org/log4j/docs/ |
2 | Commons Logging API http://jakarta.apache.org/commons/logging/api/index.html |