.Java中的異常、斷言、日誌【草稿下,Log4j專題】

y_keven發表於2013-10-28
(本章主要講解Java裡面比較核心的一塊內容——異常處理,Java異常處理機制,一致都是比較複雜的一塊,而很多時候如果寫程式的時候能夠適當地注意對應的一些異常處理情況,那麼就會在開發過程節省一大部分時間,最常見的情況就是輔助進行除錯以及維護工作以及提高系統的容錯性和穩定性。這一章和前邊類和物件章節不一樣,這一章可能涵蓋的內容沒有前邊那章多,但是我會盡量保證在整篇文章裡面把開發過程中需要注意到的與異常有關的細節問題以及對應的開發經驗寫入本文,而本章的出發點是異常處理,核心內容還涵蓋了測試、除錯、以及部署等相關內容以及。如果有筆誤的地方,請來Email指點:silentbalanceyh@126.com,謝謝)
本章目錄
1.Java異常處理
2.異常處理心得
3.斷言的使用
4.Java中的日誌(JDK1.4 Logging Framework)
5.第三方日誌庫(Log4j、Commons Logging Framework)
5.第三方日誌庫(Log4j,Commons Logging Framework)

  前邊介紹了官方的日誌框架JDK 1.4 Logging Framework,接下來介紹兩個第三方的日誌框架Log4j和Commons Logging Framework,這三個日誌框架也是整個Java界使用最多的日誌框架。
  下載地址:【直接複製就可下載】
  http://labs.xiaonei.com/apache-mirror/logging/log4j/1.2.15/apache-log4j-1.2.15.zip
  http://apache.etoak.com/logging/log4j/1.2.15/apache-log4j-1.2.15.zip
  i.Log4j:
  1)基本簡介:
  Log4j是Apache的一個開放原始碼專案,通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件、甚至是套介面伺服器、NT的事件記錄器、UNIX Syslog守護程式等;我們也可以控制每一條日誌的輸出格式;通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。 此外,通過Log4j其他語言介面,您可以在C、C++、.Net、PL/SQL程式中使用Log4j,其語法和用法與在Java程式中一樣,使得多語言分散式系統得到一個統一一致的日誌元件模組。而且,通過使用各種第三方擴充套件,您可以很方便地將Log4j整合到J2EE、JINI甚至是SNMP應用中。
  整體上講,Log4j中有三個最主要的核心元件:LoggerAppenderLayout,不僅僅如此,它同樣允許開發人員自定義多個Logger,每個Logger有自己的名字,Logger之間通過名字來表示隸屬關係。但是有一個Logger稱為Root,這個Logger不能通過名字來檢索,但是可以直接使用Logger.getRootLogger()獲取,其他的Logger都可以使用Logger.getLogger(String name)方法獲取
  Logger:
  在執行應用程式時,接收日誌語句生成的日誌請求。它是一種重要的日誌處理元件, 可以通過 log4j API 的 logger 類對其進行訪問。它的方法有:debug、info、warn、error、fatal 和 log。這些方法用於記錄訊息。
  Appender:
  管理日誌語句的輸出結果。執行日誌語句時,Logger 物件將接收來自日誌語句的記錄請求。此請求是通過 logger 傳送至 appender 的。然後,Appender 將輸出結果寫入到使用者選擇的目的地。對於不同的日誌目的地,提供不同的 appender 型別。這些 appender 包括:用於檔案的 file appender、用於資料庫的 JDBC appender 和用於 SMTP 伺服器的 SMTP appender。
  Layout:
  用於指定 appender 將日誌語句寫入日誌目的地所採用的格式。appender 可以用來格式化輸出結果的各種佈局包括:簡單佈局、模式佈局和 HTML 佈局。
  [1]先看看Log4j的包目錄結構:
org.apache.log4j:log4j主要的類、介面以及特殊的Appender類、Layout、Level和Logger
org.apache.log4j.spi:包含了針對SPI的一部分擴充套件【SPI——System Programming Interface】
org.apache.log4j.chainsaw:基於Java Swing的一個GUI日誌檢視器:

org.apache.log4j.config:用來設定或者獲取某些元件的相關屬性
org.apache.log4j.helpers:僅僅被log4j庫內聯使用的多個Class的集合,一般情況下不對外
org.apache.log4j.jdbc:一個Appender用來記錄JDBC連線的相關事件
org.apache.log4j.jmx:在JMX開發的時候可以配置的基於JMX的日誌記錄,但是目前該包裡面的類還不是特別穩定
org.apache.log4j.lf5【目前很少用,我也不知道這部分用途,沒用過】
org.apache.log4j.net:用來進行遠端日誌記錄的Appender,主要用於JMS、SMTP以及基於Socket的日誌記錄,用於向一個log4j伺服器傳送日誌進行遠端的日誌記錄
org.apache.log4j.nt:Java本地介面的應用【JNI】,介面用Java,實現用C程式碼,用來記錄Windows NT系統事件日誌的Appender元件
org.apache.log4j.or:根據物件型別來對物件進行Render操作的幫助類
org.apache.log4j.performance:效能測試程式碼
org.apache.log4j.xml:包含了多個XML元件,使用DOM Tree的結構在Log4j環境裡面用來記錄XML格式的日誌
org.apache.log4j.varia:包含了多個Appender以及Filters和其他相關元件
  [2]Log4j的安裝:
  Log4j下載下來過後需要進行簡單的安裝,先保證原來的系統環境和Java環境是正常的:
  • 下載log4j的jar包,上邊已經提供了下載地址
  • 將這些包解壓到一個目錄裡面,【*:一般對於開發人員最好將jar的包分類放在不同的目錄下邊方便使用。】
  • 將包下邊的log4j-1.2.15.jar放入開發的CLASSPATH裡面,關於Java裡面CLASSPATH的一些問題留到開發用Java裡面去講解
  • 還需要下載兩個輔助解析XML的jar,下載地址http://archive.apache.org/dist/xml/xerces-j/,這裡我下載的最新版本2.9.0,取出裡面的xercesImpl.jarxml-apis.jar兩個jar檔案放到CLASSPATH裡面。【*:在IDE環境下有專程針對CLASSPATH的設定,這種情況防止jar衝突最好的方式就是保持CLASSPATH裡面只有java標準的環境】
  2)Log4j的核心概念:
  [1]Log4j中的Logger:
  Logger在Log4j中是一個很核心的概念,它是日誌程式裡面的核心元件,在Log4j裡面,Logger總共分為六個日誌記錄級別,其定義位於org.apache.log4j.Level類裡:
  • TRACE:這種細粒度資訊時間不僅僅針對除錯,
  • DEBUG:指出細粒度資訊事件對除錯應用程式是非常有幫助的,該級別的日誌主要用於輔助開發人員除錯;
  • INFO:該級別表明訊息上細粒度上突出強調了應用程式的執行過程
  • WARN:表明有可能會出現潛在錯誤的情形
  • ERROR:表明雖然程式發生了錯誤事件,但是仍然不影響系統的繼續執行
  • FATAL:該級別表明了每個嚴重的錯誤時間,將會導致應用程式的退出。
  根據API裡面的描述還存在兩個很特殊的級別:
  • ALL:這是最低階別,用於開啟所有的日誌記錄
  • OFF:這是最高階別,用於關閉所有的日誌記錄
  一般情況下,推薦開發過程只使用ERROR、WARN、INFO、DEBUG四個級別,下圖說明了這幾個級別相互之間的關係:

  從上圖可以知道,只有當日志記錄器Logger的日誌級別高於或者等於輸出訊息的等級的時候,該日誌訊息才會被輸出。預設情況下,Logger的級別是Debug,如果我們需要建立一個Logger,有以下幾種方式:
  建立根日誌器:
