偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Java日志終極指南

開發(fā) 后端
Java使用了一種自定義的、可擴展的方法來輸出日志。雖然Java通過java.util.logging包提供了一套基本的日志處理API,但你可以很輕松的使用一種或者多種其它日志解決方案。這些解決方案盡管使用不同的方法來創(chuàng)建日志數(shù)據(jù),但它們的最終目標是一樣的,即將日志從你的應用程序輸出到目標地址。

 

Java日志基礎

Java使用了一種自定義的、可擴展的方法來輸出日志。雖然Java通過java.util.logging包提供了一套基本的日志處理API,但你可以很輕松的使用一種或者多種其它日志解決方案。這些解決方案盡管使用不同的方法來創(chuàng)建日志數(shù)據(jù),但它們的最終目標是一樣的,即將日志從你的應用程序輸出到目標地址。

在這一節(jié)中,我們會探索Java日志背后的原理,并說明如何通過日志來讓你成為一個更好的Java開發(fā)人員。

 

 

 

 

Java日志組件

Java日志API由以下三個核心組件組成:

  • Loggers:Logger負責捕捉事件并將其發(fā)送給合適的Appender。

  • Appenders:也被稱為Handlers,負責將日志事件記錄到目標位置。在將日志事件輸出之前,Appenders使用Layouts來對事件進行格式化處理。

  • Layouts:也被稱為Formatters,它負責對日志事件中的數(shù)據(jù)進行轉(zhuǎn)換和格式化。Layouts決定了數(shù)據(jù)在一條日志記錄中的最終形式。

當Logger記錄一個事件時,它將事件轉(zhuǎn)發(fā)給適當?shù)腁ppender。然后Appender使用Layout來對日志記錄進行格式化,并將其發(fā)送給控制臺、文件或者其它目標位置。另外,F(xiàn)ilters可以讓你進一步指定一個Appender是否可以應用在一條特定的日志記錄上。在日志配置中,F(xiàn)ilters并不是必需的,但可以讓你更靈活控制日志消息的流動。

Java日志***指南

 

 

 

 

日志框架

在Java中,輸出日志需要使用一個或者多個日志框架,這些框架提供了必要的對象、方法和配置來傳輸消息。Java在java.util.logging包中提供了一個默認的框架。除此之外,還有很多其它第三方框架,包括Log4j、Logback以及tinylog。還有其它一些開發(fā)包,例如SLF4JApache Commons Logging,它們提供了一些抽象層,對你的代碼和日志框架進行解耦,從而允許你在不同的日志框架中進行切換。

如何選擇一個日志解決方案,這取決于你的日志需求的復雜度、和其它日志解決方案的兼容性、易用性以及個人喜好。Logback基于Log4j之前的版本開發(fā)(版本1),因此它們的功能集合都非常類似。然而,Log4j在***版本(版本2)中引用了一些改進,例如支持多API,并提升了在用Disruptor庫的性能。而tinylog,由于缺少了一些功能,運行特別快,非常適合小項目。

另外一個考慮因素是框架在基于Java的各種不同項目上的支持程度。例如Android程序只能使用Log4j、Logback或者第三方包來記錄日志, Apache Tomcat可以使用Log4j來記錄內(nèi)部消息,但只能使用版本1的Log4j。

抽象層

