從寫代碼開始,就陸陸續續接觸到了許多日志架構,常用的Log4j 、Log4j2、 Logback等。每次自己寫項目時,就copy别人的代碼或網上的demo。配置log4j.properties或者logback.xml 就能搞定。但是一直沒有理清各個架構之間的關系。然後總感覺列印日志的時候并不是随心所欲。特此簡單分析分析。
常見的日志架構
SLF4j是日志門面,是一個日志标準,并非真正的日志實作,log4j、log4j2、logback才是真正的日志實作庫。也就是說log4j、log4j2、logback 這兄弟三才是 真正列印日志的。日志門面就是為了統一各個依賴的不同日志實作,便于我們自己項目中對類進行統一維護處理。
再來看看阿裡java開發規範:
1.【強制】應用中不可直接使用日志系統(Log4j、Logback)中的 API,而應依賴使用日志架構
SLF4] 中的 API,使用門面模式的日志架構,有利于維護和各個類的日志處理方式統一。
import org.slf4j. Logger;
import org.sLf4j. LoggerFactory;
private static final Logger Logger LoggerFactory.getLogger(Abc.class):
如題,這裡選用常用的SLF4j + Log4j 、Log4j2、 Logback 進行分析。
SLF4j分析
slf4j是門面,大的設計模式是門面系統,而logback是直接實作了slf4j-api中的接口(已經預設實作了SLF4j的日志标準),是通過接口實作的方式完成了門面的實作。而log4j和log4j2沒有直接實作接口,是以需要個擴充卡。slf4j-log4j12和log4j-slf4j-impl就是擴充卡,将原本不能實作slf4j的變得也能實作這個标準了。添加了擴充卡後,就能使用slf4j的接口來使用log4j了。
以slf4j 的概念,共可劃分為以下4種庫:
日志接口層 | slf4j-api common-logging(apache提供的一個通用的日志接口) jboss-logging(Hibernate內建的日志工具) |
日志實作層 | JUL(java.util.logging) log4j log4j2 logback slf4j-simple slf4j-nop |
綁定适配(adaptation)層 | slf4j-jcl slf4j-jdk14 slf4j-log412(log4j實作slf标準) log4j-slf4j-impl(log4j2實作slf标準) |
橋接(bridge)層 | jcl-over-slf4j jul-to-slf4j log4j-over-slf4j(log4j到slf4j的橋梁) log4j-to-slf4j(log4j2到slf4j的橋梁) |
commons-logging
commons-logging也被稱為JCL(Java Common Logging),是apache提供的一個通用的日志接口。使用者可以自由選擇第三方的日志元件作為具體實作,像log4j,或者JDK自帶的logging,commons-logging會通過動态查找的機制,在程式運作時自動找出真正使用的日志庫,規則是倘若沒有定義日志實作,則預設使用JUL日志架構作為日志實作,如果有log4j,則優先使用log4j作為日志實作。當然,commons-logging内部有一個Simple Logger的簡單實作,但是功能很弱。是以使用commons-logging,通常都是配合着log4j來使用。使用它的好處就是,代碼依賴是commons-logging而非log4j,避免了和具體的日志方案直接耦合,在有必要時,可以更改日志實作的第三方庫。
使用commons-logging的常見代碼:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class A {
private static Log logger = LogFactory.getLog(this.getClass());
}
#使用JUL作為日志實作,僅需在maven工程中導入如下依賴:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
#使用log4j作為日志實作,僅需在maven工程中導入如下依賴:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j<artifactId>
<version>1.2.17</version>
</dependency>
jul-to-slf4j.jar和slf4j-jdk14.jar 二者不能同時存在
slf4j-jdk14.jar是jul到SLF4J的綁定,将強制SLF4J的調用配置設定給jul。另一方面,jul-to-slf4j.jar,加上SLF4JBridgeHandler的安裝,加上SLF4JBridgeHandler的安裝,通過調用“SLF4JBridgeHandler.install()“将jul記錄發送給SLF4J。是以,如果兩個jar檔案同時存在(SLF4JBridgeHandler已安裝),slf4的調用将被配置設定給jul, jul記錄将發送到SLF4J,導緻一個死循環。
各個庫單獨使用
- log4j
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
classpath下配置檔案log4j.properties
log4j.rootLogger=INFO,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n
使用:
import org.apache.log4j.Logger;
...
static final Logger LOGGER = Logger.getLogger(Main.class);
- log4j2
classpath下log4j2.properties
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n
使用:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
...
static final Logger LOGGER = LogManager.getLogger(Main.class);
- logback
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
classpath下logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="console" />
</root>
</configuration>
使用:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
注意:logback本身就是實作slf4j的,如上代碼中的logger本就是slf4j的。然而log4j 和 log4j2 的logger都不是slf4j的。當然是可以更新的
為啥要更新?是想要統一到一個門面标準更新:基礎庫如何更新到SLF4j标準log4j實作方式,引入slf4j-log4j12,即加一個擴充卡。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.29</version>
</dependency>
log4j2的實作方式,引入log4j-slf4j-impl ,也是加一個擴充卡
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.9.0</version>
</dependency>
這樣組裝後就可以用slf4j的寫法了,統一了門面标準。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
實際項目中,往往會依賴于其它的包,這些依賴包可能就是不同的日志實作方式
生産場景一:某項目依賴到的其餘三個包 的日志實作分别是 log4j、log4j2、logback 。
這個時候我們不做統一處理的話,當然是不會沖突 報錯的,隻是列印的日志時候就會各自找各自的日志實作。
其中一種方式是可以 在我們的項目的redources下,新增 log4j.properties / log4j2.properties 和 logback.xml 共3個配置檔案來自定義 (覆寫) 日志輸出方式 ,這樣,輸出的日志也是會按照我們配置的進行正确輸出。
生産場景二:某項目依賴到的其餘三個包 的日志實作分别是 log4j log4j2 logback 。但是log4j 和 log4j2 已經統一了門面标準為SLF4J
這個實作我們項目在列印日志的時候,就會根據類加載随機選擇一個日志實作,可能就是按照log4j的配置檔案實作方式進行日志的列印。
問題:這種情況如何指定特定的日志實作庫呢?假如期望項目使用logback,并期望統一為slf化的logback形式,隻配置一個logback.xml就能對所有依賴進行配置這個時候可以分别剔除A依賴包的和B依賴包。
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</exclusion>
</exclusions>
這個時候,隻需要在我們項目的resources目錄下配置一個logback.xml檔案即可。當然,很有可能我們在項目中是不清楚哪一個依賴包使用到了log4j 和 log4j2 的,這個時候我們是可以在 pom 檔案的最後 寫一個不存在的版本:
ps:maven加載方式按照最外層優先使用,如果引入一個不存在的,那麼maven就不會加載這個依賴了。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>99.99.99</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>99.99.99</version>
</dependency>
也是能夠達到去除slf化 ,進而使用統一的logback列印日志的效果的
生産場景三:某項目依賴到的其餘三個包 的日志實作分别是 log4j log4j2 logback 。但是log4j 和 log4j2 并沒有實作slf門面标準。但是又想要統一為slf化的logback形式。怎麼辦呢?
也就是說要先對log4j 和 log4j2 進行slf 化才行。
思路:
1.先在整個項目下吧原有的log4j 和log4j2 剔除掉
2.再使用其它已經橋接好的log4j 和 log4j2 的包來代替slf 化的log4j 和 log4j2 的包
以下配置幾乎是萬能的,當遇到問題的時候,直接全部拷貝進去,穩定解決。
<!-- 處理單獨log4j的依賴: -->
<!-- 用log4j-over-slf4j替換log4j,使依賴中的log4j也能"實作"slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.29</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>99.99.99</version>
</dependency>
<!-- 處理單獨log4j2的依賴: -->
<!-- 用log4j-to-slf4j替換log4j2,使依賴中的log4j2也能"實作"slf4j -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>99.99.99</version>
</dependency>
<!-- 處理slf化的log4j的依賴: -->
<!-- 因為slf選binding的時候有多個候選,為防止slf4j-log4j12選中,直接去掉他 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>99.99.99</version>
</dependency>
<!-- 處理slf化的log4j2的依賴: -->
<!-- 因為slf選binding的時候有多個候選,為防止log4j-slf4j-impl選中,直接去掉他 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>99.99.99</version>
</dependency>
<!-- 最後引個新版本的logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
小結
- slf4j-log4j12:與log4j聯合使用,用于使目前項目的log4j實作slf标準
- log4j-slf4j-impl:與log4j2聯合使用,用于使目前項目的log4j2實作slf标準
- log4j-over-slf4j:與剔除log4j聯合使用,替換log4j,使log4j實作slf。用于讓單獨用log4j的依賴能遵循slf,進而統一日志配置。
- log4j-to-slf4j:與剔除log4j2聯合使用,替換log4j2,使log4j2實作slf。用于讓單獨用log4j2的依賴能遵循slf,進而統一日志配置。