由於更早的擁抱開源,Java在解決方案的數量上擁有.NET無法比擬的優勢,可這就苦了我們這樣的菜雞玩家,比如日誌,光主流的解決方案就有JUL、Log4j、Logback、Log4j2、SLF4j,這麼多方案,到底要用哪一個?
若是論資歷的話,log4j無疑是老前輩,作爲早期使用最廣的的日誌框架,由Ceki創建,後來成爲Apache的頂級項目之一,說是Java社區的日誌標準也不爲過,不過Sun公司可不這麼覺得,覺得Apache就是典型的關公面前耍大刀,於是在jdk1.4之後增加了一個名爲“java.util.logging”的包,簡稱JUL,用於日誌記錄。不過由於log4j已經有了廣大的羣衆基礎,加上JUL時間倉促,有些地方並不好用,所以不少人對此並不買賬。Apache一瞅,Sun也太小家子器了,決定教教Sun什麼叫格局,就推出了Jakarta Commons Logging,簡稱JCL,JCL定義了一套日誌接口,只要你調用的JCL的接口,你想用log4j用log4j,想用jul用jul,我全都要。據說是受不了Apache的工作氛圍,log4j的創始人Ceki於2006年出走Apache,並先後創建了Slf4j(類似於JCL)和logback(類似於log4j),於是Java日誌領域一分爲二:JCL和Slf4j兩大陣營。logback作爲後起之秀,船小好調頭,又有親爹加持,發展的越來越快,隱隱有些壓住log4j一頭,所以Apache在2012年又在log4j的基礎上升級爲2.0版本(賽亞人變身了屬於),也就是現在的log4j2。
其實說白了就是天才老爹入贅豪門,生了個哥哥,後來名門大戶的呆不習慣,離家創業,又生了個弟弟,親爹肯定是想弟弟繼承家業,但是大戶族老們可不答應,畢竟寶都押在了哥哥頭上,於是上演了一出兄弟相爭的戲碼,倒黴的卻是嫡出的JUL。
slf4j
sfl4j全稱Simple Logging Facade for Java,它爲各種日誌框架(log4j、logback等)提供了一個共同的API,而非日誌的具體實現方案,所以如果需要實現日誌的輸出,還需要引入其他框架。由於slf4j將應用程序和具體的日誌實現方案解耦,可以讓開發人員在不同的日誌框架之間進行切換,比如你開始用了log4j後來發現logback更好用,只需要修改依賴和配置即可,而無需修改代碼。所以在開發時,我們在項目中要避免直接使用log4j和logback中的API,而應該依賴使用slf4j中API。如果使用了slf4j,那麼無論你使用log4j還是logback,都可以使用下面的代碼記錄日誌:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
public static void main(String[] args) {
System.out.println("開始執行");
Logger logger = LoggerFactory.getLogger(Main.class);
logger.trace("跟蹤日誌");
logger.debug("調試日誌");
logger.info("普通日誌");
logger.warn("報警日誌");
logger.error("錯誤日誌");
System.out.println("執行完畢");
}
}
Maven依賴:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.10</version>
</dependency>
slf4j+log4j2
官網:https://logging.apache.org/log4j/2.x/index.html
在應用中想使用,slf4j+log4j來記錄日誌,除了需要使用slf4j-api之外,還需要額外引入適配器:
- slf4j 1.7.x及其之前的版本使用
log4j-slf4j-impl
。 - slf4j 2.0.x及其之後的版本使用
log4j-slf4j2-impl
。
項目依賴:
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- Log4j2 API -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<!-- Log4j2 Core Implementation -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<!-- SLF4J Log4j2 Binding -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
不過一般情況下由於log4j-core
和log4j-slf4j-impl
中已經自動依賴了log4j-api
和 slf4j-api
,所以有時候依賴會簡寫爲:
<dependencies>
<!-- Log4j2 Core Implementation -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<!-- SLF4J Log4j2 Binding -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
如果你需要使用log4j 1.x,那麼可以使用log4j-to-slf4j
用於log4j 1.x和slf4j之間橋接。
log4j的配置可通過編程方式和配置文件的方式實現,主流的方式都是使用配置文件:
- log4j 2配置文件的名稱爲log4j2.mxl,放在src目錄下,maven項目放在resources下。
- log4j 2也支持json、yaml等格式的配置文件,如log4j2.properties、log4j2.yaml、log4j2.json等
在resources下面新建log4j2.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
<!--定義變量-->
<Properties>
<!--定義日誌存儲目錄的變量-->
<Property name="logDir">D:/logs</Property>
</Properties>
<!--輸出源-->
<Appenders>
<!--輸出到控制檯-->
<Console name="Console" target="SYSTEM_OUT">
<!--可選配置:控制檯只輸出level及以上級別的信息,其他的直接拒絕,具體的可以查看ThresholdFilter相關的說明-->
<ThresholdFilter level="INFO" onMatch="ACCEPT"/>
<!--日誌的輸出格式-->
<PatternLayout pattern="[%-level]%d{yyyy-MM-dd HH:mm:ss.SSS} %logger{36} - %msg%n"/>
</Console>
<!--輸出到文件,大部分時候都會使用RollingFile-->
<File name="File" fileName="${logDir}/log.txt">
<PatternLayout>
<Pattern>%d %p %c [%t] %m%n</Pattern>
</PatternLayout>
</File>
<!-- 滾動輸出:
name:Appender的唯一標識,跟下面AppenderRef中的ref對應。
fileName:指定了日誌文件當前活動路徑和文件名,決定了日誌文件被寫入的初始文件位置。
filePattern:定義滾動後的文件命名規則
-->
<RollingFile name="RollingFileInfo" fileName="d:/logs/log.txt" filePattern="d:/logs/log-%d{yyyy-MM-dd}_%i.txt">
<!-- 只輸出level及以上級別的信息(onMatch),其他的直接拒絕(onMismatch) -->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 輸出的格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss,SSS}] [%p] - %l - %m%n"/>
<!-- Policies:滾動策略,決定何時應該產生新日誌 -->
<Policies>
<!--時間策略:具體的看文件命名格則,如{yyyy-MM-dd},那這個意思就是每天一個,如果是{yyyy-MM-dd HH}那這個意思就是每小時,以此類推-->
<TimeBasedTriggeringPolicy interval="1"/>
<!--大小策略:每當文件大小達到500MB,就開始滾動,產生一個新日誌文件,後面的%i即標識產生了第幾個文件-->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!--文件夾下最多可以有幾個文件,過期的舊文件會被刪除,默認是7-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<!-- 記錄器 -->
<Loggers>
<!--
name:指定logger對象的名稱,用於區別日誌記錄的來源
level:debug、info、warn、error、fatal,按重要程度從低到高,如果配置爲warn,那麼就只有warn、error、fatal的日誌會被記錄
additivity:用於指定日誌消息是否要向上傳遞給父級日誌記錄器 -->
<Logger name="em.im.pve" level="debug" additivity="true"/>
<!-- 日誌系統中的最頂層Logger,代表了日誌層次結構的根節點,指定使用哪些Appender -->
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileInfo"/>
</Root>
</Loggers>
</Configuration>
Appenders中遠不止控制檯和文件這兩種形式,還包括數據庫、MQ、HTTP等多種輸出形式,但業務上最常用的還是輸出到文件,如果有其他的輸出需求,再去官方閱讀文檔即可。不過我覺得對大多數人來說,困惑最多的可能還是Layout的使用,比如:[%d{yyyy-MM-dd HH:mm:ss,SSS}] [%p] - %l - %m%n
這到底是個啥?且聽我一一拆解:
- %d{yyyy-MM-dd HH:mm:ss.SSS}:就是表示格式化輸出的時間,如果你就是示例的輸出,一個%d就行。
- [ ]就是字面意思,不用糾結。
- %level:日誌級別和%p表示的結果一樣。
- %pid:進程id
- %-5P:-表示左對齊,5表示佔位空間
- %L:輸出行號
- %m:日誌內容
- %n:換行,一般就是一條日誌一行
- %t:輸入製表符
- %logger:和%c的意思一樣,輸出日誌記錄器的名稱,後面緊跟的數字是精度,以減小記錄器名稱的大小
slf4j+logback
logback中有三個主要的核心模塊:logback-core、logback-classic、logback-access
- logback-core:核心模塊,提供了日誌框架的基礎功能,是我們通常意義上說的logback。
- logback-classic:實現了slf4j api。
- logback-access:用於日誌訪問,可以通過HTTP訪問日誌信息。
由於logback-classic中已經引入了logbak-core和slf4j-api,所以直接引入logback-classic即可:
<dependencies>
<!-- LogBack -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
</dependencies>
配置文件(logback.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!--配置根:
scan:設置爲true是,如果配置文件發生更改,配置就會重新加載
scanPeriod:當scan爲true時,會依據這個進行掃描查看是不是更新,默認單位是ms
-->
<configuration scan="true" scanPeriod="60 seconds">
<!-- 變量定義 -->
<property name="logPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %c{36} - %msg%n"/>
<property name="logPath" value="d:/logs/log.txt" />
<!-- 控制檯輸出 -->
<appender name="logConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<!-- 文件輸出,記錄INFO級別以上的日誌 -->
<appender name="logFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}</file>
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
<!-- 滾動策略:當然還有FixedWindowRollingPolicy、SizeBasedTriggeringPolicy等策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--滾動輸出的路徑-->
<fileNamePattern>log/app-%d{yyyy-MM-dd}.txt</fileNamePattern>
<!--文件夾下保留文件的最大數量-->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 根日誌級別設置爲INFO,附加到控制檯和文件輸出 -->
<root level="INFO">
<appender-ref ref="logConsole"/>
<appender-ref ref="logFileInfo"/>
</root>
</configuration>