諸如SLF4J這樣的抽象層,會將你的應用程序從日志框架中解耦。應 用程序可以在運行時選擇綁定到一個特定的日志框架(例如java.util.logging、Log4j或者Logback),這通過在應用程序的類路徑 中添加對應的日志框架來實現(xiàn)。如果在類路徑中配置的日志框架不可用,抽象層就會立刻取消調(diào)用日志的相應邏輯。抽象層可以讓我們更加容易地改變項目現(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:

# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XmlFormatter

Log4j

Log4j版本1使 用的語法和 java.util.logging 的語法很類似。使用了Log4j的程序會在項目目錄中尋找一個名為 log4j.properties 的文件。默認情況下,Log4j配置會將所有日志消息輸出到控制臺上。Log4j同樣也支持XML格式的配置文件,對應的配置信息會存儲到 log4j.xml 文件中。

Log4j版本2支持XML、JSON和YAML格式的配置,這些配置會分別存儲到 log4j2.xml、log4j2.json 和 log4j2.yaml 文件中。和版本1類似,版本2也會在工程目錄中尋找這些文件。你可以在每個版本的文檔中找到相應的配置文件示例。

Logback

對于Logback來說,大部分配置都是在 logback.xml 文件中完成的,這個文件使用了和Log4j類似的XML語法。Logback同時也支持通過Groovy語言的方式來進行配置,配置信息會存儲到 logback.groovy 文件中。你可以通過每種類型配置文件的鏈接找到對應的配置文件示例。

 

 

 

 

Loggers

Loggers是用來觸發(fā)日志事件的對象,在我們的Java應用程序中被創(chuàng)建和調(diào)用,然后Loggers才會將事件傳遞給Appender。一個類 中可以包含針對不同事件的多個獨立的Loggers,你也可以在一個Loggers里面內(nèi)嵌一個Loggers,從而創(chuàng)建一種Loggers層次結(jié)構

創(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的名字。

Logger logger = Logger.getLogger(MyClass.class.getName());

記錄日志事件

Logger提供了幾種方法來觸發(fā)日志事件。然而,在你記錄一個事件之前,你還需要設置級別。日志級別用來確定日志的嚴重程度,它可以用來過濾日志事件或者將其發(fā)送給不同的Appender(想了解更多信息,請參考“日志級別”一節(jié)),Logger.log() 方法除了日志消息以外,還需要一個日志級別作為參數(shù):

logger.log(Level.WARNING, “This is a warning!”);

大部分日志框架都針對輸出特定級別日志提供了快捷方式。例如,下面語句的作用和上面語句的作用是一樣的:

logger.warning(“This is a warning!”);

你還可以阻止Logger輸出低于指定日志級別的消息。下面的示例中,Logger只能輸出高于WARNING級別的日志消息,并丟棄日志級別低于WARNING的消息:

logger.setLevel(Level.WARNING);

我們還有另外一些方法可以用來記錄額外的信息。logp()(精確日志)可以讓你指定每條日志記錄的源類(source class)和方法,而 logrb()(使用資源綁定的日志)可以讓你指定用于提取日志消息的資源。entering() 和 exiting() 方法可以讓你記錄方法調(diào)用信息,從而追蹤程序的執(zhí)行過程。

 

 

 

 

Appenders

Appenders將日志消息轉(zhuǎn)發(fā)給期望的輸出它負責接收日志事件,使用Layout格式化事件,然后將其發(fā)送給對應的目標。對于一個日志事件,我們可以使用多個Appenders來將事件發(fā)送到不同的目標位置。例如,我們可以在控制臺上顯示一個簡單的日志事件的同時,將其通過郵件的方式發(fā)送給指定的接收者。

請注意,在java.util.logging中,Appenders被稱作Handlers。

增加Appender

大部分日志框架的Appender都會執(zhí)行類似的功能,但在實現(xiàn)方面大相徑庭。如果使用 java.util.logging,你可以使用 Logger.addHandler() 方法將Appender添加到Logger中。例如,下面的代碼添加了一個新的ConsoleHandler,它會將日志輸出到控制臺:

logger.addHandler(new ConsoleHandler());

一種更常用的添加Appender的方式是使用配置文件。如果使用 java.util.logging,Appenders會定義一個以逗號隔開的列表,下面的示例將日志事件輸出到控制臺和文件:

handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler

如果使用基于XML的配置文件,Appenders會被添加到<Appenders>元素下面,如果使用Log4j,我們可以很容易地添加一個新ConsoleAppender來將日志消息發(fā)送到System.out:

<Console name="console" target="SYSTEM_OUT">
  <PatternLayout pattern="[%p] %t: %m%n" />
</Console>

 

 

 

 

Appenders類型

這一節(jié)描述了一些更通用的Appenders,以及它們在各種日志框架中是如何實現(xiàn)的。

ConsoleAppender

ConsoleAppender是最常用的Appenders之一,它只是將日志消息顯示到控制臺上。許多日志框架都將其作為默認的Appender,并且在基本的配置中進行預配置。例如,在Log4j中ConsoleAppender的配置參數(shù)如下所示。

參數(shù)

描述

filter

用于決定是否需要使用該Appender來處理日志事件

layout

用于決定如何對日志記錄進行格式化,默認情況下使用“%m%n”,它會在每一行顯示一條日志記錄

follow

用于決定Appender是否需要了解輸出(system.out或者system.err)的變化,默認情況是不需要跟蹤這種變化

name

用于設置Appender的名字

ignoreExceptions

用于決定是否需要記錄在日志事件處理過程中出現(xiàn)的異常

target

用于指定輸出目標位置,默認情況下使用SYSTEM_OUT,但也可以修改成SYSTEM_ERR

一個完整的Log4j2的配置文件如下所示:

<?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)在控制臺上,就像這樣:

An unexpected error occurred.

你也可以使用Logback實現(xiàn)完全一樣的效果:

<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

FileAppenders將日志記錄寫入到文件中,它負責打開、關閉文件,向文件中追加日志記錄,并對文件進行加鎖,以免數(shù)據(jù)被破壞或者覆蓋。

在Log4j中,如果想創(chuàng)建一個FileAppender,需要指定目標文件的名字,寫入方式是追加還是覆蓋,以及是否需要在寫入日志時對文件進行加鎖:

...
<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程序向同一個文件寫入日志時,文件的完整性。

...
<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>
...

SyslogAppender

SyslogAppenders將日志記錄發(fā)送給本地或者遠程系統(tǒng)的日志服務。syslog是一個接收日志事件服務,這些日志事件來自操作系統(tǒng)、進程、其它服務或者其它設備。事件的范圍可以從診斷信息到用戶登錄硬件失敗等。syslog的事件按照設備進行分類,它指定了正在記錄的事件的類型。例如,auth facility表明這個事件是和安全以及認證有關。

Log4j和Logback都內(nèi)置支持SyslogAppenders在Log4j中,我們創(chuàng)建SyslogAppender時,需要指定syslog服務監(jiān)聽的主機號、端口號以及協(xié)議。下面的示例演示了如何設定裝置

...
<Appenders>
  <Syslog name="SyslogAppender" host="localhost" port="514" protocol="UDP" facility="Auth" />
</Appenders>
...

在Logback中,我們可以實現(xiàn)同樣的效果:

...
<appender name="SyslogAppender" class="ch.qos.Logback.classic.net.SyslogAppender">
  <syslogHost>localhost</syslogHost>
  <port>514</port>
  <facility>Auth</facility>
</appender>
...

其它Appender

我們已經(jīng)介紹了一些經(jīng)常用到的Appenders,還有很多其它Appender。它們添加了新功能或者在其它的 一些Appender基礎上實現(xiàn)了新功能。例如,Log4j中的RollingFileAppender擴展了FileAppender,它可以在滿足特 定條件時自動創(chuàng)建新的日志文件;SMTPAppender會將日志內(nèi)容以郵件的形式發(fā)送出去;FailoverAppender會在處理日志的過程中,如 果一個或者多個Appender失敗,自動切換到其他Appender上。

如果想了解更多關于其他Appender的信息,可以查看Log4j Appender參考以及Logback Appender參考

 

 

 

 

Layouts

Layouts將日志記錄的內(nèi)容從一種數(shù)據(jù)形式轉(zhuǎn)換成另外一種。日志框架為純文本、HTML、syslog、XML、JSON、序列化以及其它日志提供了Layouts。

請注意在java.util.logging中Layouts也被稱為Formatters。

例如,java.util.logging提供了兩種Layouts:SimpleFormatter和XMLFormatter。默認情況下,ConsoleHandlers使用SimpleFormatter,它輸出的純文本日志記錄就像這樣:

Mar 31, 2015 10:47:51 AM MyClass main
SEVERE: An exception occurred.

而默認情況下,F(xiàn)ileHandlers使用XMLFormatter,它的輸出就像這樣:

<?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>

配置Layout

我們通常使用配置文件對Layouts進行配置。從Java 7開始,我們也可以使用system property來配置SimpleFormatter。

例如,在Log4j和Logback中最常用的Layouts是PatternLayout。它可以讓你決定日志事件中的哪些部分需要輸出,這是通過轉(zhuǎn)換模式(Conversion Pattern)完成的,轉(zhuǎn)換模式在每一條日志事件的數(shù)據(jù)中扮演了“占位符”的角色。例如,Log4j默認的PatternLayout使用了如下轉(zhuǎn)換模式:

<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屬性設置成你想要的Layout。在 代碼中,你可以創(chuàng)建一個新的Handler,調(diào)用setFormatter方法,然后通過logger.AddHandler()方法將Handler放 到Logger上面。下面的示例創(chuàng)建了一個ConsoleAppender,它使用XMLFormatter來對日志進行格式化,而不是使用默認的 SimpleFormatter:

Handler ch = new ConsoleHandler();
ch.setFormatter(new XMLFormatter());
logger.addHandler(ch);

這樣Logger會將下面的信息輸出到控制臺上:

<?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應該如何輸出日志記錄。從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 按照嚴重程度從重到輕,提供了以下級別:

  • SEVERE(***級別)

  • WARNING

  • INFO

  • CONFIG

  • FINE

  • FINER

  • FINEST(***級別)

另外, 還有兩個日志級別:ALL和OFF。ALL會讓Logger輸出所有消息,而OFF則會關閉日志功能。

設置日志級別

在設定日志級別后,Logger會自動忽略那些低于設定級別的日志消息。例如,下面的語句會讓Logger忽略那些低于WARNING級別的日志消息:

logger.setLevel(Level.WARNING);

然后,Logger會記錄任何WARNING或者更高級別的日志消息。我們也可以在配置文件中設置Logger的日志級別:

...
<Loggers>
  <Logger name="MyLogger" level="warning">
  ...

 

 

 

 

轉(zhuǎn)換模式

Log4j和Logback中的PatternLayout類都支持轉(zhuǎn)換模式,它決定了我們?nèi)绾螐拿恳粭l日志事件中提取信息以及如何對信息進行格式 化。下面顯示了這些模式的一個子集,對于Log4j和Logback來說,雖然這些特定的字段都是一樣的,但是并不是所有的字段都會使用相同的模式。想要 了解更多信息,可以查看Log4jLogback的PatternLayout文檔。

