SLF4J 快速入門 / 繫結原理
官網: 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);
本文為博主原創文章,如需轉載請註明連結出處! 如有幫助到你,還請留言支援一下,謝謝:) 若有問題,也歡迎討論指正。