本文由ImportNew-Wing翻譯自loggly。歡迎加入翻譯小組。轉(zhuǎn)載請見文末要求。
Java日志基礎(chǔ)Java使用了一種自定義的、可擴展的方法來輸出日志。雖然Java通過java.util.logging包提供了一套基本的日志處理API,但你可以很輕松的使用一種或者多種其它日志解決方案。這些解決方案盡管使用不同的方法來創(chuàng)建日志數(shù)據(jù),但它們的最終目標是一樣的,即將日志從你的應(yīng)用程序輸出到目標地址。
在這一節(jié)中,我們會探索Java日志背后的原理,并說明如何通過日志來讓你成為一個更好的Java開發(fā)人員。
Java日志組件Java日志API由以下三個核心組件組成:
當Logger記錄一個事件時,它將事件轉(zhuǎn)發(fā)給適當?shù)腁ppender。然后Appender使用Layout來對日志記錄進行格式化,并將其發(fā)送給控制臺、文件或者其它目標位置。另外,F(xiàn)ilters可以讓你進一步指定一個Appender是否可以應(yīng)用在一條特定的日志記錄上。在日志配置中,F(xiàn)ilters并不是必需的,但可以讓你更靈活地控制日志消息的流動。

在Java中,輸出日志需要使用一個或者多個日志框架,這些框架提供了必要的對象、方法和配置來傳輸消息。Java在java.util.logging包中提供了一個默認的框架。除此之外,還有很多其它第三方框架,包括Log4j、Logback以及tinylog。還有其它一些開發(fā)包,例如SLF4J和Apache Commons Logging,它們提供了一些抽象層,對你的代碼和日志框架進行解耦,從而允許你在不同的日志框架中進行切換。
如何選擇一個日志解決方案,這取決于你的日志需求的復(fù)雜度、和其它日志解決方案的兼容性、易用性以及個人喜好。Logback基于Log4j之前的版本開發(fā)(版本1),因此它們的功能集合都非常類似。然而,Log4j在最新版本(版本2)中引用了一些改進,例如支持多API,并提升了在用Disruptor庫的性能。而tinylog,由于缺少了一些功能,運行特別快,非常適合小項目。
另外一個考慮因素是框架在基于Java的各種不同項目上的支持程度。例如Android程序只能使用Log4j、Logback或者第三方包來記錄日志, Apache Tomcat可以使用Log4j來記錄內(nèi)部消息,但只能使用版本1的Log4j。
抽象層諸如SLF4J這樣的抽象層,會將你的應(yīng)用程序從日志框架中解耦。應(yīng)用程序可以在運行時選擇綁定到一個特定的日志框架(例如java.util.logging、Log4j或者Logback),這通過在應(yīng)用程序的類路徑中添加對應(yīng)的日志框架來實現(xiàn)。如果在類路徑中配置的日志框架不可用,抽象層就會立刻取消調(diào)用日志的相應(yīng)邏輯。抽象層可以讓我們更加容易地改變項目現(xiàn)有的日志框架,或者集成那些使用了不同日志框架的項目。
配置盡管所有的Java日志框架都可以通過代碼進行配置,但是大部分配置還是通過外部配置文件完成的。這些文件決定了日志消息在何時通過什么方式進行處理,日志框架可以在運行時加載這些文件。在這一節(jié)中提供的大部分配置示例都使用了配置文件。
java.util.logging
默認的Java日志框架將其配置存儲到一個名為 logging.PRoperties 的文件中。在這個文件中,每行是一個配置項,配置項使用點標記(dot notation)的形式。Java在其安裝目錄的lib文件夾下面安裝了一個全局配置文件,但在啟動一個Java程序時,你可以通過指定 java.util.logging.config.file 屬性的方式來使用一個單獨的日志配置文件,同樣也可以在個人項目中創(chuàng)建和存儲 logging.properties 文件。
下面的示例描述了如何在全局的logging.properties文件中定義一個Appender:
| 12345 | # default file output is in user's home directory.java.util.logging.FileHandler.pattern = %h/java%u.logjava.util.logging.FileHandler.limit = 50000java.util.logging.FileHandler.count = 1java.util.logging.FileHandler.formatter = java.util.logging.xmlFormatter |
Log4j版本1使用的語法和 java.util.logging 的語法很類似。使用了Log4j的程序會在項目目錄中尋找一個名為 log4j.properties 的文件。默認情況下,Log4j配置會將所有日志消息輸出到控制臺上。Log4j同樣也支持XML格式的配置文件,對應(yīng)的配置信息會存儲到 log4j.xml 文件中。
Log4j版本2支持XML、JSON和YAML格式的配置,這些配置會分別存儲到 log4j2.xml、log4j2.json 和 log4j2.yaml 文件中。和版本1類似,版本2也會在工程目錄中尋找這些文件。你可以在每個版本的文檔中找到相應(yīng)的配置文件示例。
Logback對于Logback來說,大部分配置都是在logback.xml文件中完成的,這個文件使用了和Log4j類似的XML語法。Logback同時也支持通過Groovy語言的方式來進行配置,配置信息會存儲到logback.groovy文件中。你可以通過每種類型配置文件的鏈接找到對應(yīng)的配置文件示例。
LoggersLoggers是用來觸發(fā)日志事件的對象,在我們的Java應(yīng)用程序中被創(chuàng)建和調(diào)用,然后Loggers才會將事件傳遞給Appender。一個類中可以包含針對不同事件的多個獨立的Loggers,你也可以在一個Loggers里面內(nèi)嵌一個Loggers,從而創(chuàng)建一種Loggers層次結(jié)構(gòu)。
創(chuàng)建新Logger在不同的日志框架下面創(chuàng)建新Logger過程大同小異,盡管調(diào)用的具體方法名稱可能不同。在使用 java.util.logging 時,你可以通過 Logger.getLogger().getLogger() 方法創(chuàng)建新Logger,這個方法接收一個string參數(shù),用于指定Logger的名字。如果指定名字的Logger已經(jīng)存在,那么只需要返回已經(jīng)存在的Logger;否則,程序會創(chuàng)建一個新Logger。通常情況下,一種好的做法是,我們在當前類下使用 class.getName() 作為新Logger的名字。
| 1 | Logger logger = Logger.getLogger(MyClass.class.getName()); |
Logger提供了幾種方法來觸發(fā)日志事件。然而,在你記錄一個事件之前,你還需要設(shè)置級別。日志級別用來確定日志的嚴重程度,它可以用來過濾日志事件或者將其發(fā)送給不同的Appender(想了解更多信息,請參考“日志級別”一節(jié)),Logger.log() 方法除了日志消息以外,還需要一個日志級別作為參數(shù):
| 1 | logger.log(Level.WARNING, “This is a warning!”); |
大部分日志框架都針對輸出特定級別日志提供了快捷方式。例如,下面語句的作用和上面語句的作用是一樣的:
| 1 | logger.warning(“This is a warning!”); |
你還可以阻止Logger輸出低于指定日志級別的消息。在下面的示例中,Logger只能輸出高于WARNING級別的日志消息,并丟棄日志級別低于WARNING的消息:
| 1 | logger.setLevel(Level.WARNING); |
我們還有另外一些方法可以用來記錄額外的信息。logp()(精確日志)可以讓你指定每條日志記錄的源類(source class)和方法,而 logrb()(使用資源綁定的日志)可以讓你指定用于提取日志消息的資源。entering() 和 exiting() 方法可以讓你記錄方法調(diào)用信息,從而追蹤程序的執(zhí)行過程。
AppendersAppenders將日志消息轉(zhuǎn)發(fā)給期望的輸出。它負責接收日志事件,使用Layout格式化事件,然后將其發(fā)送給對應(yīng)的目標。對于一個日志事件,我們可以使用多個Appenders來將事件發(fā)送到不同的目標位置。例如,我們可以在控制臺上顯示一個簡單的日志事件的同時,將其通過郵件的方式發(fā)送給指定的接收者。
請注意,在java.util.logging中,Appenders被稱作Handlers。
增加Appender大部分日志框架的Appender都會執(zhí)行類似的功能,但在實現(xiàn)方面大相徑庭。如果使用 java.util.logging,你可以使用 Logger.addHandler() 方法將Appender添加到Logger中。例如,下面的代碼添加了一個新的ConsoleHandler,它會將日志輸出到控制臺:
| 1 | logger.addHandler(new ConsoleHandler()); |
一種更常用的添加Appender的方式是使用配置文件。如果使用 java.util.logging,Appenders會定義一個以逗號隔開的列表,下面的示例將日志事件輸出到控制臺和文件:
| 1 | handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler |
如果使用基于XML的配置文件,Appenders會被添加到<Appenders>元素下面,如果使用Log4j,我們可以很容易地添加一個新ConsoleAppender來將日志消息發(fā)送到System.out:
| 123 | <Console name="console" target="SYSTEM_OUT"><PatternLayout pattern="[%p] %t: %m%n" /></Console> |
這一節(jié)描述了一些更通用的Appenders,以及它們在各種日志框架中是如何實現(xiàn)的。
ConsoleAppenderConsoleAppender是最常用的Appenders之一,它只是將日志消息顯示到控制臺上。許多日志框架都將其作為默認的Appender,并且在基本的配置中進行預(yù)配置。例如,在Log4j中ConsoleAppender的配置參數(shù)如下所示。
| 參數(shù) | 描述 |
|---|---|
| filter | 用于決定是否需要使用該Appender來處理日志事件 |
| layout | 用于決定如何對日志記錄進行格式化,默認情況下使用“%m%n”,它會在每一行顯示一條日志記錄 |
| follow | 用于決定Appender是否需要了解輸出(system.out或者system.err)的變化,默認情況是不需要跟蹤這種變化 |
| name | 用于設(shè)置Appender的名字 |
| ignoreExceptions | 用于決定是否需要記錄在日志事件處理過程中出現(xiàn)的異常 |
| target | 用于指定輸出目標位置,默認情況下使用SYSTEM_OUT,但也可以修改成SYSTEM_ERR |
一個完整的Log4j2的配置文件如下所示:
| 12345678910111213 | <?xml version="1.0" encoding="UTF-8"?><Configuration status="warn" name="MyApp"><Appenders><Console name="MyAppender" target="SYSTEM_OUT"><PatternLayout pattern="%m%n"/></Console></Appenders><Loggers><Root level="error"><AppenderRef ref="MyAppender"/></Root></Loggers></Configuration> |
這個配置文件創(chuàng)建了一個名為MyAppender的ConsoleAppender,它使用PatternLayout來對日志事件進行格式化,然后再將其輸出到System.out。<Loggers>元素對定義在程序代碼中的Loggers進行了配置。在這里,我們只配置了一個LoggerConfig,即名為Root的Logger,它會接收哪些日志級別在ERROR以上的日志消息。如果我們使用logger.error()來記錄一個消息,那么它就會出現(xiàn)在控制臺上,就像這樣:
| 1 | An unexpected error occurred. |
你也可以使用Logback實現(xiàn)完全一樣的效果:
| 12345678910 | <configuration><appender name="MyAppender" class="ch.qos.Logback.core.ConsoleAppender"><encoder><pattern>%m%n</pattern></encoder></appender><root level="error"><appender-ref ref="MyAppender" /></root></configuration> |
FileAppenders將日志記錄寫入到文件中,它負責打開、關(guān)閉文件,向文件中追加日志記錄,并對文件進行加鎖,以免數(shù)據(jù)被破壞或者覆蓋。
在Log4j中,如果想創(chuàng)建一個FileAppender,需要指定目標文件的名字,寫入方式是追加還是覆蓋,以及是否需要在寫入日志時對文件進行加鎖:
| 1234567 | ...<Appenders><File name="MyFileAppender" fileName="myLog.log" append="true" locking="true"><PatternLayout pattern="%m%n"/></File></Appenders>... |
這樣我們創(chuàng)建了一個名為MyFileAppender的FileAppender,并且在向文件中追加日志時會對文件進行加鎖操作。
如果使用Logback,你可以同時啟用prudent模式來保證文件的完整性。雖然Prudent模式增加了寫入文件所花費的時間,但它可以保證在多個FileAppender甚至多個Java程序向同一個文件寫入日志時,文件的完整性。
| 12345678910 | ...<appender name="FileAppender" class="ch.qos.Logback.core.FileAppender"><file>myLog.log</file><append>true</append><prudent>true</prudent><encoder><pattern>%m%n</pattern></encoder></appender>... |
SyslogAppenders將日志記錄發(fā)送給本地或者遠程系統(tǒng)的日志服務(wù)。syslog是一個接收日志事件服務(wù),這些日志事件來自操作系統(tǒng)、進程、其它服務(wù)或者其它設(shè)備。事件的范圍可以從診斷信息到用戶登錄硬件失敗等。syslog的事件按照設(shè)備進行分類,它指定了正在記錄的事件的類型。例如,auth facility表明這個事件是和安全以及認證有關(guān)。
Log4j和Logback都內(nèi)置支持SyslogAppenders。在Log4j中,我們創(chuàng)建SyslogAppender時,需要指定syslog服務(wù)監(jiān)聽的主機號、端口號以及協(xié)議。下面的示例演示了如何設(shè)定裝置:
| 12345 | ...<Appenders><Syslog name="SyslogAppender" host="localhost" port="514" protocol="UDP" facility="Auth" /></Appenders>... |
在Logback中,我們可以實現(xiàn)同樣的效果:
| 1234567 | ...<appender name="SyslogAppender" class="ch.qos.Logback.classic.net.SyslogAppender"><sysloGhost>localhost</syslogHost><port>514</port><facility>Auth</facility></appender>... |
我們已經(jīng)介紹了一些經(jīng)常用到的Appenders,還有很多其它Appender。它們添加了新功能或者在其它的一些Appender基礎(chǔ)上實現(xiàn)了新功能。例如,Log4j中的RollingFileAppender擴展了FileAppender,它可以在滿足特定條件時自動創(chuàng)建新的日志文件;SMTPAppender會將日志內(nèi)容以郵件的形式發(fā)送出去;FailoverAppender會在處理日志的過程中,如果一個或者多個Appender失敗,自動切換到其他Appender上。
如果想了解更多關(guān)于其他Appender的信息,可以查看Log4j Appender參考以及Logback Appender參考。
LayoutsLayouts將日志記錄的內(nèi)容從一種數(shù)據(jù)形式轉(zhuǎn)換成另外一種。日志框架為純文本、HTML、syslog、XML、JSON、序列化以及其它日志提供了Layouts。
請注意:在java.util.logging中Layouts也被稱為Formatters。
例如,java.util.logging提供了兩種Layouts:SimpleFormatter和XMLFormatter。默認情況下,ConsoleHandlers使用SimpleFormatter,它輸出的純文本日志記錄就像這樣:
| 12 | Mar 31, 2015 10:47:51 AM MyClass mainSEVERE: An exception occurred. |
而默認情況下,F(xiàn)ileHandlers使用XMLFormatter,它的輸出就像這樣:
| 123456789101112131415 | <?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE log SYSTEM "logger.dtd"><log><record><date>2015-03-31T10:47:51</date><millis>1427903275893</millis><sequence>0</sequence><logger>MyClass</logger><level>SEVERE</level><class>MyClass</class><method>main</method><thread>1</thread><message>An exception occurred.</message></record></log> |
我們通常使用配置文件對Layouts進行配置。從Java 7開始,我們也可以使用system property來配置SimpleFormatter。
例如,在Log4j和Logback中最常用的Layouts是PatternLayout。它可以讓你決定日志事件中的哪些部分需要輸出,這是通過轉(zhuǎn)換模式(Conversion Pattern)完成的,轉(zhuǎn)換模式在每一條日志事件的數(shù)據(jù)中扮演了“占位符”的角色。例如,Log4j默認的PatternLayout使用了如下轉(zhuǎn)換模式:
| 1 | <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> |
%d{HH:mm:ss.SSS} 將日期轉(zhuǎn)換成時、分、秒和毫秒的形式,%level顯示日志事件的嚴重程度,%C顯示生成日志事件的類的名字,%t顯示Logger的當前線程,%m顯示時間的消息,最后,%n為下一個日志事件進行了換行。
改變Layouts如果在java.util.logging中使用一個不同的Layout,需要將Appender的formatter屬性設(shè)置成你想要的Layout。在代碼中,你可以創(chuàng)建一個新的Handler,調(diào)用setFormatter方法,然后通過logger.AddHandler()方法將Handler放到Logger上面。下面的示例創(chuàng)建了一個ConsoleAppender,它使用XMLFormatter來對日志進行格式化,而不是使用默認的SimpleFormatter:
| 123 | Handler ch = new ConsoleHandler();ch.setFormatter(new XMLFormatter());logger.addHandler(ch); |
這樣Logger會將下面的信息輸出到控制臺上:
| 1234567891011121314 | <?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE log SYSTEM "logger.dtd"><log><record><date>2015-03-31T10:47:51</date><millis>1427813271000</millis><sequence>0</sequence><logger>MyClass</logger><level>SEVERE</level><class>MyClass</class><method>main</method><thread>1</thread><message>An exception occurred.</message></record> |
如果想了解更多信息,你可以查看Log4j Layouts參考以及Logback Layouts參考。
使用自定義Layouts自定義Layouts可以讓你指定Appender應(yīng)該如何輸出日志記錄。從Java SE 7開始,盡管你可以調(diào)整SimpleLogger的輸出,但有一個限制,即只能夠調(diào)整簡單的純文本消息。對于更高級的格式,例如HTML或者JSON,你需要一個自定義Layout或者一個單獨的框架。
如果想了解更多使用java.util.logging創(chuàng)建自定義Layouts的信息,你可以查看Jakob Jenkov的Java日志指南中的Java Logging: Formatters章節(jié)。
日志級別日志級別提供了一種方式,我們可以用它來根據(jù)嚴重程度對日志進行分類和識別。java.util.logging 按照嚴重程度從重到輕,提供了以下級別:
另外, 還有兩個日志級別:ALL和OFF。ALL會讓Logger輸出所有消息,而OFF則會關(guān)閉日志功能。
設(shè)置日志級別在設(shè)定日志級別后,Logger會自動忽略那些低于設(shè)定級別的日志消息。例如,下面的語句會讓Logger忽略那些低于WARNING級別的日志消息:
| 1 | logger.setLevel(Level.WARNING); |
然后,Logger會記錄任何WARNING或者更高級別的日志消息。我們也可以在配置文件中設(shè)置Logger的日志級別:
| 1234 | ...<Loggers><Logger name="MyLogger" level="warning">... |
Log4j和Logback中的PatternLayout類都支持轉(zhuǎn)換模式,它決定了我們?nèi)绾螐拿恳粭l日志事件中提取信息以及如何對信息進行格式化。下面顯示了這些模式的一個子集,對于Log4j和Logback來說,雖然這些特定的字段都是一樣的,但是并不是所有的字段都會使用相同的模式。想要了解更多信息,可以查看Log4j和Logback的PatternLayout文檔。
| 字段名稱 | Log4j/Logback 模式 |
|---|---|
| 消息 | %m |
| 級別/嚴重程度 | %p |
| 異常 | %ex |
| 線程 | %t |
| Logger | %c |
| 方法 | %M |
例如,下面的PatternLayout會在中括號內(nèi)x顯示日志級別,后面是線程名字和日志事件的消息:
| 1 | [%p] %t: %m |
下面是使用了上述轉(zhuǎn)換模式后的日志輸出示例:
| 12 | [INFO] main: initializing worker threads[DEBUG] worker: listening on port 12222[INFO] worker: received request from 192.168.1.200[ERROR] worker: unknown request ID from 192.168.1.200 |
如果你在Java程序中使用過異常,那么很有可能已經(jīng)看到過棧跟蹤信息。它提供了一個程序中方法調(diào)用的快照,讓你準確定位程序執(zhí)行的位置。例如,下面的棧跟蹤信息是程序試圖打開一個不存在的文件后生成的:
| 123456 | [ERROR] main: Unable to open file! java.io.FileNotFoundException: foo.file (No such file or directory)at java.io.FileInputStream.open(Native Method) ~[?:1.7.0_79]at java.io.FileInputStream.<init>(FileInputStream.java:146) ~[?:1.7.0_79]at java.io.FileInputStream.<init>(FileInputStream.java:101) ~[?:1.7.0_79]at java.io.FileReader.<init>(FileReader.java:58) ~[?:1.7.0_79]at FooClass.main(FooClass.java:47) |
這個示例使用了一個名為FooClass的類,它包含一個main方法。在程序第47行,F(xiàn)ileReader獨享試圖打開一個名為foo.file的文件,由于在程序目錄下沒有名字是foo.file的文件,因此Java虛擬機拋出了一個FileNotFoundException。因為這個方法調(diào)用被放到了try-catch語塊中,所以我們能夠捕獲這個異常并記錄它,或者至少可以阻止程序崩潰。
使用PatternLayout記錄棧跟蹤信息在寫本篇文章時最新版本的Log4j和Logback中,如果在Layout中沒有和可拋異常相關(guān)的信息,那么都會自動將%xEx(這種棧跟蹤信息包含了每次方法調(diào)用的包信息)添加到PatternLayout中。如果對于普通的日志信息的模式如下:
新聞熱點
疑難解答