字段名稱

Log4j/Logback 模式

消息

%m

級別/嚴重程度

%p

異常

%ex

線程

%t

Logger

%c

方法

%M

例如,下面的PatternLayout會在中括號內(nèi)x顯示日志級別,后面是線程名字和日志事件的消息:

[%p] %t: %m

下面是使用了上述轉(zhuǎn)換模式后的日志輸出示例:

[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í)行的位置。例如,下面的棧跟蹤信息是程序試圖打開一個不存在的文件后生成的:

[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中沒有和可拋異常相關的信息,那么都會自動將%xEx(這種棧跟蹤信息包含了每次方法調(diào)用的包信息)添加到PatternLayout中。如果對于普通的日志信息的模式如下:

[%p] %t: %m

它會變?yōu)椋?/p>

[%p] %t: %m%xEx

這樣不僅僅錯誤信息會被記錄下來,完整的棧跟蹤信息也會被記錄:

[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)

%xEx中的包查詢是一個代價昂貴的操作,如果你頻繁的記錄異常信息,那么可能會碰到性能問題,例如:

  // ...
  } catch (FileNotFoundException ex) {
    logger.error(“Unable to open file!”, ex);
}

一種解決方法是在模式中顯式的包含%ex,這樣就只會請求異常的棧跟蹤信息:

[%p] %t: %m%ex

另外一種方法是通過追加%xEx(none)的方法排除(在Log4j)中所有的異常信息:

[%p] %t: %m%xEx{none}

或者在Logback中使用%nopex:

[%p] %t: %m%nopex

使用結(jié)構化布局輸出棧跟蹤信息

如你在“解析多行棧跟蹤信息”一節(jié)中所見,對于站跟蹤信息來說,使用結(jié)構化布局來記錄是最合適的方式,例如JSON和XML。 這些布局會自動將棧跟蹤信息按照核心組件進行分解,這樣我們可以很容易將其導出到其他程序或者日志服務中。對于上述站跟蹤信息,如果使用JSON格式,部分信息顯示如下:

...
"loggerName" : "FooClass",
  "message" : "Foo, oh no! ",
  "thrown" : {
    "commonElementCount" : 0,
    "localizedMessage" : "foo.file (No such file or directory)",
    "message" : "foo.file (No such file or directory)",
    "name" : "java.io.FileNotFoundException",
    "extendedStackTrace" : [ {
    "class" : "java.io.FileInputStream",
    "method" : "open",
    "file" : "FileInputStream.java",
    ...

記錄未捕獲異常

通常情況下,我們通過捕獲的方式來處理異常。如果一個異常沒有被捕獲,那么它可能會導致程序終止。如果能夠留存任何日志,那么這是一個可以幫助我們調(diào)試為什么會發(fā)生異常的好辦法,這樣你就可以找到發(fā)生異常的根本原因并解決它。下面來說明我們?nèi)绾谓⒁粋€默認的異常處理器來記錄這些錯誤。

Thread類中有兩個方法,我們可以用它來為未捕獲的異常指定一個ExceptionHandler:

setDefaultUncaughtExceptionHandler 可以讓你在任何線程上處理任何異常setUncaughtExceptionHandler可以讓你針對一個指定的線程設定一個不同的處理方法。而ThreadGroup則允許你設定一個處理方法。大部分人會使用默認的異常處理方法。

下面是一個示例,它設定了一個默認的異常處理方法,來創(chuàng)建一個日志事件。它要求你傳入一個UncaughtExceptionHandler:

import java.util.logging.*;
public class ExceptionDemo {
  private static final Logger logger = Logger.getLogger(ExceptionDemo.class);
  public static void main(String[] args) {
    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
      public void uncaughtException(Thread t, Throwable e) {
        logger.log(Level.SEVERE, t + " ExceptionDemo threw an exception: ", e);
      };
  });
  class adminThread implements Runnable {
    public void run() {
      throw new RuntimeException();
    }
  }
  Thread t = new Thread(new adminThread());
  t.start();
  }
}

下面是一個未處理異常的輸出示例:

May 29, 2015 2:21:15 PM ExceptionDemo$1 uncaughtException
SEVERE: Thread[Thread-1,5,main] ExceptionDemo threw an exception:
java.lang.RuntimeException
  at ExceptionDemo$1adminThread.run(ExceptionDemo.java:15)
  at java.lang.Thread.run(Thread.java:745)

 

 

 

 

JSON

JSON(JavaScript Object Notation)是一種用來存儲結(jié)構化數(shù)據(jù)的格式,它將數(shù)據(jù)存儲成鍵值對的集合,類似于HashMap或者Hashtable。JSON具有的可移植性和通用性,大部分現(xiàn)代語言都內(nèi)置支持它或者通過已經(jīng)準備好的第三方類庫來支持它。

JSON支持許多基本數(shù)據(jù)類型,包括字符串、數(shù)字、布爾、數(shù)組和null。例如,你可以使用下面的JSON格式來表示一個電腦:

{
  "manufacturer": "Dell",
  "model": "Inspiron",
  "hardware": {
    "cpu": "Intel Core i7",
    "ram": 16384,
    “cdrom”: null
  },
  "peripherals": [
    {
      "type": "monitor",
      "manufacturer": "Acer",
      "model": "S231HL"
    }
  ]
}

JSON的可移植性使得它非常適合存儲日志記錄,使用JSON后,Java日志可以被任何數(shù)目的JSON解釋器所讀取。因為數(shù)據(jù)已經(jīng)是結(jié)構化的,所以解析JSON日志要遠比解析純文本日志容易。

Java中的JSON

對于Java來說,有大量的JSON實現(xiàn),其中一個是JSON.simple。JSON.simple是輕量級的、易于使用,并且全部符合JSON標準。

如果想將上面的computer對象轉(zhuǎn)換成可用的Java對象,我們可以從文件中讀取JSON內(nèi)容,將其傳遞給JSON.simple,然后返回一個Object,接著我們可以將Object轉(zhuǎn)換成JSONObject:

Object computer = JSONValue.parse(new FileReader("computer.json"));
JSONObject computerJSON = (JSONObject)computer;

