Java日志API管理最佳實(shí)踐詳解
概述
對(duì)于現(xiàn)在的應(yīng)用程序來說,日志的重要性是不言而喻的。很難想象沒有任何日志記錄功能的應(yīng)用程序運(yùn)行在生產(chǎn)環(huán)境中。日志所能提供的功能是多種多樣的,包括記錄程序運(yùn)行時(shí)產(chǎn)生的錯(cuò)誤信息、狀態(tài)信息、調(diào)試信息和執(zhí)行時(shí)間信息等。在生產(chǎn)環(huán)境中,日志是查找問題來源的重要依據(jù)。應(yīng)用程序運(yùn)行時(shí)的產(chǎn)生的各種信息,都應(yīng)該通過日志 API 來進(jìn)行記錄。
很多開發(fā)人員習(xí)慣于使用 System.out.println、System.err.println 以及異常對(duì)象的 printStrackTrace 方法來輸出相關(guān)信息。這些使用方式雖然簡便,但是所產(chǎn)生的信息在出現(xiàn)問題時(shí)并不能提供有效的幫助。這些使用方式都應(yīng)該改為使用日志 API。使用日志 API 并沒有增加很多復(fù)雜度,但是所提供的好處是顯著的。
盡管記錄日志是應(yīng)用開發(fā)中并不可少的功能,在 JDK 的最初版本中并不包含日志記錄相關(guān)的 API 和實(shí)現(xiàn)。相關(guān)的 API(java.util.logging 包,JUL)和實(shí)現(xiàn),直到 JDK 1.4 才被加入。因此在日志記錄這一個(gè)領(lǐng)域,社區(qū)貢獻(xiàn)了很多開源的實(shí)現(xiàn)。其中比較流行的包括 log4j 及其后繼者 logback。除了真正的日志記錄實(shí)現(xiàn)之外,還有一類與日志記錄相關(guān)的封裝 API,如 Apache Commons Logging 和 SLF4J。
這類庫的作用是在日志記錄實(shí)現(xiàn)的基礎(chǔ)上提供一個(gè)封裝的 API 層次,對(duì)日志記錄 API 的使用者提供一個(gè)統(tǒng)一的接口,使得可以自由切換不同的日志記錄實(shí)現(xiàn)。比如從 JDK 的默認(rèn)日志記錄實(shí)現(xiàn) JUL 切換到 log4j。這類封裝 API 庫在框架的實(shí)現(xiàn)中比較常用,因?yàn)樾枰紤]到框架使用者的不同需求。在實(shí)際的項(xiàng)目開發(fā)中則使用得比較少,因?yàn)楹苌儆许?xiàng)目會(huì)在開發(fā)中切換不同的日志記錄實(shí)現(xiàn)。本文對(duì)于這兩類庫都會(huì)進(jìn)行具體的介紹。
記錄日志只是有效地利用日志的第一步,更重要的是如何對(duì)程序運(yùn)行時(shí)產(chǎn)生的日志進(jìn)行處理和分析。典型的情景包括當(dāng)日志中包含滿足特定條件的記錄時(shí),觸發(fā)相應(yīng)的通知機(jī)制,比如郵件或短信通知;還可以在程序運(yùn)行出現(xiàn)錯(cuò)誤時(shí),快速地定位潛在的問題源。這樣的處理和分析的能力對(duì)于實(shí)際系統(tǒng)的維護(hù)尤其重要。當(dāng)運(yùn)行系統(tǒng)中包含的組件過多時(shí),日志對(duì)于錯(cuò)誤的診斷就顯得格外重要。
本文首先介紹關(guān)于日志 API 的基本內(nèi)容。
Java 日志 API
從功能上來說,日志 API 本身所需求的功能非常簡單,只需要能夠記錄一段文本即可。API 的使用者在需要進(jìn)行記錄時(shí),根據(jù)當(dāng)前的上下文信息構(gòu)造出相應(yīng)的文本信息,調(diào)用 API 完成記錄。一般來說,日志 API 由下面幾個(gè)部分組成:
- 記錄器(Logger):日志 API 的使用者通過記錄器來發(fā)出日志記錄請(qǐng)求,并提供日志的內(nèi)容。在記錄日志時(shí),需要指定日志的嚴(yán)重性級(jí)別。
- 格式化器(Formatter):對(duì)記錄器所記錄的文本進(jìn)行格式化,并添加額外的元數(shù)據(jù)。
- 處理器(Handler):把經(jīng)過格式化之后的日志記錄輸出到不同的地方。常見的日志輸出目標(biāo)包括控制臺(tái)、文件和數(shù)據(jù)庫等。
記錄器
當(dāng)程序中需要記錄日志時(shí),首先需要獲取一個(gè)日志記錄器對(duì)象。一般的日志記錄 API 都提供相應(yīng)的工廠方法來創(chuàng)建記錄器對(duì)象。每個(gè)記錄器對(duì)象都是有名稱的。一般的做法是使用當(dāng)前的 Java 類的名稱或所在包的名稱作為記錄器對(duì)象的名稱。記錄器的名稱通常是具有層次結(jié)構(gòu)的,與 Java 包的層次結(jié)構(gòu)相對(duì)應(yīng)。比如 Java 類“com.myapp.web.IndexController”中使用的日志記錄器的名稱一般是“com.myapp.web.IndexController”或“com.myapp.web”。除了使用類名或包名之外,還可以根據(jù)日志記錄所對(duì)應(yīng)的功能來進(jìn)行劃分,從而選擇不同的名稱。
比如用“security”作為所有與安全相關(guān)的日志記錄器的名稱。這樣的命名方式對(duì)于某些橫切的功能比較實(shí)用。開發(fā)人員一般習(xí)慣于使用當(dāng)前的類名作為日志記錄器的名稱,這樣可以快速在日志記錄中定位到產(chǎn)生日志的 Java 類。使用有意義的其他名稱在很多情況下也是一個(gè)不錯(cuò)的選擇。
在通過日志記錄器對(duì)象記錄日志時(shí),需要指定日志的嚴(yán)重性級(jí)別。根據(jù)每個(gè)記錄器對(duì)象的不同配置,低于某個(gè)級(jí)別的日志消息可能不會(huì)被記錄下來。該級(jí)別是日志 API 的使用者根據(jù)日志記錄中所包含的信息來自行決定的。不同的日志記錄 API 所定義的級(jí)別也不盡相同。日志記錄封裝 API 也會(huì)定義自己的級(jí)別并映射到底層實(shí)現(xiàn)中相對(duì)應(yīng)的實(shí)際級(jí)別。
比如 JDK 標(biāo)準(zhǔn)的日志 API 使用的級(jí)別包括 OFF、SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST 和 ALL 等,Log4j 使用的級(jí)別則包括 OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE 和 ALL 等。一般情況下,使用得比較多的級(jí)別是 FATAL、ERROR、WARN、INFO、DEBUG 和 TRACE 等。這 6 個(gè)級(jí)別所對(duì)應(yīng)的情況也有所不同:
- FATAL:導(dǎo)致程序提前結(jié)束的嚴(yán)重錯(cuò)誤。
- ERROR:運(yùn)行時(shí)異常以及預(yù)期之外的錯(cuò)誤。
- WARN:預(yù)期之外的運(yùn)行時(shí)狀況,不一定是錯(cuò)誤的情況。
- INFO:運(yùn)行時(shí)產(chǎn)生的事件。
- DEBUG:與程序運(yùn)行時(shí)的流程相關(guān)的詳細(xì)信息。
- TRACE:更加具體的詳細(xì)信息。
在這 6 個(gè)級(jí)別中,以 ERROR、WARN、INFO 和 DEBUG 作為常用。
日志記錄 API 的使用者通過記錄器來記錄日志消息。日志消息在記錄下來之后只能以文本的形式保存。不過有的實(shí)現(xiàn)(如 Log4j)允許在記錄日志時(shí)使用任何 Java 對(duì)象。非 String 類型的對(duì)象會(huì)被轉(zhuǎn)換成 String 類型。由于日志記錄通常在出現(xiàn)異常時(shí)使用,記錄器在記錄消息時(shí)可以把產(chǎn)生的異常(Throwable 類的對(duì)象)也記錄下來。
每個(gè)記錄器對(duì)象都有一個(gè)運(yùn)行時(shí)對(duì)應(yīng)的嚴(yán)重性級(jí)別。該級(jí)別可以通過配置文件或代碼的方式來進(jìn)行設(shè)置。如果沒有顯式指定嚴(yán)重性級(jí)別,則會(huì)根據(jù)記錄器名稱的層次結(jié)構(gòu)關(guān)系往上進(jìn)行查找,直到找到一個(gè)設(shè)置了嚴(yán)重性級(jí)別的名稱為止。比如名稱為“com.myapp.web.IndexController”的記錄器對(duì)象,如果沒有顯式指定其嚴(yán)重性級(jí)別,則會(huì)依次查找是否有為名稱“com.myapp.web”、“com.myapp”和“com”指定的嚴(yán)重性級(jí)別。如果仍然沒有找到,則使用根記錄器配置的值。
通過記錄器對(duì)象來記錄日志時(shí),只是發(fā)出一個(gè)日志記錄請(qǐng)求。該請(qǐng)求是否會(huì)完成取決于請(qǐng)求和記錄器對(duì)象的嚴(yán)重性級(jí)別。記錄器使用者產(chǎn)生的低于記錄器對(duì)象嚴(yán)重性級(jí)別的日志消息不會(huì)被記錄下來。這樣的記錄請(qǐng)求會(huì)被忽略。除了基于嚴(yán)重性級(jí)別的過濾方式之外,日志記錄框架還支持其他自定義的過濾方式。比如 JUL 可以通過實(shí)現(xiàn) java.util.logging.Filter 接口的方式來進(jìn)行過濾。Log4j 可以通過繼承 org.apache.log4j.spi.Filter 類的方式來過濾。
格式化器
實(shí)際記錄的日志中除了使用記錄器對(duì)象時(shí)提供的消息之外,還包括一些元數(shù)據(jù)。這些元數(shù)據(jù)由日志記錄框架來提供。常用的信息包括記錄器的名稱、時(shí)間戳、線程名等。格式化器用來確定所有這些信息在日志記錄中的展示方式。不同的日志記錄實(shí)現(xiàn)提供各自默認(rèn)的格式化方式和自定義支持。
JUL 中通過繼承 java.util.logging.Formatter 類來自定義格式化的方式,并提供了兩個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn) SimpleFormatter 類和 XMLFormatter 類。清單 1 中給出了 JUL 中自定義格式化器的實(shí)現(xiàn)方式,只需要繼承自 Formatter 類并實(shí)現(xiàn) format 方法即可。參數(shù) LogRecord 類的對(duì)象中包含了日志記錄中的全部信息。
清單 1. JUL 中自定義格式化器的實(shí)現(xiàn)
public class CustomFormatter extends Formatter { public String format(LogRecord record) { return String.format("<%s> [%s] : %s", new Date(record.getMillis()), record.getLoggerName(), record.getMessage()); } }
對(duì)于自定義的格式化器類,需要在 JUL 的配置文件中進(jìn)行指定,如清單 2 所示。
清單 2. 在 JUL 配置文件中指定自定義的格式化器類
java.util.logging.ConsoleHandler.formatter = logging.jul.CustomFormatter
Log4j 在格式化器的實(shí)現(xiàn)上要簡單一些,由 org.apache.log4j.PatternLayout 類來負(fù)責(zé)完成日志記錄的格式化。在自定義時(shí)不需要?jiǎng)?chuàng)建新的 Java 類,而是通過配置文件指定所需的格式化模式。在格式化模式中,不同的占位符表示不同類型的信息。比如“%c”表示記錄器的名稱,“%d”表示日期,“%m”表示日志的消息文本,“%p”表示嚴(yán)重性級(jí)別,“%t”表示線程的名稱。清單 3 給出了 Log4j 配置文件中日志記錄的自定義方式。
清單 3. Log4j 中日志記錄的自定義方式
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%p] %c - %m%n
日志處理器
日志記錄經(jīng)過格式化之后,由不同的處理器來進(jìn)行處理。不同的處理器有各自不同的處理方式。比如控制臺(tái)處理器會(huì)把日志輸出到控制臺(tái)中,文件處理器把日志寫入到文件中。除了這些之外,還有寫入到數(shù)據(jù)庫、通過郵件發(fā)送、寫入到 JMS 隊(duì)列等各種不同的處理方式。
日志處理器也可以配置所處理日志信息的最低嚴(yán)重性級(jí)別。低于該級(jí)別的日志不會(huì)被處理。這樣可以控制所處理的日志記錄數(shù)量。比如控制臺(tái)處理器的級(jí)別一般設(shè)置為 INFO,而文件處理器則一般設(shè)置為 DEBUG。
日志記錄框架一般提供了比較多的日志處理器實(shí)現(xiàn)。開發(fā)人員也可以創(chuàng)建自定義的實(shí)現(xiàn)。
Java 日志封裝 API
除了 JUL 和 log4j 這樣的日志記錄庫之外,還有一類庫用來封裝不同的日志記錄庫。這樣的封裝庫中一開始以 Apache Commons Logging 框架最為流行,現(xiàn)在比較流行的是 SLF4J。這樣封裝庫的 API 都比較簡單,只是在日志記錄庫的 API 基礎(chǔ)上做了一層簡單的封裝,屏蔽不同實(shí)現(xiàn)之間的區(qū)別。由于日志記錄實(shí)現(xiàn)所提供的 API 大致上比較相似,封裝庫的作用更多的是達(dá)到語法上的一致性。
在 Apache Commons Logging 庫中,核心的 API 是 org.apache.commons.logging.LogFactory 類和 org.apache.commons.logging.Log 接口。LogFactory 類提供了工廠方法用來創(chuàng)建 Log 接口的實(shí)現(xiàn)對(duì)象。比如 LogFactory.getLog 可以根據(jù) Java 類或名稱來創(chuàng)建 Log 接口的實(shí)現(xiàn)對(duì)象。Log 接口中為 6 個(gè)不同的嚴(yán)重性級(jí)別分別定義了一組方法。比如對(duì) DEBUG 級(jí)別,定義了 isDebugEnabled()、debug(Object message) 和 debug(Object message, Throwable t) 三個(gè)方法。從這個(gè)層次來說,Log 接口簡化了對(duì)于日志記錄器的使用。
SLF4J 庫的使用方式與 Apache Commons Logging 庫比較類似。SLF4J 庫中核心的 API 是提供工廠方法的 org.slf4j.LoggerFactory 類和記錄日志的 org.slf4j.Logger 接口。通過 LoggerFactory 類的 getLogger 方法來獲取日志記錄器對(duì)象。與 Apache Commons Logging 庫中的 Log 接口類似,Logger 接口中的方法也是按照不同的嚴(yán)重性級(jí)別來進(jìn)行分組的。Logger 接口中有同樣 isDebugEnabled 方法。不過 Logger 接口中發(fā)出日志記錄請(qǐng)求的 debug 等方法使用 String 類型來表示消息,同時(shí)可以使用包含參數(shù)的消息,如清單 4 所示。
清單 4. SLF4J 的使用方式
public class Slf4jBasic { private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jBasic.class); public void logBasic() { if (LOGGER.isInfoEnabled()) { LOGGER.info("My log message for %s", "Alex"); } } }
MDC
MDC(Mapped Diagnostic Context,映射調(diào)試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。某些應(yīng)用程序采用多線程的方式來處理多個(gè)用戶的請(qǐng)求。在一個(gè)用戶的使用過程中,可能有多個(gè)不同的線程來進(jìn)行處理。典型的例子是 Web 應(yīng)用服務(wù)器。當(dāng)用戶訪問某個(gè)頁面時(shí),應(yīng)用服務(wù)器可能會(huì)創(chuàng)建一個(gè)新的線程來處理該請(qǐng)求,也可能從線程池中復(fù)用已有的線程。在一個(gè)用戶的會(huì)話存續(xù)期間,可能有多個(gè)線程處理過該用戶的請(qǐng)求。這使得比較難以區(qū)分不同用戶所對(duì)應(yīng)的日志。當(dāng)需要追蹤某個(gè)用戶在系統(tǒng)中的相關(guān)日志記錄時(shí),就會(huì)變得很麻煩。
一種解決的辦法是采用自定義的日志格式,把用戶的信息采用某種方式編碼在日志記錄中。這種方式的問題在于要求在每個(gè)使用日志記錄器的類中,都可以訪問到用戶相關(guān)的信息。這樣才可能在記錄日志時(shí)使用。這樣的條件通常是比較難以滿足的。MDC 的作用是解決這個(gè)問題。
MDC 可以看成是一個(gè)與當(dāng)前線程綁定的哈希表,可以往其中添加鍵值對(duì)。MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問。當(dāng)前線程的子線程會(huì)繼承其父線程中的 MDC 的內(nèi)容。當(dāng)需要記錄日志時(shí),只需要從 MDC 中獲取所需的信息即可。MDC 的內(nèi)容則由程序在適當(dāng)?shù)臅r(shí)候保存進(jìn)去。對(duì)于一個(gè) Web 應(yīng)用來說,通常是在請(qǐng)求被處理的最開始保存這些數(shù)據(jù)。清單 5 中給出了 MDC 的使用示例。
清單 5. MDC 使用示例
public class MdcSample { private static final Logger LOGGER = Logger.getLogger("mdc"); public void log() { MDC.put("username", "Alex"); if (LOGGER.isInfoEnabled()) { LOGGER.info("This is a message."); } } }
清單 5 中,在記錄日志前,首先在 MDC 中保存了名稱為“username”的數(shù)據(jù)。其中包含的數(shù)據(jù)可以在格式化日志記錄時(shí)直接引用,如清單 6 所示,“%X{username}”表示引用 MDC 中“username”的值。
清單 6. 使用 MDC 中記錄的數(shù)據(jù)
log4j.appender.stdout.layout.ConversionPattern=%X{username} %d{yyyy-MM-dd HH:mm:ss} [%p] %c - %m%n
日志記錄最佳實(shí)踐
下面主要介紹一些在記錄日志時(shí)的比較好的實(shí)踐。
檢查日志是否可以被記錄
當(dāng)日志記錄器收到一個(gè)日志記錄請(qǐng)求時(shí),如果請(qǐng)求的嚴(yán)重性級(jí)別低于記錄器對(duì)象的實(shí)際有效級(jí)別,則該請(qǐng)求會(huì)被忽略。在日志記錄方法的實(shí)現(xiàn)中會(huì)首先進(jìn)行這樣的檢查。不過推薦的做法是在調(diào)用 API 進(jìn)行記錄之前,首先進(jìn)行相應(yīng)的檢查,這樣可以避免不必要的性能問題,如清單 7 所示。
清單 7. 檢查日志是否可以被記錄
if (LOGGER.isDebugEnabled()) { LOGGER.debug("This is a message."); }
清單 7 中的做法的作用在于避免了構(gòu)造日志記錄消息所帶來的開銷。日志消息中通常包含與當(dāng)前上下文相關(guān)的信息。為了獲取這些信息并構(gòu)造相應(yīng)的消息文本,不可避免會(huì)產(chǎn)生額外的開銷。尤其對(duì)于 DEBUG 和 TRACE 級(jí)別的日志消息來說,它們所出現(xiàn)的頻率很高,累加起來的開銷比較大。因此在記錄 INFO、DEBUG 和 TRACE 級(jí)別的日志時(shí),首先進(jìn)行相應(yīng)的檢查是一個(gè)好的實(shí)踐。而 WARN 及其以上級(jí)別的日志則一般不需要進(jìn)行檢查。
日志中包含充分的信息
日志中所包含的信息應(yīng)該是充分的。在記錄日志消息時(shí)應(yīng)該盡可能多的包含當(dāng)前上下文中的各種信息,以方便在遇到問題時(shí)可以快速的獲取到所需的信息。比如在網(wǎng)上支付功能中,與支付相關(guān)的日志應(yīng)該完整的包含當(dāng)前用戶、訂單以及支付方式等全部信息。一種比較常見的做法是把相關(guān)的日志記錄分散在由不同日志記錄器所記錄的日志中。當(dāng)出現(xiàn)問題之后,需要手工查找并匹配相關(guān)的日志來定位問題,所花費(fèi)的時(shí)間和精力會(huì)更多。因此,應(yīng)該盡可能在一條日志記錄中包含足夠多的信息。
使用合適的日志記錄器名稱
一般的日志記錄實(shí)踐是使用當(dāng)前 Java 類的全名作為其使用的日志記錄器的名稱。這樣做可以得到一個(gè)與 Java 類和包的層次結(jié)構(gòu)相對(duì)應(yīng)的日志記錄器的層次結(jié)構(gòu)??梢院芊奖愕陌凑詹煌哪K來設(shè)置相應(yīng)的日志記錄級(jí)別。
不過對(duì)于某些全局的或是橫切的功能,如安全和性能等,則推薦使用功能相關(guān)的名稱。比如程序中可能包含用來提供性能剖析信息的日志記錄。對(duì)于這樣的日志記錄,應(yīng)該使用同一名稱的日志記錄器,如類似“performance”或“performance.web”。這樣當(dāng)需要啟用和禁用性能剖析時(shí),只需要配置這些名稱的記錄器即可。
使用半結(jié)構(gòu)化的日志消息
在介紹日志記錄 API 中的格式化器時(shí)提到過,日志記錄中除了基本的日志消息之外,還包括由日志框架提供的其他元數(shù)據(jù)。這些數(shù)據(jù)按照給定的格式出現(xiàn)在日志記錄中。這些半結(jié)構(gòu)化的格式使得可以通過工具提取日志記錄中的相關(guān)信息進(jìn)行分析。在使用日志 API 進(jìn)行記錄時(shí),對(duì)于日志消息本身,也推薦使用半結(jié)構(gòu)化的方式來組織。
比如一個(gè)電子商務(wù)的網(wǎng)站,當(dāng)用戶登錄之后,該用戶所產(chǎn)生的不同操作所對(duì)應(yīng)的日志記錄中都可以包含該用戶的用戶名,并以固定的格式出現(xiàn)在日志記錄中,如清單 8 所示。
清單 8. 使用半結(jié)構(gòu)化的日志消息
[user1] 用戶登錄成功。 [user1] 用戶成功購買產(chǎn)品 A。 [user2] 訂單 003 付款失敗。
當(dāng)需要通過日志記錄來排查某個(gè)用戶所遇到的問題時(shí),只需要通過正則表達(dá)就可以很快地查詢到用戶相關(guān)的日志記錄。
日志聚合與分析
在程序中正確的地方輸出合適的日志消息,只是合理使用日志的第一步。日志記錄的真正作用在于當(dāng)有問題發(fā)生時(shí),能夠幫助開發(fā)人員很快的定位問題所在。不過一個(gè)實(shí)用的系統(tǒng)通常由很多個(gè)不同的部分組成。這其中包括所開發(fā)的程序本身,也包括所依賴的第三方應(yīng)用程序。以一個(gè)典型的電子商務(wù)網(wǎng)站為例,除了程序本身,還包括所依賴的底層操作系統(tǒng)、應(yīng)用服務(wù)器、數(shù)據(jù)庫、HTTP 服務(wù)器和代理服務(wù)器和緩存等。當(dāng)一個(gè)問題發(fā)生時(shí),真正的原因可能來自程序本身,也可能來自所依賴的第三方程序。這就意味著開發(fā)人員可能需要檢查不同服務(wù)器上不同應(yīng)用程序的日志來確定真正的原因。
日志聚合的作用就在于可以把來自不同服務(wù)器上不同應(yīng)用程序產(chǎn)生的日志聚合起來,存放在單一的服務(wù)器上,方便進(jìn)行搜索和分析。在日志聚合方面,已經(jīng)有不少成熟的開源軟件可以很好的滿足需求。本文中要介紹的是 logstash,一個(gè)流行的事件和日志管理開源軟件。logstash 采用了一種簡單的處理模式:輸入 -> 過濾器 -> 輸出。
logstash 可以作為代理程序安裝到每臺(tái)需要收集日志的機(jī)器上。logstash 提供了非常多的插件來處理不同類型的數(shù)據(jù)輸入。典型的包括控制臺(tái)、文件和 syslog 等;對(duì)于輸入的數(shù)據(jù),可以使用過濾器來進(jìn)行處理。典型的處理方式是把日志消息轉(zhuǎn)換成結(jié)構(gòu)化的字段;過濾之后的結(jié)果可以被輸出到不同的目的地,比如 ElasticSearch、文件、電子郵件和數(shù)據(jù)庫等。
Logstash 在使用起來很簡單。從官方網(wǎng)站下載 jar 包并運(yùn)行即可。在運(yùn)行時(shí)需要指定一個(gè)配置文件。配置文件中定義了輸入、過濾器和輸出的相關(guān)配置。清單 9 給出了一個(gè)簡單的 logstash 配置文件的示例。
清單 9. logstash 配置文件示例
input { file { path => [ "/var/log/*.log", "/var/log/messages", "/var/log/syslog" ] type => 'syslog' } } output { stdout { debug => true debug_format => "json" } }
清單 9 中定義了 logstash 收集日志時(shí)的輸入(input)和輸出(output)的相關(guān)配置。輸入類型是文件(file)。每種類型輸入都有相應(yīng)的配置。對(duì)于文件來說,需要配置的是文件的路徑。對(duì)每種類型的輸入,都需要指定一個(gè)類型(type)。該類型用來區(qū)分來自不同輸入的記錄。代碼中使用的輸出是控制臺(tái)。配置文件完成之后,通過“java -jar logstash-1.1.13-flatjar.jar agent -f logstash-simple.conf”就可以啟動(dòng) logstash。
在日志分析中,比較重要的是結(jié)構(gòu)化的信息。而日志信息通常只是一段文本,其中的不同字段表示不同的含義。不同的應(yīng)用程序產(chǎn)生的日志的格式并不相同。在分析時(shí)需要關(guān)注的是其中包含的不同字段。比如 Apache 服務(wù)器會(huì)產(chǎn)生與用戶訪問請(qǐng)求相關(guān)的日志。
在日志中包含了訪問者的各種信息,包括 IP 地址、時(shí)間、HTTP 狀態(tài)碼、響應(yīng)內(nèi)容的長度和 User Agent 字符串等信息。在 logstash 收集到日志信息之后,可以根據(jù)一定的規(guī)則把日志信息中包含的數(shù)據(jù)提取出來并命名。logstash 提供了 grok 插件可以完成這樣的功能。grok 基于正則表達(dá)式來工作,同時(shí)提供了非常多的常用類型數(shù)據(jù)的提取模式,如清單 10 所示。
清單 10. 使用 grok 提取日志記錄中的內(nèi)容
//Apache 訪問日志 49.50.214.136 GET /index.html 200 1150 "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17" //grok 提取模式 %{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:status} %{NUMBER:bytes} %{QS:useragent}
在經(jīng)過上面 grok 插件的提取之后,Apache 訪問日志被轉(zhuǎn)換成包含字段 client、method、request、status、bytes 和 useragent 的格式化數(shù)據(jù)??梢愿鶕?jù)這些字段來進(jìn)行搜索。這對(duì)于分析問題和進(jìn)行統(tǒng)計(jì)都是很有幫助的。
當(dāng)日志記錄通過 logstash 進(jìn)行收集和處理之后,通常會(huì)把這些日志記錄保存到數(shù)據(jù)庫中進(jìn)行分析和處理。目前比較流行的方式是保存到 ElasticSearch 中,從而可以利用 ElasticSearch 提供的索引和搜索能力來分析日志。已經(jīng)有不少的開源軟件在 ElasticSearch 基礎(chǔ)之上開發(fā)出相應(yīng)的日志管理功能,可以很方便的進(jìn)行搜索和分析。本文中介紹的是 Graylog2。
Graylog2 由服務(wù)器和 Web 界面兩部分組成。服務(wù)器負(fù)責(zé)接收日志記錄并保存到 ElasticSearch 之中。Web 界面則可以查看和搜索日志,并提供其他的輔助功能。logstash 提供了插件 gelf,可以把 logstash 收集和處理過的日志記錄發(fā)送到 Graylog2 的服務(wù)器。這樣就可以利用 Graylog2 的 Web 界面來進(jìn)行查詢和分析。只需要把清單 9 中的 logstash 的配置文件中的 output 部分改成清單 11 中所示即可。
清單 11. 配置 logstash 輸出到 Graylog2
output { gelf { host => '127.0.0.1' } }
在安裝 Graylog2 時(shí)需要注意,一定要安裝與 Graylog2 的版本相對(duì)應(yīng)的版本的 ElasticSearch,否則會(huì)出現(xiàn)日志記錄無法保存到 ElasticSearch 的問題。本文中使用的是 Graylog2 服務(wù)器 0.11.0 版本和 ElasticSearch 0.20.4 版本。
除了 Graylog2 之外,另外一個(gè)開源軟件 Kibana 也比較流行。Kibana 可以看成是 logstash 和 ElasticSearch 的 Web 界面。Kibana 提供了更加豐富的功能來顯示和分析日志記錄。與代碼清單中的 logstash 的配置相似,只需要把輸出改為 elasticsearch 就可以了。Kibana 可以自動(dòng)讀取 ElasticSearch 中包含的日志記錄并顯示。
小結(jié)
日志記錄是應(yīng)用程序開發(fā)中的重要一環(huán)。不過這一環(huán)比較容易被開發(fā)人員忽視,因?yàn)樗a(chǎn)生的影響在程序運(yùn)行和維護(hù)時(shí)。對(duì)于一個(gè)生產(chǎn)系統(tǒng)來說,日志記錄的重要性是不言而喻的。本文首先以 java.util.logging 包和 log4j 為例介紹了 Java 日志 API 的主要組成部分和使用方式,同時(shí)也介紹了 Apache Commons Logging 和 SLF4J 兩種日志封裝 API。本文也給出了一些記錄日志時(shí)應(yīng)該采用的最佳實(shí)踐。最后介紹了如何使用開源工具對(duì)日志進(jìn)行聚合和分析。通過本文,開發(fā)人員可以了解如何在開發(fā)中有效的使用日志。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot運(yùn)行到dokcer中 dockerfile的場景分析
這篇文章主要介紹了springboot運(yùn)行到dokcer中 dockerfile,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03java開發(fā)BeanUtils類解決實(shí)體對(duì)象間賦值
這篇文章主要為大家介紹了java開發(fā)中使用BeanUtils類實(shí)現(xiàn)實(shí)體對(duì)象之間的賦值有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步學(xué)有所得2021-10-10Java編程實(shí)現(xiàn)比對(duì)兩個(gè)文本文件并標(biāo)記相同與不同之處的方法
這篇文章主要介紹了Java編程實(shí)現(xiàn)比對(duì)兩個(gè)文本文件并標(biāo)記相同與不同之處的方法,涉及java針對(duì)文本文件的讀取、遍歷、判斷等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10Java框架學(xué)習(xí)Struts2復(fù)選框?qū)嵗a
這篇文章主要介紹了Java框架學(xué)習(xí)Struts2復(fù)選框?qū)嵗a,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02