日誌Log精講
1日誌為什麼要存在
我們平時寫的工具或應用程式,可以通過人眼檢視除錯來檢視執行過程中出現的錯誤,可以通過system.out.println()來輸出程式執行狀態資訊檢視。
(1)那麼,一個龐大複雜的專案工程,含有大量的介面和方法,執行過程冗長繁雜,還要使用上述的辦法來慢慢排查問題麼?
自然不是,一個設計良好的專案,肯定記錄了專案執行日誌log。
日誌的強大之處,在於它能記錄儲存專案的執行狀態,便於日後查閱,很是方便快捷、易於除錯;更重要的是,他還能夠完成跟蹤除錯、程式狀態記錄、崩潰資料恢復等工作。
但如果使用system.out.println(),則太弱了,不僅繁瑣麻煩,輸出的地方和多少都不易控制。
(2)那麼,如何設計一個良好的日誌類來應用到專案中,便於提升專案的開發和維護呢?
答案:在java的世界裡,我們不用考慮和設計日誌類,因為已經存在了很多優秀的日誌系統供我們使用,比如:Log4j和Logback,還有sun公司的java.util.Logging,另外java專案中的各種框架(如:Spring,Mybatis,Httpclient…)等第三方包都有自己的日誌系統來實現日誌功能,但是這麼多的日誌實現系統同時存在,難以避免的就出現了各個日誌系統不相容的災難性情況!
(3)那麼,應該如何解決雜亂的日誌系統不相容的災難情況呢?
解決辦法就是出現了日誌框架。
2 日誌系統和日誌框架
日誌系統:日誌類的具體實現。經典的有log4j,log4j作者推出的被高度評價的logBack,以及jdk自帶的java.util.Logging等。
日誌框架:為解決多個日誌系統的相容性問題而設計的框架,當然如果只有一個日誌系統,就完全沒必要使用日誌框架了。常用的主流日誌框架有commons-logging和slf4j。
下面說一下這兩種主流框架的使用!
3 commons-logging框架
3.1介紹
commons-logging是apache推出的日誌框架,只是規定了日誌的介面規範,可以理解為是一個通用的日誌介面,其設計原理類似於jdk中servlet和jdbc的設計。
目前,主流的日誌系統log4j和java.util.Logging都實現了commons-logging定義的介面。那麼,就可以很方便的使用這些實現了 commons-logging介面的日誌系統了,使用的時候不用去考慮具體的日誌實現或到底是哪種日誌系統,使用者可以自由選擇第三方日誌元件作為具體實現。
舉個便於理解的例子:
有這樣兩個實現了Log介面的類:
public class MyLog implements Log {
System.out.println(“info”);
}
public class YourLog implements Log {
System.out.println(“info”);
}
你可以把commons-logging框架理解為Log介面,只是一個定義了規範的介面,把實現了commons-logging框架的眾多日誌系統看作是實現了Log介面的實現類MyLog和YourLog。使用的時候拿著Log介面使用就行了,不用關注具體的實現類;即,使用日誌系統的時候拿著日誌框架commons-logging使用就行了。
3.2 好處總結
第一:簡化了使用和配置,為“所有的Java日誌實現”提供一個統一的介面,並且能分離專案和日誌。
第二:使用日誌框架commons-loging可以自適應的選擇日誌實現系統。
自適應日誌工具選擇功能
不得不說的是它的自適應日誌工具選擇功能?
1) commons-logging首先在classpath下尋找自己的配置檔案commons-logging.properties,找到則使用自己定義的Log;找不到則繼續查詢是否已定義系統環境變數org.apache.commons.logging.Log,找到則使用自己定義的Log;
2)否則,在classpath中查詢是否有Log4j的路徑,若有則使用Log4j;
3)否則,使用JDK(1.4版本以後提供了日誌功能)自身的日誌實現類;
4)如果還沒有,則使用commons-logging自己提供的一個簡單的日誌實現類SimpleLog;
所以,commons-logging可以保證日誌功能的實現,自適應的儘可能找到一個最合適的工具,這也是它的優勢所在。
可以看到,commons-logging對程式設計者和Log4j都非常友好。為了簡化配置,一般不使用commons-logging的配置檔案,也不設定與commons-logging相關的系統環境變數,而只需將Log4j的Jar包放置到classpash中就可以了。這樣就很簡單地完成了commons-logging與Log4j的融合。如果不想用Log4j了怎麼辦?只需將classpath中的Log4j的Jar包刪除即可。就這麼簡單!
3.2 經典應用commons-logging+log4j
這種實現出現在各種框架裡,如Spring,Webx,Mybatis等等, 一般為了避免直接依賴具體的日誌實現,一般都是結合commons-logging 來實現。常見應用實現程式碼如下:
//1: 匯入所有需的commons-logging類
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class XXX{
//2:要使用日誌的類XXX中定義私有靜態變數logger,,這是目前被普通認為的最好的方式;
//靜態是為了避免出現多個例項,
private static Log logger = LogFactory.getLog(XXX.class);
//3:在要使用日誌的方法中使用日誌
public void method(args){
log.debug("111");
log.info("222");
log.warn("333");
log.error("444");
log.fatal("555");
}
}
例項化私有靜態日誌變數的時候,為什麼不寫作LogFactory.getLog(this.getClass())?因為static類成員訪問不到this指標!
通過上述,可以基本滿足輸出日誌資訊,如果要對輸出的資訊有特殊要求,那麼,就需要輸出配置資訊log4j.properties。
3.3 日誌輸出 配置檔案
在程式中,可以通過每個類的私有靜態變數log,將不同性質的日誌資訊輸出到不同型別的日誌檔案或不同目的地(目的地是哪裡?視配置可定,可能是stdout,也可能是檔案,還可能是傳送到郵件,甚至傳送簡訊到手機……詳見下文對log4j.properties的介紹):
l debug() 輸出“除錯”級別的日誌資訊;
l info() 輸出“資訊”級別的日誌資訊;
l warn() 輸出“警告”級別的日誌資訊;
l error() 輸出“錯誤”級別的日誌資訊;
l fatal() 輸出“致命錯誤”級別的日誌資訊;
根據不同的性質,日誌資訊通常被分成不同的級別,從低到高依次是:“除錯(DEBUG)”“資訊(INFO)”“警告(WARN)”“錯誤(ERROR)”“致命錯誤(FATAL)”。為什麼要把日誌資訊分成不同的級別呢?這實際上是方便我們更好的控制它。比如,通過Log4j的配置檔案,我們可以設定“輸出‘除錯’及以上級別的日誌資訊”(即“除錯”“資訊”“警告”“錯誤”“致命錯誤”),這對專案開發人員可能是有用的;我們還可以設定“輸出“警告”及以上級別的日誌資訊”(即“警告”“錯誤”“致命錯誤”),這對專案終端使用者可能是有用的。
僅從字面上理解,也可以大致得出結論:最常用的應該是debug()和info();而warn()、error()、fatal()僅在相應事件發生後才使用。
所以要正確地應用Log4j輸出日誌資訊,log4j.properties
log4j.rootLogger = DEBUG, CONSOLE,A1
#最最重要的一個屬性, 分成兩部分:第一個逗號之前的是第一部分,指定“輸出級別”;後面的是第二部分,指定“輸出目的地”
“輸出級別”有可選的五個值,分別是DEBUG、INFO、WARN、ERROR、FATAL,它們是由Log4j系統定義的。如果此處指定的是“WARN”則僅呼叫warn()、error()、fatal()方法輸出的日誌資訊才被輸出到“輸出目的地”,而呼叫debug()、info()方法輸出的日誌資訊不被輸出到“輸出目的地”。
“輸出目的地”就是我們自己定義的了,就在log4j.properties的後面部分,此檔案定義的“輸出目的地”有CONSOLE、FILE、 ROLLING_FILE、SOCKET、LF5_APPENDER、MAIL、DATABASE、A1、im。該檔案之所以可作主模板,就是因為它比較 全面地定義了各種常見的輸出目的地(控制檯、檔案、電子郵件、資料庫等)。
為什麼說“CONSOLE”表示將日誌資訊輸出到“控制檯”呢?那就要看一下後文的定義了:
# 應用於控制檯
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.Threshold = DEBUG
log4j.appender.CONSOLE.Target = System.out
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = [framework] %d-%c -%-4r [%t] %-5p %c %x-%m%n
#log4j.appender.CONSOLE.layout.ConversionPattern = [start]%d {DATE}[DATE]%n %p[PRIORITY]%n %x[NDC]%n %t[THREAD] n %c[CATEGORY] %n %m[MESSAGE] %n %n
為什麼說“A1”表示將日誌資訊輸出到“SampleMessages.log4j檔案”呢?還要看後文的定義:
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
log4j.properties檔案模板:
##Log4J的配置之簡單使它遍及於越來越多的應用中了
##Log4J配置檔案實現了輸出到控制檯、檔案、回滾檔案、傳送日誌郵件、輸出到資料庫日誌表、自定義標籤等全套功能。擇其一二使用就夠用了。
##此檔案(log4j.properties)內容來自網路,非本文作者liigo原創。
log4j.rootLogger = DEBUG, CONSOLE,A1
log4j.addivity.org.apache = true
# 應用於控制檯
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.Threshold = DEBUG
log4j.appender.CONSOLE.Target = System.out
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = [framework] %d-%c -%-4r[%t] %-5p %c %x-%m %n
#log4j.appender.CONSOLE.layout.ConversionPattern = [start] %d {DATE}[DATE]%n %p[PRIORITY] %n %x[NDC] %n %t[THREAD] n %c[CATEGORY]%n %m[MESSAGE]%n %n
#應用於檔案
log4j.appender.FILE = org.apache.log4j.FileAppender
log4j.appender.FILE.File = file.log
log4j.appender.FILE.Append = false
log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern = [framework] %d-%c-%-4r[%t] %-5p %c%x-%m%n
# Use this layout for LogFactor 5 analysis
# 應用於檔案回滾
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.appender.SOCKET = org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost = localhost
log4j.appender.SOCKET.Port = 5001
log4j.appender.SOCKET.LocationInfo = true
# Set up for Log Facter 5
log4j.appender.SOCKET.layout = org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern = [start] %d {DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n
# Log Factor 5 Appender
log4j.appender.LF5_APPENDER = org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords = 2000
# 傳送日誌給郵件
log4j.appender.MAIL = org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold = FATA
log4j.appender.MAIL.BufferSize = 10
log4j.appender.MAIL.From = [email protected]
log4j.appender.MAIL.SMTPHost = www.wusetu.com
log4j.appender.MAIL.Subject = Log4J Message
log4j.appender.MAIL.To = [email protected]
#SMTP傳送認證的帳號名
log4j.appender.MAIL.SMTPUsername=****
#SMTP傳送認證帳號的密碼
log4j.appender.MAIL.SMTPPassword=******
log4j.appender.MAIL.layout = org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern = [framework] %d-%c-%-4r[%t]%-5p%c%x-%m%n
# 用於資料庫
log4j.appender.DATABASE = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL = jdbc:mysql: // localhost:3306/test
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
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
#自定義Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = [email protected]
log4j.appender.im.layout = org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern = [framework] %d-%c-%-4r[%t]%-5p%c%x-%m%n
# 結束
4slf4j框架
4.1介紹
slf4j全稱為Simple Logging Facade for JAVA,一個java的簡單日誌門面。類似於Apache Common-Logging,是對不同日誌框架提供的一個門面封裝,可以在部署的時候不修改任何配置即可接入一種日誌實現方案。但是,他在編譯時靜態繫結真正的Log庫。使用SLF4J時,如果你需要使用某一種日誌實現,那麼你必須選擇正確的SLF4J的jar包的集合。
4.2經典應用slf4j+logback
Logback必須配合sl4j使用。由於logback和sl4j是同一個作者,其相容性不言而喻。但sl4j面臨與其他日誌框架和日誌系統的相容性問題。
常見應用實現程式碼如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class XXX {
private static Log logger = LogFactory.getLog(this.getClass());
}
4.3日誌輸出 配置檔案
類似於commons-logging的輸出配置檔案,slf4j也同樣需要一個配置檔案logback.xml,通過該配置檔案,在程式執行之後,slf4j會自動載入logback的日誌實現,然後logback從類路徑中讀取logback.xml檔案中的日誌配置資訊,實現日誌的記錄等功能。
一個logback.xml模板配置示例及註釋詳解:
<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:當此屬性設定為true時,配置檔案如果發生改變,將會被重新載入,預設值為true。
scanPeriod:設定監測配置檔案是否有修改的時間間隔,如果沒有給出時間單位,預設單位是毫秒。當scan為true時,此屬性生效。預設的時間間隔為1分鐘。
debug:當此屬性設定為true時,將打印出logback內部日誌資訊,實時檢視logback執行狀態。預設值為false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 上下文變數設定,用來定義變數值,其中name的值是變數的名稱,value的值時變數定義的值。
通過<property>定義的值會被插入到logger上下文中。定義變數後,可以使“${}”來使用變數。 -->
<property name="CONTEXT_NAME" value="logback-test" />
<!-- 上下文名稱:<contextName>, 每個logger都關聯到logger上下文,
預設上下文名稱為“default”。但可以使用<contextName>設定成其他名字,用於區分不同應用程式的記錄。
一旦設定,不能修改。 -->
<contextName>${CONTEXT_NAME}</contextName>
<!-- <appender>是<configuration>的子節點,是負責寫日誌的元件。
有兩個必要屬性name和class。
name指定appender名稱,
class指定appender的實現類。 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 對日誌進行格式化。 -->
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%class|%thread|%method|%line|%msg%n
</pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 被寫入的檔名,可以是相對目錄,也可以是絕對目錄,如果上級目錄不存在會自動建立,沒有預設值。 -->
<file>${logs.dir}/logback-test.log</file>
<!-- 當發生滾動時的行為 -->
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<!-- 必須包含“%i”例如,假設最小值和最大值分別為1和2,命名模式為 mylog%i.log,會產生歸檔檔案mylog1.log和mylog2.log。還可以指定檔案壓縮選項,例如,mylog%i.log.gz 或者 沒有log%i.log.zip -->
<FileNamePattern>${logs.dir}/logback-test.%i.log</FileNamePattern>
<!-- 視窗索引最小值 -->
<minIndex>1</minIndex>
<!-- 視窗索引最大值 -->
<maxIndex>1</maxIndex>
</rollingPolicy>
<!-- 啟用滾動的條件。 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<!-- 活動檔案的大小,預設值是10MB -->
<maxFileSize>30MB</maxFileSize>
</triggeringPolicy>
<!-- 對記錄事件進行格式化。 -->
<encoder>
<charset>UTF-8</charset>
<Pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%class|%thread|%method|%line|%msg%n
</Pattern>
</encoder>
</appender>
<!-- 特殊的<logger>元素,是根logger。只有一個level屬性,應為已經被命名為"root".
level:設定列印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能設定為INHERITED或者同義詞NULL。預設是DEBUG。
<root>可以包含零個或多個<appender-ref>元素,標識這個appender將會新增到這個loger。 -->
<root>
<level value="WARN" />
<appender-ref ref="stdout" />
<appender-ref ref="file" />
</root>
<!-- 用來設定某一個 包 或者具體的某一個 類 的日誌列印級別、以及指定<appender>,
name:用來指定受此logger約束的某一個包或者具體的某一個類。
level:用來設定列印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,還有一個特俗值INHERITED或者同義詞NULL,代表強制執行上級的級別。如果未設定此屬性,那麼當前loger將會繼承上級的級別。
additivity:是否向上級logger傳遞列印資訊。預設是true。(這個logger的上級就是上面的root)
<logger>可以包含零個或多個<appender-ref>元素,標識這個appender將會新增到這個logger。-->
<logger name="xuyihao.logback.test" level="DEBUG" additivity="true"></logger>
</configuration>
參考資料:
《java日誌元件介紹(common-logging,log4j,slf4j,logback )》
《log4j和log》
《logback.xml配置模版》