另外,為了取得鍵值對的信息,你可以使用任何日志框架來記錄一個JSONObject,JSONObject對象包含一個toString()方法, 它可以將JSON轉(zhuǎn)換成文本:

2015-05-06 14:54:32,878 INFO  JSONTest main {"peripherals":[{"model":"S231HL","manufacturer":"Acer","type":"monitor"}],"model":"Inspiron","hardware":{"cdrom":null,"ram":16384,"cpu":"Intel Core i7"},"manufacturer":"Dell"}

雖然這樣做可以很容易的打印JSONObject,但如果你使用結(jié)構化的Layouts,例如JSONLayout或者XMLLayout,可能會導致意想不到的結(jié)果:

...
"message" : "{"peripherals":[{"model":"S231HL","manufacturer":"Acer","type":"monitor"}],"model":"Inspiron","hardware":{"cdrom":null,"ram":16384,"cpu":"Intel Core i7"},"manufacturer":"Dell"}",
...

Log4j中的JSONLayout并沒有內(nèi)置支持內(nèi)嵌JSON對象,但你可以通過創(chuàng)建自定義Layout的方式來添加一個JSONObject字段,這個Layout會繼承或者替換JSONLayout。然 而,如果你使用一個日志管理系統(tǒng),需要記住許多日志管理系統(tǒng)會針對某些字段使用預定義的數(shù)據(jù)類型。如果你創(chuàng)建一個Layout并將JSONObject存 儲到message字段中,那么它可能會和日志系統(tǒng)中使用的String數(shù)據(jù)類型相沖突。一種解決辦法是將JSON數(shù)據(jù)存儲到一個字段中,然后將字符串類 型的日志消息存儲到另外一個字段中。

其它JSON庫

除了JSON.simple,Java中還有很多其它JSON庫。JSON-java是由JSON創(chuàng)建者開發(fā)的一個參考實現(xiàn),它包含了額外的一些功能,可以轉(zhuǎn)換其它數(shù)據(jù)類型,包括web元素。但是目前JSON-java已經(jīng)沒有人來維護和提供支持了。

如果想將JSON對象轉(zhuǎn)換成Java對象或者逆向轉(zhuǎn)換,Google提供了一個Gson庫。使 用Gson時,可以很簡單使用 toJson() 和 fromJson() 方法來解析JSON,這兩個方法分別用來將Java對象轉(zhuǎn)換成JSON字符串以及將JSON字符串轉(zhuǎn)換成Java對象。Gson甚至可以應用在內(nèi)存對象 中,允許你映射到那些沒有源代碼的對象上。

Jackson

Jackson是一個強大的、流行的、功能豐富的庫,它可以在Java中管理JSON對象。有一些框架甚至使用Jackson作為它們的JSONLayouts。盡管它很大并且復雜,但Jackson對于初學者和高級用戶來說,是很容易使用的。

Logback通過logback-jackson和logback-json-classic庫繼承了Jackson,這兩個庫也是logback-contrib項目的一部分。在集成了Jackson后,你可以將日志以JSON的格式導出到任何Appender中。

Logback Wiki詳細解釋了如何將JSON添加到logback中,在Wiki頁面中的示例使用了LogglyAppender,這里的配置也可以應用到其他Appender上。下面的示例說明了如何將JSON格式化的日志記錄寫入到名為myLog.json的文件中:

...
<appender name="file" class="ch.qos.Logback.core.FileAppender">
  <file>myLog.json</file>
  <encoder class="ch.qos.Logback.core.encoder.LayoutWrappingEncoder">
  <layout class="ch.qos.Logback.contrib.json.classic.JsonLayout">
    <jsonFormatter class="ch.qos.Logback.contrib.jackson.JacksonJsonFormatter"/>
    </layout>
  </encoder>
</appender>
...

你也可以通過FasterXML Wiki找到更多關于Jackson的深度介紹。

了解更多JSON相關信息

你可以通過JSON主頁學習更多JSON相關信息,或者通過CodeAcademy來通過學習一個交互式的快速上手教程(請注意這個課程是基于JavaScript的,而不是Java)有一些在線工具例如JSONLintJSON在線編輯器可以幫助你解析、驗證以及格式化JSON代碼。

 

 

 

 

NDC、MDC以及ThreadContext

當處理多線程應用程序,特別是web服務時,跟蹤事件可能會變得困難。當針對多個同時存在的多個用戶生成日志記錄時,你如何區(qū)分哪個行為和哪個日志 事件有關呢?如何兩個用戶沒有成功打開一個相同的文件,或者在同一時間沒有成功登陸,那么怎么處理日志記錄?你可能需要一種方式來將日志記錄和程序中的唯 一標示符關聯(lián)起來,這些標識符可能是用戶ID,會話ID或者設備ID。而這就是NDC、MDC以及ThreadContext的用武之地。

NDC、MDC和ThreadContext通過向單獨的日志記錄中添加***的數(shù)據(jù)戳,來創(chuàng)建日志足跡(log trails)。這些數(shù)據(jù)戳也被稱為魚標記(fish tagging),我們可以通過一個或者多個***的值來區(qū)分日志。這些數(shù)據(jù)戳在每個線程級別上進行管理,并且一直持續(xù)到線程結(jié)束,或者直到數(shù)據(jù)戳被刪 掉。例如,如果你的Web應用程序為每個用戶生成一個新的線程,那么你可以使用這個用戶的ID來標記日志記錄。當你想在一個復雜的系統(tǒng)中跟蹤特定的請求、 事務或者用戶,這是一種非常有用的方法。