Logger logger = Logger.getRootLogger();
  建立一個新的日誌器:
Logger logger = new Logger("MyLogger");
  建立一個基於類的日誌器:
Logger logger = new Logger(MyClass.class);
  在Log4j裡面設定一個日誌的級別使用語句:
logger.setLevel(Level.DEBUG);
  【*:在1.2版本之前,Log4j裡面沒有使用Logger類,主要是使用Category類,從版本1.2開始才使用Logger類,就上邊的方式建立Logger
  這裡先看一段程式碼,然後對Category進行一個詳細說明,實際上從版本1.2開始Category和Logger是父子關係,Logger從Category類繼承而來;在該版本之前我們一般使用Category來建立日誌記錄器,在前邊的版本里面Category就等同於現在我們所見到的Logger。在版本1.2中,Category被標記為deprecated,而直接使用Logger替代它,一般情況下Java裡面被標記為deprecated的方法和類都有替代方法以及類來進行對應功能的版本更新,我們一般在開發過程中儘量避免使用標記為deprecated的類或者方法。下邊的程式碼是升級的改動:
// 被拋棄的程式碼形式:
Category cat = Category.getInstance("foo.bar");
// 目前使用的建立Logger的形式:
Logger logger = Logger.getInstance("foo.bar");
  為了使得上邊的圖形裡面的內容更加容易理解,這裡提供另外一段程式碼:
package org.susan.java.logging;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

class Foo{}

public class Log4jLevelTester {
public static void main(String args[]){
Logger logger = Logger.getLogger(Foo.class);
logger.setLevel(Level.INFO);

// 該請求是被允許的,因為 WARN >= INFO
logger.warn("Low fuel level.");

// 該請求時背禁止的,因為 DEBUG < INFO
logger.debug("Starting search for nearest gas station.");
}
}
  上邊這段程式碼很好說明了logger在對待LEVEL的時候的用法,在setLevel的時候Logger設定了日誌器本身的Level,然後在使用裡面對應的warn、debug方法的時候會判斷使用的方法和日誌記錄器設定的Level進行詳細的比較,當滿足上邊圖示的判斷條件的時候才會輸出日誌資訊。在這裡還有一點需要注意的是方法warn和debug,這兩個方法都不是Logger類所具有的方法,而是從它的父類繼承過來的Category的方法,在使該方法的時候會針對我們最初設定的日誌的Level進行一個比較和判斷,最終決定是否要被記錄下來。那麼在環境配置好的情況下,我們執行該程式。
  【異常】這裡會遇到一個初學Log4j的常見問題,我們會發現控制檯只有這句話輸出:
log4j:WARN No appenders could be found for logger (org.susan.java.logging.Foo).
log4j:WARN Please initialize the log4j system properly.
  這句話的意思是在系統環境裡面沒有找到我們所需要的配置檔案,一般理解是缺乏log4j.properties屬性檔案,在此處,如果我們不使用配置檔案的方式就將下邊的程式碼修改成:
package org.susan.java.logging;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

class Foo{}

public class Log4jLevelTester {
public static void main(String args[]){
BasicConfigurator.configure();
Logger logger = Logger.getLogger(Foo.class);
logger.setLevel(Level.INFO);

// 該請求是被允許的,因為 WARN >= INFO
logger.warn("Low fuel level.");

// 該請求時背禁止的,因為 DEBUG < INFO
logger.debug("Starting search for nearest gas station.");
}
}
  修改成上邊這段程式碼過後,我們就可以直接在沒有log4j.properties配置檔案的情況下直接執行上邊的程式,該程式碼將會有以下輸出:
0 [main] WARN org.susan.java.logging.Foo - Low fuel level.
  通過這裡我們就可以知道Log4j裡面的Level的排序為:DEBUG < INFO < WARN < ERROR < FATAL,等到後邊再來分析上邊的程式碼如果不使用BasicConfigurator.configure();而是使用我們開發常用的屬性檔案log4j.properties進行日誌記錄的相關配置,關於配置檔案的詳細內容等介紹完Layout和Appender了過後再說明。
  [2]Log4j中的Appender:
  Log4j裡面的Appender類似於JDK 1.4 Logging Framework裡面的Handler元件,其主要目的是管理我們日誌記錄的結果,描述了這些日誌怎樣進行輸出,下邊列舉了比較常用的一些Appender的快照:
  • ConsoleAppender:這種Appender會管理我們的日誌訊息,將日誌事件記錄到控制檯以System.out或者System.err作為輸出機制,它預設的輸出為System.out
  • RollingFileAppender:這種Appender從FileAppender繼承過來,大部分屬性和FileAppender是類似的,當日志記錄的檔案到達檔案容量的最大值的時候,會自動建立一個新的日誌檔案
  • FileAppender:將日誌輸出到普通文字檔案【這裡有點需要說明,好像從版本1.2開始該Appender將會標記為禁用,替代的方案為該類的類:WriterAppender和ConsoleAppender】
  • DailyRollingFileAppender:該Appender從FileAppender繼承而來,能夠按照一定的頻度滾動日誌記錄檔案
  • WriterAppender:根據使用者的選擇將日誌的資訊以資料流的方式傳送到任何使用者指定的地方
  • SMTPAppender:在特殊的事件日誌發生的時候,傳送一封Email到指定的郵件地址,一般情況下是針對ERROR級別以及FATAL級別的錯誤進行這種Appender的配置
  • SocketAppender:將傳送一個LoggingEvent物件到一個遠端的日誌伺服器,一般情況下是一個SocketNode物件
  • SocketHubAppender:將傳送一個LoggingEvent物件的集合到一個遠端的日誌伺服器,一般情況下是一個SocketNodes
  • SyslogAppender:將日誌記錄訊息傳送到配置好的一個syslog遠端伺服器
  • TelnetAppender:該Appender和SocketHubAppender類似,也是向伺服器傳送日誌資訊,但是不是一個SocketNode物件或者SocketNode物件列,一般傳送的是Category【1.1版】輸出的結果。
  整個org.apache.log4j包結構裡面,Appender的結構圖如下:[I]標識介面[A]標識抽象類[C]標識具體類
  介面:
  Appender[I]
  類層次結構:
  AppenderSkeleton[A]
  |—AsyncAppender[C]
  |—org.apache.log4j.jdbc.JDBCAppender[C]
  |—org.apache.log4j.net.JMSAppender[C]
  |—org.apache.log4j.lf5.LF5Appender[C]
  |—org.apache.log4j.nt.NTEventAppender[C]
  |—org.apache.log4j.varia.NullAppender[C]
  |—org.apache.log4j.net.SMTPAppender[C]
  |—org.apache.log4j.net.SocketAppender[C]
  |—org.apache.log4j.net.SocketHubAppender[C]
  |—org.apache.log4j.net.SyslogAppender[C]
  |—org.apache.log4j.net.TelnetAppender[C]
  |—WriterAppender[C]
    |—ConsoleAppender[C]
    |—FileAppender[C]
      |—RollingFileAppender[C]
        |—org.apache.log4j.varia.ExternallyRolledFileAppender[C]
      |—DailyRollingFileAppender[C]
  AsyncAppender:該Appender將會使用非同步的方式來進行日誌記錄,而且該Appender會將我們在日誌記錄中記錄下來的相關資訊,一旦發現有其他的Appender與之相關,就會將這些資訊傳送給相對應的Appender。這種Appender和其他Appender不一樣的地方在於它在進行日誌記錄的過程中會開啟一條新的執行緒(Thread)來完成日誌記錄。
  【*:AsyncAppender是不能像其他日誌記錄器一樣通過log4j.properties檔案來配置,這個Appender只能使用DOMConfigurator通過編碼的方式來進行配置】
  這裡提供一段使用AsyncAppender的程式碼:
