官網: http://www.slf4j.org/
GitHub: https://github.com/qos-ch/slf4j
一、簡介
SLF4J(Simple Logging Façade for Java)日志框架,是各種日志框架的簡單門面(simple facade)或抽象接口,允許用戶部署時選擇具體的日志實現。
相較於 JCL 有什么優點:
- 其在設計上簡單得多,因此也足夠健壯。
- 靜態綁定非常簡單 但足夠有效,解決了困擾 JCL 的類加載器(class loader)問題
- 參數化日志的增強,解決了重要的日志性能問題
- 在 org.slf4j.Logger 接口中,Marker 對象的引入為更進階的日志系統預留了空間;同時也允許切換回傳統的日志系統
二、需求及包引入
要求及說明:
- JDK版本要求:1.5+
- 向后兼容性:
slf4j-api 自身目前是向后兼容所有版本的,意味着可以從 1.0 升至任意更新版本。
但根據slf4j-api版本不同,具體到綁定層上,則可能需要特定版本的綁定。例如 slf4j-api-1.5.6 需使用 slf4j-simple-1.5.6 而 slf4j-simple-1.4.2 將無法工作。 - 包依賴整體邏輯,參考:Java 日志框架概述(slf4j / log4j / JUL / Common-logging(JCL) / logback)
- 所有綁定層/橋接層庫,均隨 SLF4J 一起發布,可在此處尋找: https://github.com/qos-ch/slf4j
簡單示例:引入 SLF4j API 及其實現(以logback為例)
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
三、SLF4J API 簡單使用示例
- 聲明及獲取 LOGGER
private static final Logger LOGGER = LoggerFactory.getLogger(DistrictController.class);
- 打印日志
LOGGER.debug("Attempted to do something in Obj {}", district);
四、更多相關知識
1. 日志等級
分為5個等級,可見 org.slf4j.event
- ERROR
- WARN
- INFO
- DEBUG
- TRACE
2. SLF4J 日志接口設計
- 日志打印接口
slf4j 以"日志等級"作為方法名,並至少接受一個 String 類型的消息描述,如:
至於為什么不直接接受 Object ,參考: http://www.slf4j.org/faq.html#string_or_objectdebug(String msg) debug(String format, Object arg) debug(String msg, Throwable t) …
- 模板/參數化消息
SLFJ4J 為消息提供了參數化(如下圖),以解決debug("hello" + "world")
這種使用方式無論是否啟用該等級日志都會進行字符串連接"的問題,避免日志開銷(據官方講30倍的開銷)
消息中參數占位符(formatting anchor):{}
例:logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);
更多性能 / 轉義 / 匹配規則信息,可參考: http://www.slf4j.org/faq.html#logging_performance
五、綁定實現原理
- 在第一次調用 LoggerFactory#getLogger 時,會嘗試調用 LoggerFactory#bind 進行日志工廠的初始化。
- 根據版本不同,實現綁定的方式也不一致。但並非網上所謂的“編譯時綁定”這么高深,關於這種說法后面會解釋。
綁定這種術語其實本身就是slf4j官方自創的-
在 slf4j 1.8 版本之前:LoggerFactory#bind 基於 COC(Convention over Configuration)的思想,約定大於配置,單純調用 org.slf4j.impl.StaticLoggerBinder#getSingleton 來初始化。
但實際上 slf4j-api 根本不包含此類,而是由各實現/綁定包(如 slf4j-log4j)來提供
源碼如下:import org.slf4j.impl.StaticLoggerBinder private final static void bind() { try { StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; } catch (NoClassDefFoundError ncde) { INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); Util.report("Defaulting to no-operation (NOP) logger implementation"); Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); } }
這也是為什么官方特別說明,放且僅放一個綁定,不要在類路徑上放置多個綁定,因為會沖突。(you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path. Here is a graphical illustration of the general idea)
-
在 slf4j 1.8 之后:采用 Java SPI (Service Provider Interface) 機制
private final static void bind() { List<SLF4JServiceProvider> providersList = findServiceProviders(); if (providersList != null && !providersList.isEmpty()) { PROVIDER = providersList.get(0); PROVIDER.initialize(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; } } private static List<SLF4JServiceProvider> findServiceProviders() { ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>(); for (SLF4JServiceProvider provider : serviceLoader) { providerList.add(provider); } return providerList; }
以 slf4j-log4j12 為例,基於 SPI 實現了 SLF4JServiceProvider
-
六、常見問題
- 網上為什么說“編譯時綁定”
答:雖然有些迷惑的說法,但也並不是毫無道理。原因如下:- 官方的說法自身就很具誤導性,感覺是有意的:
"In fact, each SLF4J binding is hardwired at compile time to use one and only one specific logging framework. For example, the slf4j-log4j12-1.7.28.jar binding is bound at compile time to use log4j” - 雖有些不太准確,但 1.8 以前時的“靜態綁定”一定程度上也能解釋為編譯時綁定。
- 官方的說法自身就很具誤導性,感覺是有意的:
- 是否應該將類的 Logger 成員聲明為靜態?
http://www.slf4j.org/faq.html#declared_static
七、相關踩坑
- 引用調用SLF4J API 運行報錯
答:SLF4J只是接口層,未找到實現則默認的空實現(nop)【1.6 開始】,並打印警告SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
可引入 SLF4J 實現/綁定層,如:<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> <scop>test</scop> </dependency>
- 在多個參數情況下,若想打印 Throwable 堆棧信息,需注意Throwable必須放在最后一個
例:logger.error("錯誤消息:{}",e.getMessage(),e);