嵌套診斷上下文(NDC)

NDC或者嵌套診斷上下文(Nested Diagnostic Context)是基于棧的思想,信息可以被放到棧上或者從棧中移除。而棧中的值可以被Logger訪問,并且Logger無需顯示想日志方法中傳入任何值。

下面的代碼示例使用NDC和Log4j來將用戶姓名和一條日志記錄關聯(lián)起來。NDC是一個靜態(tài)類,因此我們可以直接訪問它的方法,而無需實例化一個 NDC對象。在這個示例中, NDC.oush(username) 和 NDC.push(sessionID) 方法在棧中存儲了當前的用戶名(admin)和會話ID(1234),而NDC.pop()方法將一些項從棧中移除,NDC.remove()方法讓 Java回收內(nèi)存,以免造成內(nèi)存溢出。

[[141048]]

import java.io.FileReader;
import org.apache.Log4j.Logger;
import org.apache.Log4j.NDC;
...
String username = "admin";
String sessionID = "1234";
NDC.push(username);
NDC.push(sessionID);
try {
  // tmpFile doesn't exist, causing an exception.
  FileReader fr = new FileReader("tmpFile");
}
catch (Exception ex) {
  logger.error("Unable to open file.");
}
finally {
  NDC.pop();
  NDC.pop();
  NDC.remove();
}

Log4j的PatternLayout類通過%x轉(zhuǎn)換字符從NDC中提取如果一個日志事件被觸發(fā),那么完整的NDC棧就被傳到Log4j:

<PatternLayout pattern="%x %-5p - %m%n" />

運行程序后,我們可以得出下面的輸出:

"admin 1234 ERROR – Unable to open file."

映射診斷上下文(MDC)

MDC或者映射診斷上下文和NDC很相似,不同之處在于MDC將值存儲在鍵值對中,而不是棧中。這樣你可以很容易的在Layout中引用一個單獨的 鍵。MDC.put(key,value) 方法將一個新的鍵值對添加到上下文中,而 MDC.remove(key) 方法會移除指定的鍵值對。

如果想在日志中同樣顯示用戶名和會話ID,我們需要使用 MDC.put() 方法將這兩個變量存儲成鍵值對:

import java.io.FileReader;
import org.apache.Log4j.Logger;
import org.apache.Log4j.MDC;
...
MDC.put("username", "admin");
MDC.put("sessionID", "1234");
try {
  // tmpFile doesn't exist, causing an exception.
  FileReader fr = new FileReader("tmpFile");
}
catch (Exception ex) {
  logger.error("Unable to open file!");
}
finally {
  MDC.clear();
}

這里再一次強調(diào),在不需要使用Context后,我們需要使用 MDC.clear() 方法將所有的鍵值對從MDC中移除,這樣會降低內(nèi)存的使用量,并阻止MDC在后面試圖調(diào)用那些已經(jīng)過期的數(shù)據(jù)。

在日志框架中訪問MDC的值時,也稍微有些區(qū)別。對于存儲在上下文中的任何鍵,我們可以使用%X(鍵)的方式來訪問對應的值。這樣,我們可以使用 %X(username) 和 %X(sessionID) 來獲取對應的用戶名和會話ID:

<PatternLayout pattern="%X{username} %X{sessionID} %-5p - %m%n" />
"admin 1234 ERROR – Unable to open file!"

如果我們沒有指定任何鍵,那么MDC上下文就會被以 {(key, value),(key, value)} 的方式傳遞給Appender。

Logback中的NDC和MDC

和Log4j不同,Logback內(nèi)置沒有實現(xiàn)NDC。但是slf4j-ext包提供了一個NDC實現(xiàn),它使用MDC作為基礎。在Logback內(nèi)部,你可以使用 MDC.put()MDC.remove() 和 MDC.clear() 方法來訪問和管理MDC:

import org.slf4j.MDC;
...
Logger logger = LoggerFactory.getLogger(MDCLogback.class);
...
MDC.put("username", "admin");
MDC.put("sessionID", "1234");
try {
  FileReader fr = new FileReader("tmpFile");
}
catch (Exception ex) {
  logger.error("Unable to open file.");
}
finally {
  MDC.clear();
}

在Logback中,你可以在Logback.xml中將如下模式應用到Appender上,它可以輸出和上面Log4j相同的結(jié)果:

<Pattern>[%X{username}] %X{sessionID} %-5p - %m%n</Pattern>
"[admin] 1234 ERROR - Unable to open file."

針對MDC的訪問并不僅僅限制在PatternLayout上,例如,當使用JSONFormatter時,MDC中的所有值都會被導出:

{
"timestamp":"1431970324945",
"level":"ERROR",
"thread":"main",
"mdc":{
"username":"admin",
"sessionID":"1234"
},
"logger":"MyClass",
"message":"Unable to open file.",
"context":"default"
}

ThreadContext