package org.susan.java.logging;

import org.apache.log4j.AsyncAppender;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.TTCCLayout;

public class AsyncLogging {
private static Logger logger = Logger.getLogger("org.susan.java.logging.AsyncLogging");
private AsyncAppender asyncAppender = null;
private ConsoleAppender consoleAppender = null;
public AsyncLogging(){
try{
logger.setAdditivity(false);
asyncAppender = new AsyncAppender();
TTCCLayout layout = new TTCCLayout("yyyy-MM-dd");
consoleAppender = new ConsoleAppender(layout,"System.out");
asyncAppender.setName("Async");
asyncAppender.setBufferSize(5);
asyncAppender.setLocationInfo(true);
asyncAppender.setBlocking(false);
asyncAppender.activateOptions();
asyncAppender.addAppender(consoleAppender);
logger.addAppender(asyncAppender);
}catch(Exception ex){
ex.printStackTrace();
}
}
public void doLogging(){
logger.debug("Hello One");
logger.debug("Hello Two");
logger.debug("Hello Three");
}
public static void main(String args[]){
AsyncLogging demo = new AsyncLogging();
demo.doLogging();
}
}
  上邊這段程式碼,建立了一個AsyncAppender,並且將她與一個ConsoleAppender連線起來,實現日誌記錄的非同步操作,執行上邊這段程式碼將會得到以下結果:
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello One
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Two
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Three
  需要注意的是下邊的程式碼:
  asyncAppender.addAppender(consoleAppender);
  該程式碼段的就可以將我們所需要的Appender和AsyncAppender連線起來進行非同步方式的日誌記錄。
  這裡遇到過一個問題,因為我查了很多資料沒有找到相關的文件,就是這裡AsyncAppender的非同步方式的原理。
  我個人覺得:AsyncAppender可以使得和它相關的所有連線的Appender進行關聯過後非同步操作,那麼一旦在AsyncAppender裡面新增了一個Appender過後,該日誌記錄會和AsyncAppender結合在一起實現非同步日誌記錄,而且新增的每個Appender都是使用了和AsyncAppender相關的非同步機制而使用新的執行緒去完整針對不同的輸出源進行彼此不影響的併發操作,每個執行緒關聯到不同的輸出源,也就是說Appender作為一個非同步日誌記錄的最初入口,使得在做日誌記錄裡面的Appender可以實現非同步機制,而Log4j裡面的Appender內部原理本身應該就是非同步的,但是內部的原理和外部相互之間Appender之間的結構沒有直接的關聯。按照這樣的理解,每個AsyncAppender可以關聯多個不同的AsyncAppender,於是我修改了上邊程式碼段:
package org.susan.java.logging;

import org.apache.log4j.AsyncAppender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.TTCCLayout;

public class AsyncLogging {
private static Logger logger = Logger.getLogger("org.susan.java.logging.AsyncLogging");
private AsyncAppender asyncAppender = null;
private ConsoleAppender consoleAppender = null;
//這是新新增的一個Appender2作為asyncAppender去連線的一個新的ConsoleAppender
private ConsoleAppender consoleAppender2 = null;
public AsyncLogging(){
try{
logger.setAdditivity(false);
asyncAppender = new AsyncAppender();
TTCCLayout layout = new TTCCLayout("yyyy-MM-dd");
consoleAppender = new ConsoleAppender(layout,"System.out");
// 為了在Eclipse平臺裡面測試的時候可以更加明顯知道誰在進行記錄,所以第二個ConsoleAppender使用了System.err
TTCCLayout layout1 = new TTCCLayout("yyyy-MM-dd");
consoleAppender2 = new ConsoleAppender(layout1,"System.err");

asyncAppender.setName("Async");
asyncAppender.setBufferSize(5);
asyncAppender.setLocationInfo(true);
asyncAppender.setBlocking(false);
asyncAppender.activateOptions();

asyncAppender.addAppender(consoleAppender);
// 這裡建立的非同步Appender新增了第二個Appender與之關聯起來
asyncAppender.addAppender(consoleAppender2);
logger.addAppender(asyncAppender);
}catch(Exception ex){
ex.printStackTrace();
}
}
public void doLogging(){
logger.debug("Hello One");
logger.debug("Hello Two");
logger.debug("Hello Three");
}
public static void main(String args[]){
AsyncLogging demo = new AsyncLogging();
demo.doLogging();
}
}
  修改過後將會得到下邊的輸出:
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello One
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello One
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Two
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Three
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Two
2009-09-17 [main] DEBUG org.susan.java.logging.AsyncLogging - Hello Three
  這樣我們推測的結果就一目瞭然了,上邊紅色部分是consoleAppender2輸出的結果,灰色部分是consoleAppender輸出的結果
  JDBCAppender:1.2版本里面的JDBCAppender將來很有可能會被替換掉,不僅僅如此,它也不會用來記錄相關異常。JDBCAppender主要提供了一個機制直接將日誌寫入到資料庫,每一個Appender的呼叫都會新增到一個ArrayList裡面作為緩衝,當這個緩衝裡面填充了事件日誌資訊的時候,將會被JDBCAppender替代掉,然後根據相關配置生成對應的SQL指令碼並且執行。在log4j.properties配置檔案裡面可以配置該Appender的BufferSize(緩衝區大小)、dbURL(資料庫的URL地址)、User(資料庫使用者名稱)、Password(資料庫密碼)。在操作過程中,可以通過使用setSql方法來設定在日誌記錄中需要使用的SQL語句,這個SQL語句將會傳送到PatternLayout物件【當然該物件是由使用者定義的】,預設情況下所有的模式轉換在PatternLayout格式化過程都是會出現在生成的SQL命令裡面的。
  有時候我們開發過程需要自己定義一個基於資料庫的日誌記錄器,當JDBCAppender作為我們使用類的父類的時候,有幾個操作是必須要注意的:
  • 重寫方法getConnection()來獲取我們需要的連線,重寫該方法主要是啟用應用程式的連線池
  • 重寫closeConnection(Connectoin con)方法,如果我們重寫了getConnection方法,那麼應該重寫closeConnection方法用來關閉我們所需要的連線,或者說將不使用的連線放入到我們需要的連線池裡面
  • 重寫getLogStatement(LoggingEvent event)方法用來生成動態的語句,預設情況下我們直接使用SQL語句的可選值。
  JMSAppender:在使用JMSAppender的過程中,事件訊息是作為JMS訊息定義的型別ObjectMessage在傳輸過程進行序列化和反序列化操作完成的,其使用模式為JMS Topic方式。
  LF5Appender【不瞭解詳情】:這種Appender將會把日誌記錄到基於日誌控制檯的Swing介面上,該Swing控制檯支援各種型別、多種詳細試圖以及一個基於全文檢索的日誌搜尋
  NTEventLogAppender:這個Appender只能使用於Windows作業系統,而且在使用該Appender的時候,需要將Log4j解壓過後裡面的NTEventLogAppender.dll動態連結庫配置到Windows系統的PATH環境變數裡面,否則我們在開發過程將會收到JVM的錯誤資訊:java.lang.UnsatisfiedLinkError
  這裡再提供一段將日誌寫入Windows事件檢視器的程式碼:
package org.susan.java.logging;

import org.apache.log4j.Logger;

public class NTEventTester {
public static void main(String args[]){
Logger logger = Logger.getLogger("NTlog");
logger.info("This is the test info message");
// 只有這條記錄會寫入進去
logger.fatal("This is the test fatal message");
}
}
  僅僅這段程式碼當然不夠,看看這段程式碼的配置檔案log4j.properties
log4j.logger.NTlog = FATAL,stdout
log4j.appender.stdout=org.apache.log4j.nt.NTEventLogAppender
log4j.appender.stdout.Source=org.susan.java.logging.NTEventTester
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1};%L - %m%n
  上邊的配置後邊會講到,在配置章節講log4j.properties的資訊的時候,這裡先不做說明,但是注意log4j.logger.NTlog裡面的NTlog就是在程式碼裡面通過Logger.getLogger("NTlog")裡面的引數,而且邊的等級指定了記錄什麼資訊,執行這段程式碼可能控制檯會有下邊的輸出:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no NTEventLogAppender in java.library.path
  上邊的錯誤就是上邊講到的沒有配置NTEventLogAppender.dll的緣故,所以按照上邊的配置方法需要將dll配置到PATH環境變數裡面,這裡再簡單說明以下:
  在32bit系統裡面,可以直接把NTEventLogAppender.dll拷貝到C:/Windows/System32,這裡C為安裝Windows操作系統盤的碟符名稱
  在64bit系統裡面的路徑應該是:C:/Windows/SysWOW64,這一點不要搞錯
  配置好了過後控制檯仍然沒有相關輸出,彆著急,去Windows看看事件檢視器就會出現下邊的輸出:

  從截圖可以看到,該事件的源為org.susan.java.logging.NTEventTester,也就是我們自己將這條記錄寫入到事件檢視器裡面去的。
  NullAppender:上邊已經說過了org.apache.log4j.varia裡面包含了多個相關元件以及Filter元件,這個Appender可以將日誌記錄訊息傳送到任意裝置
  SMTPAppender:使用該Appender的時候,可以配置BufferSize引數來配置有多少個日誌記錄訊息會通過郵件發出去。
  這裡再提供一份傳送郵件的日誌記錄器的測試程式碼:
package org.susan.java.logging;

import org.apache.log4j.Logger;

public class EmailLogging {
public static void main(String args[]){
Logger logger = Logger.getLogger("Emaillog");
logger.info("This is the test info message");
logger.fatal("This is the test fatal message");
System.out.println("Success...");
}
}
  這段程式碼本身和上邊NT記錄的本身沒有太大的區別,而真正的區別在於下邊的配置檔案項:
log4j.logger.Emaillog = WARN,stdout
log4j.appender.stdout=org.apache.log4j.net.SMTPAppender
log4j.appender.stdout.SMTPHost=smtp.126.com
log4j.appender.stdout.To=silentbalanceyh@126.com
log4j.appender.stdout.Subject=ErrorLog
log4j.appender.stdout.From=silentbalanceyh@126.com
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1};%L - %m%n
  執行這段程式碼你就可以直接將郵件傳送出去了,而且是將日誌記錄以郵件的形式發出去。但由於有些郵件客戶端的問題,可能會遇到類似下邊的錯誤:
log4j:ERROR Error occured while sending e-mail notification.
javax.mail.MessagingException: 503 bad sequence of commands
  由於本文只著重講日誌記錄,所以這個錯誤這裡不做講解。
  SocketAppender:這種Appender將傳送LoggingEvent物件到一個遠端的日誌伺服器,一般情況下是一個SocketNode,而該Appender本身擁有下邊這些屬性,該Appender是客戶端操作的:
  • 如果傳送的是SocketNode,而且這種情況下,會實現日誌記錄的實時同步。換句話說,這些事件日誌會在同一個時間戳進行記錄,而且在這種情況下,有些日誌會在本地被本地客戶端儲存下來。
  • SocketAppender在使用過程中不需要使用Layout,它會直接把LoggingEvent物件序列化到伺服器上
  • 遠端日誌記錄使用TCP協議,因此如果伺服器是可達到的,那麼事件日誌最終將直接被送到伺服器上
  • 如果遠端伺服器出了問題,傳送日誌記錄的請求將會暫停。儘管如此,如果當伺服器恢復正常的時候,日誌記錄將會繼續傳送該請求,當能夠繼續連線伺服器的時候,這種重連線會被一個connector執行緒啟動。
  • 事件日誌將會被本地TCP實現進行緩衝儲存。意思就是如果連線到伺服器的速度很慢但是比日誌記錄產生日誌的速度快的時候,該日誌記錄客戶端將不會因為網速很慢而受影響;但是如果網速很慢比起日誌記錄客戶端產生日誌的速度都慢的時候,該日誌記錄客戶端只能使用該網速的速錄來進行日誌記錄。
  • SocketAppender若不關聯到任何型別,它將不會被connector執行緒內的垃圾回收器回收。而且connector執行緒僅僅會在伺服器出問題的情況下才會存在,為了避免垃圾回收的問題,我們應該在程式設計過程中顯示呼叫close()方法,
  • 如果JVM主機的SocketAppender在它顯示關閉或者被垃圾回收器回收之前退出,這種情況下可能使得資料通道里面的資料丟失,這是Windows系統的一個常見問題。所以為了避免資料丟失,最好是在該應用程式退出之前顯示呼叫SocketAppender的close()方法或者直接呼叫LogManager.shutdown()方法。
  SocketHubAppender:這個Appender和SocketAppender不同的地方是,該Appender是服務端操作的,該Appender一旦執行起來了就開始對外傳送相關訊息,無論有沒有接收者,其相關特性和SocketAppender差不多,但是由於該Appender不負責輸出資訊,同樣的也是不需要設定Layout的。
  SyslogAppender:該Appender主要是負責向遠端syslog伺服器傳送事件日誌記錄的
  TelnetAppender:該Appender主要負責向一個只讀埠傳送時間日誌,而且在整個過程是可以基於TCP/IP進行監控的。客戶端可以通過telnet連線到該埠收取日誌記錄。
  WriterAppender:WriterAppender可以將事件日誌傳送到使用者選擇的任何一個地方,輸出類為基於Writer和OutputStream的相關裝置。
  ConsoleAppender:這種Appender會管理我們的日誌訊息,將日誌事件記錄到控制檯以System.out或者System.err作為輸出機制,它預設的輸出為System.out
  FileAppender:該Appender可以將事件日誌寫入到一個檔案裡面,它支援java.io.Writer和Console的方式,在1.2的說明裡面,將來可能會被WriterAppender和ConsoleAppender替代。
  RollingFileAppender:這種Appender從FileAppender繼承過來,大部分屬性和FileAppender是類似的,當日志記錄的檔案到達檔案容量的最大值的時候,會自動建立一個新的日誌檔案
  ExternallyRolledFileAppender:該Appender用於監聽某個埠號上邊的“RollOver”訊息【設定配置裡面的Port】
  DailyRollingFileAppender:該Appender從FileAppender繼承而來,能夠按照一定的頻度滾動日誌記錄檔案。
  [3]Log4j中的Layout:
  Log4j中的Layout類似於JDK Logging裡面的格式化輸出,只是Log4j在輸出過程將這些內容規範化了,從Log4j的整體結構上來講,主要有五個子類的Layout
  • HTMLLayout:輸出為HTML的表格方式,如果沒有指定編碼格式的話,該輸出的編碼格式為UTF-8UTF-16
  • SimpleLayout:輸出為最簡單的格式,僅僅列印一個“Level名 - 日誌記錄資訊”,例如:“DEBUG - Hello World”
  • DataLayout:該類是一個抽象類,主要是通過可選項來定製輸出,該類有一個子類TTCCLayout
    |—TTCCLayout
    :TTCC的格式主要包含了時間、執行緒、分類、巢狀內容資訊,這四個內容都是可以通過人為程式設計的方式進行關閉和開啟的,時間格式依賴DateFormat類
    176 [main] INFO org.apache.log4j.examples.Sort - Populating an array of 2 elements in reverse order.
    225 [main] INFO org.apache.log4j.examples.
    SortAlgo
    - Entered the sort method.
    262 [main] DEBUG org.apache.log4j.examples.
    SortAlgo
    .OUTER i=1 - Outer loop.
    276 [main] DEBUG org.apache.log4j.examples.
    SortAlgo
    .SWAP i=1 j=0 - Swapping intArray[0] = 1 and intArray[1] = 0
    290 [main] DEBUG org.apache.log4j.examples.
    SortAlgo
    .OUTER i=0 - Outer loop.
    304 [main] INFO org.apache.log4j.examples.
    SortAlgo.DUMP - Dump of interger array:

    該Layout的第一個欄位是該程式執行的時間,以毫秒為單位,第二個欄位主要輸出的執行的執行緒,其次是相關日誌記錄的登記,第四個欄位是該語句出現的位置以及相關型別。最後一個欄位表示巢狀內容相關資訊
    【*:不要在不同的Appender裡面使用同一個TTCCLayout例項,因為TTCCLayout是非執行緒安全的,最好的用法是一個Appender使用唯一的一個TTCCLayout例項。】
  這裡先看一段TTCCLayout的例項程式碼:
package org.susan.java.logging;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.apache.log4j.TTCCLayout;
/**
*針對TTCCLayout的一個簡單應用
**/
public class TTCCLayoutDemo {
private static Logger logger = Logger.getLogger("TestTTCC");
private ConsoleAppender appender = null;
private TTCCLayout layout = null;
public TTCCLayoutDemo()
{
logger.setAdditivity(false);
// 初始化定義的TTCCLayout
layout = new TTCCLayout("yyyy-MM-dd");
// 初始化該Layout對應的Appender,這裡使用的ConsoleAppender
appender = new ConsoleAppender(layout,"System.out");
// 將Appender新增到定義的Logger
logger.addAppender(appender);
}

public void computeSquareRoot(double number){
NDC.push(new Double(number).toString());
double sqrt = Math.sqrt(number);
logger.info("The sqrt value: " + sqrt);
NDC.pop();
}

public static void main(String args[]){
TTCCLayoutDemo demo = new TTCCLayoutDemo();
demo.computeSquareRoot(22);
demo.computeSquareRoot(44);
}
}
  不需要log4j.properties檔案,這裡會得到下邊的輸出:
2009-09-17 [main] INFO TestTTCC 22.0 - The sqrt value: 4.69041575982343
2009-09-17 [main] INFO TestTTCC 44.0 - The sqrt value: 6.6332495807108
  【*:這裡需要注意的是輸出和上邊講解遇到的TTCCLayout的輸出不一樣,因為我們在初始化構造TTCCLayout的時候使用的不同的引數的緣故。】
  • XMLLayout:該Layout會按照XML格式輸,其定義檔案為log4j.dtd,該格式化輸出輸出的不是一個標準的良好格式的XML文件,一般格式如下:
    <?xml version="1.0"
    ?>
    <!
    DOCTYPE log4j:eventSet SYSTEM "log4j.dtd" [<!ENTITY data SYSTEM "abc">]
    >
    <log4j:eventSet
    vertion="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/"
    >
    &data;

    </log4j:eventSet>

    該格式輸出的編碼一般是
    UTF-8或者UTF-16
  • PatternLayout:這是Layout裡面相對複雜的一個Layout這種情況下日誌的格式是由模式字串來進行設定的,主要依賴全域性的format方法來格式化一個LoggingEvent然後通過模式轉化將日誌的格式輸出,輸出結果是需要依賴模式轉換的
  [4]模式轉換:
  模式轉換經常使用在C語言的printf函式裡面,模式轉換就是定義一定的轉化模式,然後將對應日誌輸出的值用轉換模式進行匹配然後轉換為字串輸出。在Log4j裡面,所有的轉換模式都是由一個%開始,然後跟著相對的模式符號,而且該模式可以使用在log4j.properties屬性檔案裡面。
  舉個例子,如果使用了模式“%-5p [%t]: %m%n”模式,看下邊程式碼段:
  root.debug("Hello Debug!");
  root.info("Hello Info!");
  上邊這段程式碼我們將會得到下邊的輸出:
DEBUG [main]: Hello Debug!
INFO [main]: Hello Info!
  那麼這些模式裡面的模式表示什麼意思呢?接下來看一張模式轉換表:
模式引數 用法描述 舉例
%c 列出logger名字空間的名稱 假設當前logger的名空間為“org.java.susan.logging”
%c——org.java.susan.logging
%20c——若名空間長度小於20的時候,則左邊用空格填充
%-20c——若名空間長度小於20的時候,右邊填充空白
%.40c——若名空間長度超過40就截去多餘的字元
%20.40c——若名空間小於20,左邊用空白填充,如果超過40就截去多餘字元
%-20.40c——若名空間小於20,右邊用空白填充,如果超過40就截去多餘字元
%c{2}——susan.logging
%c{4}——org.java.susan.logging
%C 列舉出logger的類全名(包含包路徑) 假設當前類是org.java.susan.logging.PatternTester
%C——org.java.susan.logging.PatternTester
%C{1}——PatternTester
%d 顯示日誌記錄的時間,{<日期格式>}使用ISO8601定義的日期格式 %d{yyyy/MM/dd HH:mm:ss,SSS}——
2009/9/17 13:25:22,134【最後三位為毫秒】
%d{ABSOLUTE}——13:25:22,134
%d{DATE}——17 Sep 2009 13:25:22,134
%d{ISO8601}——2009-9-17 13:25:22,134
%F 顯示呼叫的logger的原始檔名 %F——PatternTester.java
%l 輸出日誌事件發生的位置,包括類目錄、發生執行緒,以及在程式碼中的行數 %l——PatternTester.fun(PatternTester.java:45)
%L 輸出日誌事件發生的程式碼行 45
%m 顯示輸出訊息 %m——該訊息輸出的主要目的是為了進行Debug操作
%M 顯示呼叫logger的方法名 %M——fun
%n 當前平臺下的換行符 %n——表示換行
%p 顯示該日誌的優先順序 %p——INFO
%r 顯示從程式啟動時到記錄該條日誌時已經經過的毫秒 %r——1435
%t 輸出產生該日誌事件的執行緒名 %t——PatternTester
%x 按NDC順序輸出日誌 假設某執行緒呼叫順序是MyDriver呼叫了org.java.susan.logging.PatternTester
%c %x - %m%n——MyDriver - Call org.java.susan.logging.PatternTester - Log in PatterTester
MyDriver - Return to MyDriver
%X 按MDC輸出日誌。通常用於多個客戶端連線同一臺伺服器,方便伺服器區分是哪個客戶端訪問留下來的日誌。 %X{5}——(記錄代號為5的客戶端日誌)
%% 顯示一個百分號 %%——%
  [5]關於NDC和MDC:
  NDC(Nested Diagnostic Context)和MDC(Mapped Diagnostic Context)是log4j中非常有用的兩個類,它們用於儲存應用程式的上下文資訊,從而便於在log中使用這些上下文資訊。
  <!--[if !supportEmptyParas]--><!--[endif]-->
  NDC採用了一個類似棧機制來push和pop上下文資訊,每一個執行緒都獨立地儲存上下文資訊。比如說一個servlet就可以針對每一個request建立對應的NDC,儲存客戶端地址等資訊。當使用的時候,我們要儘可能確保在進入一個context的時候,把相關的資訊使用NDC.push(message);在離開這個context的時候使用NDC.pop()將資訊刪除。另外由於設計上的一些問題,還需要保證在當前thread結束的時候使用NDC.remove()清除記憶體,否則會產生記憶體洩漏的問題。儲存了上下文資訊之後,我們就可以在log的時候將資訊輸出。
  使用NDC的重要好處就是,當我們輸出一些上下文資訊的時候,不需要讓logger去尋求這些資訊,而只需要在適當的位置進行儲存,然後再配置檔案中修改PatternLayout。
  MDC和NDC的用法類似,不同的是MDC內部使用了類似map的機制來儲存相關資訊,上下文資訊也是每個執行緒獨立儲存,不同的是資訊都是以它的key值儲存在“map”中。相對應的方法,MDC.put(key,value);MDC.remove(key);MDC.get(key);在配置PatternLayout的時候使用:%x{key}來輸出對應的value,同樣的MDC也有一個MDCMatchFilter。【*:MDC是執行緒獨立的,但是一個子執行緒會自動活得一個父執行緒MDC的拷貝】
  3)配置相關以及屬性檔案log4j.properties
  單獨把配置獨立為一個小節是因為Log4j大部分是需要進行配置使用的,從上邊郵件傳送以及NT事件記錄器的程式碼Demo可以知道其實僅僅需要在Log4j.properties檔案裡面配置一定的屬性,開發過程就簡化了很多,上邊兩端程式碼基本一模一樣【NT日誌記錄器和Email日誌記錄Demo程式碼】,僅僅修改了配置檔案。
  [1]BasicConfigurator類:
  BasicConfigurator類主要為Log4j提供一個基礎的配置環境,通過呼叫BasicConfigurator.configure方法可以建立一個簡單的Log4j環境,而且這個方法是通過硬編碼直接將Appender設定為ConsoleAppender的,該配置的輸出設定為PatternLayout類,模式為"%-4r [%t] %-5p %c %x - %m%n",日誌記錄的級別為Level.DEBUG,程式碼說明上邊已經用到過了。
  注意:如果不對Log4j進行任何配置的話,將會收到類似下邊的警告:
log4j:WARN No appenders could be found for logger (org.susan.java.logging.Foo).
log4j:WARN Please initialize the log4j system properly.
  如果我們通過配置log4j.properties配置檔案的方式來進行配置,只需要進行下邊的設定:
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
  [2]log4j.properties檔案:(參考連結:http://www.wangchao.net.cn/bbsdetail_60723.html
  根配置:
  根日誌記錄器配置的語法為:[真正使用的時候,記得去掉開始的#,在properties檔案裡面#的含義標識行註釋]
#log4j.rootLogger = [level], appenderName,appenderName,...
  其中Level就是日誌本身的優先順序,log4j裡面建議使用的級別為:ERROR,WARN,INFO,DEBUG
  appenderName就是日誌輸出的地方,某一個Appender的類名
  Appender配置:
  配置日誌輸出目的Appender的語法為:
#log4j.appender.appenderName = className
#log4j.appender.appenderName.key = value
#log4j.appender.appenderName.key1 = value1
  其中appenderName為定義一個Appender的名字、className為一個類全名
  Layout配置:
  佈局配置格式的語法為:
#log4j.appender.appenderName.layout = className
#log4j.appender.appenderName.layout.key1 = value1
#log4j.appender.appenderName.layout.key2 = value2
  關於Log4j中使用佈局列印的模式如下:
#%p 輸出優先順序,DEBUG,INFO,WARN,ERROR,FATAL
#%r 輸出自應用啟動到輸出該log資訊耗費的毫秒
#%c 輸出所屬的類,通常就是在所在類的全名
#%t 輸出產生該日誌事件的執行緒名
#%n 輸出一個回車換行,Windows為“/r/n”,Unix平臺為“/n”
#%d 輸出日誌的時間點的日期或者時間,格式為ISO8601,也可以使用%d{yyyy MM dd}格式化輸出
#%l 輸出日誌發生的位置,包括類名、執行緒以及程式碼中的行
  【例:配置一個名為myConsole的ConsoleAppender,其輸出Layout為TTCCLayout】
log4j.appender.myConsole = org.apache.log4j.ConsoleAppender
log4j.appender.myConsole.layout = org.apache.log4j.TTCCLayout
  【例:配置一個TTCCLayout的具體屬性】
log4j.appender.myConsole.layout.ThreadPrinting=false
log4j.appender.myConsole.layout.ContextPrinting=false
log4j.appender.myConsole.layout.CategoryPrefixing=false
log4j.appender.myConsole.layout.DateFormat=RELATIVE
  【例:配置一個自定義名稱的Logger】
log4j.logger.org.susan.java.logging.mylogger=DEBUG,myConsole
  這種情況下,可以使用Logger logger = Logger.getLogger("org.susan.java.logging.mylogger");來獲取該日誌記錄,而該日誌記錄的輸出輸出到myConsole這個Appender
  【例:配置一個FileLayout】
log4j.appender.myConsole.layout = org.apache.log4j.PatternLayout
log4j.appender.myConsole.layout.conversionPattern=%p - %m%n
  【例:配置一個SMTP的郵件記錄】
log4j.logger.EmailLogger = WARN,SMTP
log4j.appender.SMTP=org.apache.log4j.net.SMTPAppender
log4j.appender.SMTP.SMTPHost=smtp.126.com
log4j.appender.SMTP.To=silentbalanceyh@126.com
log4j.appender.SMTP.Subject=ErrorLog
log4j.appender.SMTP.From=silentbalanceyh@126.com
log4j.appender.SMTP.layout=org.apache.log4j.SimpleLayout
  【例:NT事件檢視器配置】
log4j.logger.NTLogger = FATAL,NTEVENT
log4j.appender.NTEVENT=org.apache.log4j.nt.NTEventLogAppender
log4j.appender.NTEVENT.Source=org.susan.java.logging.NTEventTester
log4j.appender.NTEVENT.layout=org.apache.log4j.PatternLayout
log4j.appender.NTEVENT.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1};%L - %m%n
  【例:Rolling檔案配置】
log4j.logger.RollingLogger = FATAL,ROLLING_FILE
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=rolling.log
log4j.appender.ROLLING_FILE.Append=true
log4j.appender.ROLLING_FILE.MaxFileSize=10KB
log4j.appender.ROLLING_FILE.MaxBackupIndex=1
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
  【例:Socket相關配置】
log4j.logger.SocketLogger = INFO,SOCKET
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=5001
log4j.appender.SOCKET.LocationInfo=true
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.
SOCKET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n
  【例:JDBC配置】
log4j.logger.JDBCLogger = INFO,DATABASE
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/db_rect
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=********
log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
  
【例:DailyRolling日誌記錄配置】
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=SampleMessages.log4j
log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j'
log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout
  ii.Commons Logging Framework:
  1)JCL基本概念[介紹版本為1.1]:
  Commons Logging Framework又稱為JCL(Java Common Logging),該日誌記錄框架和Log4j以及Java Logging(JDK 1.4 Logging Framework)存在本質區別。前兩個日誌記錄器本身提供了詳細的實現,都有一個屬於自己的體系結構,而JCL本身沒有。JCL可以真正稱為一個日誌框架,因為它提供的是一個日誌介面,而且是一個輕量級的不依賴實現的日誌記錄的抽象介面工具包,它允許任何開發者使用工具在該抽象框架下邊進行日誌記錄的實現層的開發。而它本身只是一個輕量級的抽象框架
  官方下載地址:
  http://apache.freelamp.com/commons/logging/binaries/commons-logging-1.1.1-bin.zip
  因為JCL本身的類比較少,先看看整個抽象框架的包結構:[I]標識介面[A]標識抽象類[C]標識具體類,[E]標識異常
  org.apache.commons.logging
  |—[I]Log
  |—[C]LogFactory
  |—[C]LogSource
  |—[E]LogConfigurationException
  org.apache.commons.logging.impl
  |—[C]AvalonLogger
  |—[C]Jdk13LumberjackLogger
  |—[C]Jdk14Logger
  |—[C]Log4JLogger
  |—[C]LogFactoryImpl
  |—[C]LogKitLogger
  |—[C]NoOpLog
  |—[C]ServletContextCleaner
  |—[C]SimpleLog
  |—[C]WeakHashtable
  • Log介面:
    實現該介面的例項可以被LogFactory順利建立和初始化,如果某一個日誌記錄想要初始化必須呼叫一個帶String單引數的建構函式,傳入的語意為該日誌記錄器的名稱
    Log介面定義了日誌的幾個登記,所有實現該介面的類都需要遵循這幾個等級:
    trace、debug、info、warn、error、fatal
  • LogFactory為一個抽象類,使用工廠模式,可以建立一個實現了Log介面的日誌記錄器例項,而且該記錄器實現是整合了JavaAPI和JAXP
    【關鍵:LogFactory的實現是基於
    SAXParserFactory和DocumentBuilderFactory的,所以在使用的時候有可能還會用到Apache Xerces[一個可以解析XML的庫]
  • LogSource類【Deprecated】
    該類是原版本的建立Log例項的類,現在已經被LogFactory替代了。當LogFactory建立某個Log例項的時候,應用程式可以通過呼叫方法
    makeNewLogInstance去例項化某個類或者針對該類的實現類進行配置工作。預設情況下,如果呼叫方法getInstance()將會使用下列演算法:
    [1]如果Log4J是配置好了的,那麼LogFactory會建立一個
    org.apache.commons.logging.impl.Log4JLogger例項
    [2]如果JDK是1.4或者以上的版本,LogFactory就會建立一個org.apache.commons.logging.impl.Jdk14Logger例項
    [3]如過兩個都沒有滿足,就返回一個org.apache.commons.logging.impl.NoOpLog例項
    上邊是預設的配置和預設的建立例項的行為,但是我們可以通過兩種方法修改相關內容
    [1]啟動命令提示行,在進行java編譯的時候設定自定義的org.apache.commons.logging.Log的實現類的名稱
    [2]在執行時呼叫
    LogSource.setLogImplementation()

  • LogConfigurationException類:
    當使用LogFactory建立實現了Log類介面的例項的時候,如果建立失敗就會丟擲該異常
  • AvalonLogger類:
    該類有兩種用途:
    [1]該類的例項可以通過AvalonLogger(Logger)方法來進行構造,在這種情況下,該類在Logger外層進行了一層簡單的封裝,這種用法最大的用途在於使用一個屬性設定器的時候。
    [2]方法setDefaultLogger(org.apache.avalon.framework.logger.Logger)的屬性呼叫來設定Avalon類的關聯

      這裡插入Avalon框架的一段介紹:
    Apache的Avalon是一個包括核心框架、工具、元件和容器的面向元件程式設計(COP)的完整開發平臺。通過使用關鍵設計模式,如反向控制模式(IoC)和分離考慮模(SoC),Avalon實現了傳統OOP框架的一些優點: 1.沒有執行鎖 2.元件之間低耦合 3.管理元件生命週期 4.配置管理和易用的API 5.元件後設資料框架和工具 6.服務相關的管理獨立的、J2EE或Web環境的嵌入式容器 在COP方面,可重用的元件能夠被組合到容器中,以提供應用程式模組。模組可以依次使用來建立你所需要的,從客戶桌面應用程式,到FTP伺服器,到Web服務,等等。Avalon提供各種基本元件和預設的應用程式模組,幫助你快速的建立你自己的應用程式解決方案。
  • Jdk13LumberjackLogger:實現了介面org.apache.commons.logging.Log,封裝了Lumberjack實現部分,主要用於JDK 1.4之前實現Java的日誌記錄,Lumberjack專案是用來進行Java日誌記錄的老版本的日誌API
  • Jdk14Logger:實現了介面org.apache.commons.logging.Log,用來封裝了上邊我們講到的JDK 1.4 Logging Framework
  • Log4JLogger:實現介面org.apache.commons.logging.Log,用來封裝了Log4j版本1.2裡面的Logger例項,初始化該例項的配置的時候應該使用通常的初始化方式。
    該類不能用於Log4j 1.3,主要原因在於:[1]1.2版本的Logger使用的是Priority引數而不是我們介紹的Level引數;[2]Level類是繼承於Priority的,在1.3版本里面,需要修改相關優先順序,但是不是繼承於Priority的,所以不相容1.3版本,具體原因是否如此我不太清楚。
  • LogFactoryImpl類:LogFactory的子類,通過一定的演算法動態選擇應該使用哪種日誌記錄器,主要對底層的不同的日誌實現類進行簡單的封裝
    [1]
    使用工廠配置屬性org.apache.commons.logging.Log用來標識使用的實現類
    [2]
    使用org.apache.commons.logging.Log系統屬性用來標識使用的實現類
    [3]如果Log4J是配置好了的,那麼LogFactory會建立一個org.apache.commons.logging.impl.Log4JLogger例項
    [4]如果JDK是1.4或者以上的版本,LogFactory就會建立一個org.apache.commons.logging.impl.
    Jdk14Logger
    例項
    [5]如過兩個都沒有滿足,就返回一個org.apache.commons.logging.impl.
    SimpleLog例項

    不僅僅如此,該類還有一個方法可以提供反向的關聯,使用Log實現類的方法setLogFactory(),傳入一個引數LogFactory,這種方法呼叫過後就可以修改實現類對應的Factory的關聯項,在整個程式執行過程中,Factory將會記錄下來所有建立過的Logger例項物件,當再次呼叫getInstance()方法的時候就直接從記錄裡面啟用該例項。
    【我仔細思考了以下,這個地方應該使用了Logger的“池化”技術,使用工廠雖然可以反覆進行例項的構造,但是使用的是Logger的name作為標識,從設計上考慮,Logger的名字一般情況下都是某個類的全名,從整個系統級別講,如果OO設計設計得不錯的話,每一個類如果需要進行日誌記錄的話最好使用單個日誌記錄器,這樣可以節省系統開銷。】
  • LogKitLogger類:實現了介面org.apache.commons.logging.Log,該類封裝了avalon-logkit日誌系統,僅僅將LogKit留給使用者自己進行配置。
  • NoOpLog類:該類將會丟擲所有的相關異常資訊,沒有任何系統屬性配置的支援,一般情況下不使用該類。
  • SimpleLog類:簡單實現了org.apache.commons.logging.Log介面,用來記錄所有可用的日誌資訊,而且把所有定義好的Logger輸出到System.err,下邊的一些系統屬性可以用來配置該Logger的一些屬性:
    [1]org.apache.commons.logging.simplelog.defaultlog——預設的SimpleLog的等級引數,必須是以下的值的集合(trace,debug,info,warn,error,fatal
    ),如果沒有設定預設為info
    [2]org.apache.commons.logging.simplelog.
    log.xxxxx——定義某個名稱為xxxxx例項的日誌器的等級,同上值必須是一個Level的集合
    [3]org.apache.commons.logging.simplelog.
    showlogname
    ——如果為true在日誌記錄的時候需要輸出name屬性,如果false的話就不輸出Logger的名稱,預設為false
    [4]org.apache.commons.logging.simplelog.
    showShortLogname——和上邊引數一樣,唯一的區別是該項是類名,而上邊的引數是輸出的類全名,還有不同的是預設值為true
    [5]org.apache.commons.logging.simplelog.
    showdatetime——為true就輸出時間,如果為false就不輸出時間,預設是false
    [6]org.apache.commons.logging.simplelog.
    dateTimeFormat——設定時間的格式,該格式應該是java.text.SimpleDateFormat可以解析的時間合法格式
    這種配置需要檢測類載入器在路徑裡面是否可以找到simplelog.properties的屬性檔案用來配置以上的選項,或者直接在java命令後邊帶上相關的引數
  • ServletContextCleaner類和WeakHashtable類在此不做講解,需要了解的可以去查詢該類的API,而且我沒有用到過這兩個類,所以也沒有去了解這兩個類到底做了些什麼,不好意思。
  2)如何配置Log4j實現
  Log4j是一個常用的日誌記錄庫,所以在該抽象框架裡面配置相關實現的時候需要針對一些屬性做一些簡單的操作。JCL本身不提供任何相關的實現,它僅僅是一個抽象框架,在路徑下邊必須提供一個屬性檔案:
  commons-logging.properties
  配置的時候需要配置以下屬性:
#log4j.configuration=log4j.properties
  使用該屬性指定我們需要的Log4j的屬性檔案的名稱,如果沒有指定,預設的就是log4j.properties
#log4j.rootCategory=priority[,appender]*
  設定預設的root日誌記錄器的優先順序
#log4j.logger.logger.name=priority
  設定名稱為logger的優先順序,一般情況為:DEBUG,INFO,WARN,ERROR,FATAL
#log4j.appender.appender.Threshold=priority
  設定不同的輸出源:console,files,sockets或者其他的。
  關於這兩個日誌庫的整合使用,提供一段程式碼進行簡單說明:
  log4j.properties內容:
log4j.logger.org.susan.java.logging.commonlog= WARN,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1};%L - %m%n
  commons-logging.properties內容:
log4j.configuration=log4j.properties
  執行程式碼:
package org.susan.java.logging;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LogTest {
public static void main(String args[]){
Log log = LogFactory.getLog("org.susan.java.logging.commonlog");
log.debug("debug");
log.error("error");
log.fatal("fatal");
log.info("info");
log.warn("warn");
}
}
  下邊是輸出結果:
20:26:11,014 ERROR commonlog;10 - error
20:26:11,038 FATAL commonlog;11 - fatal
20:26:11,039 WARN commonlog;13 - warn
  這樣就將抽象框架和Log4j實現整合到一起了。
5.總結
  到這裡,關於異常、日誌、斷言部分的內容就全部講完了,有什麼筆誤的地方記得發Email給我:silentbalanceyh@126.com。日誌部分主要選取了Java裡面比較常用的日誌記錄框架,根據各種開源產品的應用可以知道,Log4j也是推薦使用的日誌記錄框架,此處也針對各種不同的日誌記錄器進行了詳細的說明。斷言部分本身內容不太多,只要理解該用法和相關意思了就很容易使用了,最終用於開發的時候注意使用方法以及用的位置合適就可以了。總體來講,這一部分內容有可能我們在平時的資訊管理類的系統裡面很難遇到,但是真正使用的時候也很不想去查閱官方文件,這裡總結的很多內容來自官方文件的參考以及個人做的一些實驗。記得如果有什麼問題發Email給我我會不斷玩善的。沒有想過篇幅過長,本來只有上下兩篇,現在只能打散了形成三篇,希望各位讀者見諒。

相關文章