JDK Logging
從jdk1.4起,JDK開始自帶一套日志系統。JDK Logger最大的優點就是不需要任何類庫的支持,只要有Java的運行環境就可以使用。相對於其他的日志框架,JDK自帶的日志可謂是雞肋,無論易用性,功能還是擴展性都要稍遜一籌,所以在商業系統中很少直接使用。Java標准庫內置了日志包java.util.logging
,我們可以直接用。
JDK的Logging定義了7個日志級別,從嚴重到普通:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
因為默認級別是INFO,因此,INFO級別以下的日志,不會被打印出來。使用日志級別的好處在於,調整級別,就可以屏蔽掉很多調試相關的日志輸出。
使用Java標准庫內置的Logging有以下局限:
Logging系統在JVM啟動時讀取配置文件並完成初始化,一旦開始運行main()
方法,就無法修改配置;
配置不太方便,需要在JVM啟動時傳遞參數-Djava.util.logging.config.file=<config-file-name>
。
因此,Java標准庫內置的Logging使用並不是非常廣泛。
Commons Logging日志庫
Commons Logging是使用最廣泛的日志模塊;
和Java標准庫提供的日志不同,Commons Logging是一個第三方日志庫,它是由Apache創建的日志模塊。
Commons Logging的特色是,它可以掛接不同的日志系統,並通過配置文件指定掛接的日志系統。默認情況下,Commons Loggin自動搜索並使用Log4j(Log4j是另一個流行的日志系統),如果沒有找到Log4j,再使用JDK Logging。
使用Commons Logging只需要和兩個類打交道,並且只有兩步:
第一步,通過LogFactory
獲取Log
類的實例; 第二步,使用Log
實例的方法打日志。
Commons Logging定義了6個日志級別:
- FATAL
- ERROR
- WARNING
- INFO
- DEBUG
- TRACE
默認級別是INFO
。
使用Commons Logging時,如果在靜態方法中引用Log
,通常直接定義一個靜態類型變量:
// 在靜態方法中引用Log:
public class Main {
static final Log log = LogFactory.getLog(Main.class);
static void foo() {
log.info("foo");
}
}
在實例方法中引用Log
,通常定義一個實例變量:
// 在實例方法中引用Log:
public class Person {
protected final Log log = LogFactory.getLog(getClass());
void foo() {
log.info("foo");
}
}
注意到實例變量log的獲取方式是LogFactory.getLog(getClass())
,雖然也可以用LogFactory.getLog(Person.class)
,但是前一種方式有個非常大的好處,就是子類可以直接使用該log
實例。例如:
// 在子類中使用父類實例化的log:
public class Student extends Person {
void bar() {
log.info("bar");
}
}
此外,Commons Logging的日志方法,例如info()
,除了標准的info(String)
外,還提供了一個非常有用的重載方法:info(String, Throwable)
,這使得記錄異常更加簡單:
try {
...
} catch (Exception e) {
log.error("got exception!", e);
}
Log4j日志框架
Commons Logging,可以作為“日志接口”來使用。而真正的“日志實現”可以使用Log4j。
Log4j是一種非常流行的日志框架,最新版本是2.x。
Log4j是一個組件化設計的日志系統,它的架構大致如下:
當我們使用Log4j輸出一條日志時,Log4j自動通過不同的Appender把同一條日志輸出到不同的目的地。例如:
- console:輸出到屏幕;
- file:輸出到文件;
- socket:通過網絡輸出到遠程計算機;
- jdbc:輸出到數據庫
在輸出日志的過程中,通過Filter來過濾哪些log需要被輸出,哪些log不需要被輸出。例如,僅輸出ERROR
級別的日志。
最后,通過Layout來格式化日志信息,例如,自動添加日期、時間、方法名稱等信息。
上述結構雖然復雜,但我們在實際使用的時候,並不需要關心Log4j的API,而是通過配置文件來配置它。
以XML配置為例,使用Log4j的時候,我們把一個log4j2.xml
的文件放到classpath
下就可以讓Log4j讀取配置文件並按照我們的配置來輸出日志。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<!-- 定義日志格式 -->
<Property name="log.pattern">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n</Property>
<!-- 定義文件名變量 -->
<Property name="file.err.filename">log/err.log</Property>
<Property name="file.err.pattern">log/err.%i.log.gz</Property>
</Properties>
<!-- 定義Appender,即目的地 -->
<Appenders>
<!-- 定義輸出到屏幕 -->
<Console name="console" target="SYSTEM_OUT">
<!-- 日志格式引用上面定義的log.pattern -->
<PatternLayout pattern="${log.pattern}" />
</Console>
<!-- 定義輸出到文件,文件名引用上面定義的file.err.filename -->
<RollingFile name="err" bufferedIO="true" fileName="${file.err.filename}" filePattern="${file.err.pattern}">
<PatternLayout pattern="${log.pattern}" />
<Policies>
<!-- 根據文件大小自動切割日志 -->
<SizeBasedTriggeringPolicy size="1 MB" />
</Policies>
<!-- 保留最近10份 -->
<DefaultRolloverStrategy max="10" />
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<!-- 對info級別的日志,輸出到console -->
<AppenderRef ref="console" level="info" />
<!-- 對error級別的日志,輸出到err,即上面定義的RollingFile -->
<AppenderRef ref="err" level="error" />
</Root>
</Loggers>
</Configuration>
雖然配置Log4j比較繁瑣,但一旦配置完成,使用起來就非常方便。對上面的配置文件,凡是INFO
級別的日志,會自動輸出到屏幕,而ERROR
級別的日志,不但會輸出到屏幕,還會同時輸出到文件。並且,一旦日志文件達到指定大小(1MB),Log4j就會自動切割新的日志文件,並最多保留10份。
有了配置文件還不夠,因為Log4j也是一個第三方庫,我們需要從這里下載Log4j,解壓后,把以下3個jar包放到classpath
中:
- log4j-api-2.x.jar
- log4j-core-2.x.jar
- log4j-jcl-2.x.jar
因為Commons Logging會自動發現並使用Log4j,所以,把上一節下載的commons-logging-1.2.jar
也放到classpath
中。
要打印日志,只需要按Commons Logging的寫法寫,不需要改動任何代碼,就可以得到Log4j的日志輸出。
在開發階段,始終使用Commons Logging接口來寫入日志,並且開發階段無需引入Log4j。如果需要把日志寫入文件, 只需要把正確的配置文件和Log4j相關的jar包放入classpath
,就可以自動把日志切換成使用Log4j寫入,無需修改任何代碼。
SLF4J和Logback
Commons Logging和Log4j這一對好基友,它們一個負責充當日志API,一個負責實現日志底層,搭配使用非常便於開發。
其實SLF4J類似於Commons Logging,也是一個日志接口,而Logback類似於Log4j,是一個日志的實現。
為什么有了Commons Logging和Log4j,又會蹦出來SLF4J和Logback?這是因為Java有着非常悠久的開源歷史,不但OpenJDK本身是開源的,而且我們用到的第三方庫,幾乎全部都是開源的。開源生態豐富的一個特定就是,同一個功能,可以找到若干種互相競爭的開源庫。
因為對Commons Logging的接口不滿意,有人就搞了SLF4J。因為對Log4j的性能不滿意,有人就搞了Logback。
對比一下Commons Logging和SLF4J的接口:
使用SLF4J和Logback和前面講到的使用Commons Logging加Log4j是類似的,先分別下載SLF4J和Logback,然后把以下jar包放到classpath下:
- slf4j-api-1.7.x.jar
- logback-classic-1.2.x.jar
- logback-core-1.2.x.jar
然后使用SLF4J的Logger和LoggerFactory即可。和Log4j類似,我們仍然需要一個Logback的配置文件,把logback.xml
放到classpath下,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<file>log/output.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>log/output.log.%i</fileNamePattern>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>1MB</MaxFileSize>
</triggeringPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
從目前的趨勢來看,越來越多的開源項目從Commons Logging加Log4j轉向了SLF4J加Logback。
參考資料