Version 2 of Log4j merged MDC and NDC into a single concept known as the Thread Context. The Thread Context is an evolution of MDC and NDC, presenting them respectively as the Thread Context Map and Thread Context Stack. The Thread Context is managed through the static ThreadContext class, which is implemented similar to Log4j 1’s MDC and NDC classes.

Log4j版本2中將MDC和NDC合并到一個單獨的組件中,這個組件被稱為線程上下文。線程上下文是針對MDC和NDC的進化,它分別用線程上下文Map映射線程上下文棧來表示MDC和NDC。我們可以通過ThreadContext靜態(tài)類來管理線程上下文,這個類在實現(xiàn)上類似于Log4j版本1中的MDC和NDC。

When using the Thread Context Stack, data is pushed to and popped from a stack just like with NDC:

當使用線程上下文棧時,我們可以向NDC那樣向棧中添加或者刪除數(shù)據(jù):

import org.apache.logging.Log4j.ThreadContext;
...
ThreadContext.push(username);
ThreadContext.push(sessionID);
// Logging methods go here
ThreadContext.pop();
...

當使用線程上下文映射時,我們可以像MDC那樣將值和鍵結(jié)合在一起:

import org.apache.logging.Log4j.ThreadContext;
...
ThreadContext.put(“username”,"admin");
ThreadContext.put("sessionID", "1234");
// Logging methods go here
ThreadContext.clearMap();
...

ThreadContext類提供了一些方法,用于清除棧、清除MDC、清除存儲在上下文中的所有值,對應的方法是ThreadContext.clearAll()、ThreadContext.clearMap()和ThreadContext.clearStack()。

和在MDC以及NDC中一樣,我們可以使用Layouts在線程上下文中訪問這些值。使用PatternLayout時,%x轉(zhuǎn)換模式會從棧中獲取值,%X和%X(鍵)會從圖中獲取值。

ThreadContext過濾

一些框架允許你基于某些屬性對日志進行過濾。例如,Log4j的DynamicThresholdFilter 會在鍵滿足特定條件的情況下,自動調(diào)整日志級別。再比如,如果我們想要觸發(fā)TRACE級別的日志消息,我們可以創(chuàng)建一個名為trace-logging-enabled的鍵,并向log4j配置文件中添加一個過濾器:

<Configuration name="MyApp">
<DynamicThresholdFilter key="trace-logging-enabled" onMatch="ACCEPT" onMismatch="NEUTRAL">
<KeyValuePair key="true" value="TRACE" />
</DynamicThresholdFilter>
...

如果ThreadContext包含一個名為trace-logging-enabled的鍵,onMatch 和 onMismatch 會決定如何處理它。關于 onMatch 和 onMismatch,我們有三個可選項:ACCEPT,它會處理過濾器的規(guī)則;DENY,它會忽略過濾器的規(guī)則;NEUTRAL,它會推遲到下一個過濾 器。除了這些,我們還定義一個鍵值對,當值為true時,我們啟用TRACE級別的日志。

現(xiàn)在,當trace-logging-enabled被設置成true時,即使根Logger設置的日志級別高于TRACE,Appender也會記錄TRACE級別的消息。

你可能還想過濾一些特定的日志到特定的Appender中,Log4j中提供了ThreadContextMapFilter來實現(xiàn)這一點。如果我們想要限制某個特定的Appender,只記錄針對某個用戶的TRACE級別的消息,我們可以基于username鍵添加一個ThreadContextMapFilter:

<Console name="ConsoleAppender" target="SYSTEM_OUT">
<ThreadContextMapFilter onMatch="ACCEPT" onMismatch="DENY">
<KeyValuePair key="username" value="admin" />
</ThreadContextMapFilter>
...

如果想了解更多信息,你可以查看Log4jLogback文檔中關于DynamicThresholdFilter部分。

 

 

 

 

Markers

Markers允許你對單獨的日志記錄添加一些***的數(shù)據(jù)。它可以用來對日志記錄進行分組,觸發(fā)一些行為或者對日志記錄進行過濾,并將過濾結(jié)果輸出到指定的Appender中。你甚至可以將Markers和ThreadContext結(jié)合在一起使用,以提高搜索和過濾日志數(shù)據(jù)的能力。

例如,假設我們有一個可以連接到數(shù)據(jù)庫的類,如果在打開數(shù)據(jù)庫的時候發(fā)生了異常,我們需要把異常記錄成fatal錯誤。我們可以創(chuàng)建一個名為DB_ERROR的Marker,然后將其應用到日志事件中:

import org.apache.logging.Log4j.Marker;
import org.apache.logging.Log4j.MarkerManager;
...
final static Marker DB_ERROR = MarkerManager.getMarker("DATABASE_ERROR");
...
logger.fatal(DB_ERROR, "An exception occurred.");

為了在日志輸出中顯示Marker信息,我們需要在PatternLayout中添加%marker轉(zhuǎn)換模式:

<PatternLayout pattern="%p %marker: %m%n" />
[FATAL] DATABASE_ERROR: An exception occurred.

或者對于JSON和XML格式的Layouts,會自動在輸出中包含Marker信息:

...
"thread" : "main",
"level" : "FATAL",
"loggerName" : "DBClass",
"marker" : {
  "name" : "DATABASE_ERROR"
},
"message" : "An exception occurred.",
...

通過對Marker數(shù)據(jù)進行自動解析和排序,集中式的日志服務可以很容易對日志進行搜索處理。

Markers過濾

Marker過濾器可以讓你決定哪些Marker由哪些Logger來處理。marker字段會比較在日志事件里面的Marker名字,如果名字匹配,那么Logger會執(zhí)行后續(xù)的行為。例如,在Log4j中,我們可以配置一個Appender來只顯示哪些使用了DB_ERROR Marker的消息,這可以通過log4j2.xml中的Appender添加如下信息來實現(xiàn):

<MarkerFilter marker="DATABASE_ERROR" onMatch="ACCEPT" onMismatch="DENY" />

如果日志記錄中某一條的Marker可以匹配這里的marker字段,那么onMatch會決定如何處理這條記錄。如果不能夠匹配,或者日志記錄中 沒有Marker信息,那么onMismatch就會決定如何處理這條記錄。對于onMatch和onMismatch來說,有3個可選 項:ACCEPT,它允許記錄事件;DENY,它會阻塞事件;NEUTRAL,它不會對事件進行任何處理。

在Logback中,我們需要更多一些設置。首先,想Appender中添加一個新的EvaluatorFilter,并如上所述指定onMatch和onMismatch行為然后,添加一個OnMarkerEvaluator并將Marker的名字傳遞給它:

<filter class="ch.qos.Logback.core.filter.EvaluatorFilter">
  <evaluator class="ch.qos.Logback.classic.boolex.OnMarkerEvaluator">
    <marker>DATABASE_ERROR</marker>
  </evaluator>
  <onMatch>ACCEPT</onMatch>
  <onMismatch>DENY</onMismatch>
</filter>

將Markers和NDCMDC以及ThreadContext結(jié)合使用

Marker的功能和ThreadContext類似,它們都是向日志記錄中添加***的數(shù)據(jù),這些數(shù)據(jù)可以被Appender訪問。如果把這兩者結(jié)合使用,可以讓你更容易的對日志數(shù)據(jù)進行索引和搜索。如果能夠知道何時使用哪一種技術,會對我們有所幫助。

NDC、MDC和ThreadContext被用于將相關日志記錄結(jié) 合在一起。如果你的應用程序會處理多個同時存在的用戶,ThreadContext可以讓你將針對某個特定用戶的一組日志記錄組合在一起。因為 ThreadContext針對每個線程都是不一樣的,所以你可以使用同樣的方法來對相關的日志記錄進行自動分組。

另一方面,Marker通常用于標記或者高亮顯示某些特殊事件。在上述示例中,我們使用DB_ERROR Marker來標明在方法中發(fā)生的SQL相關異常。我們可以使用DB_ERROR Marker來將這些事件的處理過程和其他事件區(qū)分開來,例如我們可以使用SMTP Appender來將這些事件通過郵件發(fā)送給數(shù)據(jù)庫管理員。

 

 

 

 

額外資源

指南和教程

  • Java Logging(Jakob Jenkov)——使用Java Logging API進行日志開發(fā)教程

  • Java Logging Overview(Oracle)—— Oracle提供的在Java中進行日志開發(fā)的指南

  • Log4J Tutorial(Tutorials Point)——使用log4j 版本1進行日志開發(fā)的指南

日志抽象層

  • Apache Commons Logging(Apache)——針對Log4j、Avalon LogKit和java.util.logging的抽象層

  • SLF4J(QOS.ch)——一個流程的抽象層,應用在多個日志框架上,包括Log4j、Logback以及java.util.logging

日志框架

  • Java Logging API(Oracle)—— Java默認的日志框架

  • Log4j(Apache)——開源日志框架

  • Logback(Logback Project)——開源項目,被設計成Log4j版本1的后續(xù)版本

  • tinylog(tinylog)——輕量級開源logger

 

 
責任編輯:王雪燕 來源: ImportNew
相關推薦

2017-03-27 21:14:32

Linux日志指南

2023-05-05 17:20:04

2012-08-21 06:53:00

測試軟件測試

2025-03-11 00:54:42

2015-03-05 11:28:51

Linux桌面環(huán)境終極指南

2015-11-08 14:44:48

2022-02-09 09:00:00

云計算BigQuerySnowflake

2013-12-18 09:36:08

企業(yè)移動指南

2022-07-22 13:14:57

TypeScript指南

2024-05-17 10:59:25

云計算谷歌云

2022-03-28 09:52:42

JavaScript語言

2020-07-19 08:15:41

PythonDebug

2025-01-14 00:00:00

2024-07-10 09:07:09

2024-01-31 15:28:38

物聯(lián)網(wǎng)IOT連接技術

2023-05-23 18:31:14

Rust編程

2022-06-30 08:00:00

MySQL關系數(shù)據(jù)庫開發(fā)

2024-08-19 00:40:00

SQL數(shù)據(jù)庫

2021-07-07 05:50:06

SD-WAN軟件定義廣域網(wǎng)網(wǎng)絡

2022-06-06 07:24:09

Caddy開源Ubuntu
點贊
收藏

51CTO技術棧公眾號