1. 程式人生 > >《SLF4J官方文件》SLF4J-FAQ 常見問題解答

《SLF4J官方文件》SLF4J-FAQ 常見問題解答

原文地址

一般性問題

  1. 什麼是SLF4J?
  2. 什麼時候應該使用SLF4J?
  3. SLF4J仍是另一個日誌外觀嗎?
  4. 如果SLF4J可修復JCL,那為什麼不在JCL里加入修復而是建立一個新專案?
  5. 使用SLF4J時,我必須重新編譯我的應用以轉換到一個不同的日誌系統嗎?
  6. SLF4J的要求是什麼?
  7. SLF4J向後相容版本嗎?
  8. 使用SLF4J時遇到訪問許可權錯誤,原因是什麼?
  9. 為什麼SLF4J是在X11型別許可證下許可而不是Apache軟體許可?

10.在哪裡能獲得特定的SLF4J繫結?

11.我的庫應該嘗試配置logging嗎?

12.為了減小我們軟體的依賴庫數量,我們想讓SLF4J成為一個可選擇的依賴包。這個是好主意嗎?

13.Maven傳遞依賴怎麼樣?

14.如何排除依賴Maven的commons-logging?

關於SLF4J API

  1. 為什麼日誌器介面的列印方法不接收物件型別的訊息,但只收String型別訊息?
  2. 我可以輸出一個沒有訊息的異常日誌嗎?
  3. 輸出日誌(關閉日誌)的最快方法是什麼?
  4. 如何列印單獨(可能是複雜的)物件的字串內容日誌?
  5. 為什麼slf4j.Logger介面沒有FATAL級別的方法?
  6. 為什麼TRACED級別的日誌只在SLF4J 1.4.0版本介紹?
  7. SLF4J日誌API支援I18N(國際化)嗎?
  8. 不通過LoggerFactory裡的靜態方法,可以重新獲取日誌器嗎?
  9. 存在錯誤/丟擲異常時,可以引數化日誌宣告嗎?

實現SLF4J API

  1. 如何使我的日誌底層SLF4J可相容?
  2. 如何使我的日誌系統新增支援Marker介面?
  3. SLF4J的版本檢查機制如何工作?

關於日誌的一般性問題

1.類的日誌器成員變數應該宣告為靜態變數嗎?

2.類裡的宣告日誌器有推薦的習語嗎?

一般性問題

1.什麼是SLF4J?

SLF4J是一個簡單的日誌系統外觀,它允許終端使用者在部署時植入所需日誌系統。

2.什麼時候應該使用SLF4J?

簡單而言,為庫和其他嵌入元件的日誌需求,它們應該考慮下SLF4J,因為庫不能強加終端使用者日誌框架的選擇。另外,獨立應用使用SLF4J不一定有意義。獨立應用可以直接援引他們選擇的日誌框架。對於logback,問題是沒有實際意義的,因為logback通過SLF4J暴露它的logger API。

SLF4J只是個外觀,意味著它不提供一個完整的日誌方案。例如配置輸出源或者設定日誌級別的操作不能和SLF4J執行。因此,在某個時間點上,任何微不足道的應用將需要直接援引底層日誌系統。換句話說,完全對立的API底層日誌系統是不可能給獨立應用程式使用的。然而,SLF4J減少這種依賴的影響到無痛苦級別。

假設你的CRM應用用log4j來記錄日誌。然後,你的一個重要客戶端請求日誌要通過JDK1.4執行日誌記錄。如果你的應用充斥著數以千計的直接log4j呼叫,轉移到JDK1.4的呼叫將相對是一個冗長和容易出錯的過程。更糟的是,你可能需要維護兩個版本的CRM軟體。你已經引用SLF4J API而不失log4J,通過替換二者的jar檔案,遷移可在幾分鐘內完成。

SLF4J允許元件開發人員推遲終端使用者的日誌系統的選擇,但最終仍需要做出一個選擇。

3.SLF4J仍是另一個日誌外觀嗎?

SLF4J在概念上和JCL非常相似。因此它可以被認為是另一個日誌外觀。但是,SLF4J在設計上更簡單和更健壯。簡而言之,SLF4J避免了植入JCL的類載入問題。

4.如果SLF4J可修復JCL,那為什麼不在JCL里加入修復而是建立一個新專案?

這是非常好的問題。首先,SLF4J靜態繫結方法非常簡單,甚至很可笑。很難說服開發者這個方法的有效性。僅僅在SLF4J釋出後和開始變得被接收後,它才在相關社群裡獲得尊重。

其次,SLF4J提出2個改進手段,這都趨向被低估了。用務實的方法,引數化日誌訊息解決了一個跟日誌效能聯絡的重要問題。標記物件,由org.slf4j.Logger介面支援,為高階日誌系統的採用鋪路,如果必要的話,同時讓放開轉回更多傳統日誌系統的大門。

5.使用SLF4J時,我必須重新編譯我的應用以轉換到一個不同的日誌系統嗎?

不,你不需要重新編譯你的應用。你可以通過移除之前的SLF4J繫結,然後將它替換為你選擇的繫結,就能轉換到不同的日誌系統了。

例如,如果你在使用NOP實現,希望轉換到log4j 1.2版本,在你的類目錄下用 slf4j-log4j12.jar 替換slf4j-nop.jar,但也別忘了新增log4j-1.2.x.jar 。像轉換到JDK 1.4 logging?只需用slf4j-jdk14.jar替換slf4j-log4j12.jar

6.SLF4J的要求是什麼?

1.7.0版本SLF4J需要JDK1.5以上。早期的SLF4J版本,像SLF4J 1.4,1.5和1.6,要求JDK1.4。

Binding Requirements
slf4j-nop JDK 1.5
slf4j-simple JDK 1.5
slf4j-log4j12 JDK 1.5, plus any other library dependencies required by the log4j appenders in use加入任何其他所需的依賴包取決於使用的log4j輸出源
slf4j-jdk14 JDK 1.5 or later
logback-classic JDK 1.5 or later, plus any other library dependencies required by the logbackappenders in use加入任何其他所需的依賴包取決於使用的log4j輸出源

7.SLF4J向後相容版本嗎?

從客戶端角度,SLF4J API對所有版本都向後相容。這意味著你能從SLF4J版本1.0升級到任何以後的版本,沒有任何問題。對於slf4j-api版本N和slf4j-api

版本M, 版本N編譯的程式碼將和版本M編譯的程式碼一起正常運作。迄今為止, slf4j-api裡的二進位制相容性從來沒有被打破。

然而,從客戶端角度,SLF4J API非常穩定時,SLF4J繫結,比如slf4j-simple.jar

或slf4j-log4j12.jar,需要一個特定版本的SLF4J-API。混合不同版本的slf4j可能會出現問題和強烈不提倡的。例如,如果你正在使用slf4j-api-1.5.6.jar,然後你也應該使用slf4j-simple-1.5.6.jar,使用slf4j-simple-1.4.2.jar將不會工作。

在初始化時時,如果SLF4J推測可能有版本不匹配問題,它將發出一個關於不匹配的警告。

8.使用SLF4J時遇到訪問許可權錯誤,原因是什麼?

如下是錯誤詳情:

Exception in thread "main" java.lang.IllegalAccessError: tried to access field
org.slf4j.impl.StaticLoggerBinder.SINGLETON from class
org.slf4j.LoggerFactory    at
org.slf4j.LoggerFactory.<clinit>(LoggerFactory.java:60)

這個問題是由LoggerFactory類的靜態初始化嘗試直接訪問org.slf4j.impl.StaticLoggerBinder的單例變數造成的。這在SLF4J 1.5.5和之前的版本允許,在1.5.6及以後的版本,單例變數已經被標記為私有訪問許可權了。

如果你遇到了上述的錯誤,你可以用老版本的slf4j-api,比如1.4.3,同時使用一個新版本的slf4j繫結,比如1.5.6。通常,當你的Maven pom.xml檔案使用hibernate 3.3.0,在slf4j-api 1.4.2版本上宣告一個依賴時,將發生上述異常。如果你的pom.xml在slf4j繫結上宣告依賴,比方說slf4j-log4j12 版本1.5.6,你會觸發非法訪問錯誤。

檢視Maven裡那個slf4j-api版本被拉入,用下述maven依賴植入。

mvndependency:tree

如果你使用Eclipse,請不要依靠m2eclipse顯示的關係樹。
如果你的pom.xml明確在slf4j-api上聲明瞭依賴,這個版本的api匹配已宣告的繫結,將使這個問題消失。
也請讀向後相容性的FAQ,以獲得更多普遍性解釋。
9.為什麼SLF4J是在X11型別許可證下許可而不是Apache軟體許可?
SLF4J是在X11型別許可證下許可而不是Apache軟體許可,這是因為X11許可是被Apache軟體基金會以及自由軟體基金會的各自許可認為是科相容的。
10.在哪裡能獲得特定的SLF4J繫結?

SimpleLoggerNOPLoggerog4jLoggerAdapterJDK14LoggerAdapter 的SLF4J繫結包含在slf4j-nop.jarslf4j-simple.jar,slf4j-log4j12.jar, and slf4j-jdk14.jar檔案裡。

給logback-classic的繫結附帶在logback distribution(http://logback.qos.ch/download.html)上。對於其他所有的繫結,logback-classice繫結需要slf4j-api.jar。

11.我的庫應該嘗試配置logging嗎?

嵌入的元件,比如庫,不進不需要配置底層日誌框架,它們也不應該做這些。它們應該引入SLF4j來記錄日誌,但也應該讓終端使用者配置日誌環境。當嵌入的元件嘗試在自己配置日誌時,它們常常會覆蓋終端使用者的意願。在一天的結束時,終端使用者不得不讀日誌,處理日誌。她應該是決定自己想要的日誌配置的人。

12.為了減小我們軟體的依賴庫數量,我們想讓SLF4J成為一個可選擇的依賴包。這個是好主意嗎?

當一個軟體工程到達它需要設計日誌策略的點時,這個問題會提起 。

讓Wombat成為一個具有極少依賴的軟體庫。如果SLF4J被選作為Wombat的日誌API,那麼一個新的slf4j-api.jar依賴庫將被加入帶Wombat的依賴包列表中。由於寫日誌的包裝並不難,一些開發者冒險包裝SLF4J,只有當它已經出現在類路徑中時連結它,使得SLF4J成為Wombat的一個可選擇的依賴庫。為了解決這個依賴問題,包裝將被從SLF4J的API中隔離Wombat,以確保Wombat中的日誌不會過時的。

另外,任何SLF4J-包裝都取決於SLF4J得定義。它們一定有相同的通用api。在將來,如果出現一個新的、明顯不同的日誌API,和直接使用SLF4J的程式碼一樣,使用包裝器的程式碼也將同樣很難移植到新的API.因此,包裝器不可能是面向未來的程式碼,但在SLF4J上增加了一個額外的間接連線,使得這更復雜,這本身就是一中間接連線。

脆弱性增加實際上比這個還遭。包裝器需要依賴隨時變化的一些內部SLF4J介面,這違背了面向客戶端API永不改變的原則。因此,包裝器通常依賴一些同他們一起編譯的主要版本。針對SLF4J 1.5.x版本編譯的包裝器將不會同SLF4J 版本1.6工作,但是使用org.slf4j.Logger, LoggerFactory, MarkerFactory, org.slf4j.Marker和MDC的客戶端程式碼將和任何版本SLF4J 1.0及以後的版本正常工作。合理地假設在大部分工程中,Wombat將是許多依賴庫中的一個。如果每個依賴庫有自己日誌包裝器,那麼大概每個包裝器需要獨立配置。因此,不只是處理SLF4J的日誌框架,Wombat的使用者也必須處理Wombat的日誌包裝器。

為了使SLF4J可選擇,每個框架提出的自己的包裝器將加劇這個問題。(配置或處理5個不同的日誌包裝器將很不愉快,也不招人喜歡)

Velocity專案採納的日誌策略 是一個“自定義日誌抽象”反模式的好例子。通過採用一個獨立的日誌抽象的策略,Velocity開發者已經使自己的開發更復雜,但更重要的是,他們讓開發對他們的使用者更難。

一些專案試著在類路勁上檢測SLF4J的存在性,如果存在的話就轉換到SLF4J.雖然這個方法看上去足夠的透明,但是它將導致錯誤的位置資訊。底層日誌框架將列印包裝器的位置(類名和行數)而不是列印真正呼叫者的位置。還有就是除了引數化日誌、像SLF4J支援MDC和標記的API覆蓋問題。儘管有人可以在數小時內提出一個似乎可執行的SLF4J包裝器,隨著時間的推移,許多技術問題將出現,這是Wombat開發者不得不處理的問題。注意SLF4J已經發展了好幾年了,有260個bug報告提起它。

基於以上原因,框架的開發者應該抵制自己寫的日誌包裝的誘惑。 它不僅是浪費開發者時間,對上述日誌的使用者,這實際上讓開發更困難,使日誌程式碼自相矛盾地更容易改變。

13.Maven傳遞依賴怎麼樣?

作為使用Maven構建庫作者,你可能想使用繫結來測試你的應用程式,比如SLF4J-log4j12或的logback經典,對使用者不強制log4j或logback-classic作為依賴。 這是比較容易做到的。

基於你的庫的程式碼依賴於SLF4J API,你需要宣告SLF4J的API作為一個編譯時(預設範圍)的依賴。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.21</version>
</dependency>

通過宣告SLF4J繫結依賴為“測試”的範圍,可以實現限制在測試繫結使用的SLF4J的傳遞。例如:

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.21</version>
  <scope>test</scope>
</dependency>

因此,至於你的使用者要匯出SLF4J的API作為庫的傳遞依賴,但不是任何SLF4J繫結或任何底層日誌系統。

需要注意的是由於SLF4J 1.6版,在沒有SLF4J繫結,SLF4J的API將預設為無無操作實現。

14.如何排除依賴Maven的commons-logging?

替代1明確排除

很多使用Maven的軟體專案宣告commons-logging作為一個依賴。因此,如果你希望遷移到SLF4J或使用jcl-over-slf4j,你需要排除專案中所有依賴裡的comms-logging,這些依賴庫傳遞依賴common-logging。依賴排除 在Maven檔案裡已有描述。為分佈在幾個pom.xml裡的多個依賴明確地排除common-logging,可能是一個笨重的和相對容易出錯的過程。

替代2規定的範圍

在專案的pom.xml檔案中所提供的範圍內,通過宣告Commons-logging,它可以像依賴一樣簡單、方便地排除。實際的commons-logging類將由jcl-over-slf4j提供。 這轉化為以下POM檔案片段:

<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.1.1</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
  <version>1.7.21</version>
  </dependency>

第一依賴性宣告本質規定commons-logging會通過你的環境“以某種方式”提供。第二個宣告引入jcl-over-slf4j到你的專案中。 作為jcl-over-slf4j是commons-logging完美的二進位制相容更換,第一個斷言為真。

不幸的是,雖然在規定範圍內宣告的commons-logging能夠完成任務,你的IDE,比如Eclipse中,仍將在專案的類路徑上放置commons-logging.jar,通過你的IDE可以看到。 您將需要確保jcl-over-slf4j前,您的IDE 的commons-logging.jar的是可見的。

替代3空構件

另一種方法是依靠一個空的commons-logging.jar構件。這個聰明的辦法首先 由Erik van Oosten想出和最初支援。

空構件可從一個http://version99.qos.ch高可用性Maven倉庫,複製在位於不同地域的多個主機。

下面的宣告新增version99庫設定的Maven的搜尋遠端資源庫。 該儲存庫中包含的commons-logging和log4j的空構件。 順便說一句,如果你使用的version99庫,請在<version99 AT qos.ch>給我們一行。

<repositories>
  <repository>
    <id>version99</id>
    <!-- highly available repository serving empty artifacts -->
    <url>http://version99.qos.ch/</url>
  </repository>
</repositories>

在你的專案裡的<dependencyManagement>部分,宣告99-empty版本中的commons-logging的將為commons-logging指向所有傳遞的依賴以輸入版本9999-empty,從而很好地解決的commons-logging排斥問題。對於commons-logging的類將由jcl-over-slf4j提供。 以下行宣告的commons-logging版本99-empty(在依賴管理部分),並宣告jcl-over-slf4j作為一個依賴。

<dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>commons-logging</groupId>
       <artifactId>commons-logging</artifactId>
       <version><strong>99-empty</strong></version>
     </dependency>
     ... other declarations...
   </dependencies>
 </dependencyManagement>

 <!-- Do not forget to declare a dependency on jcl-over-slf4j in the        -->
 <!-- dependencies section. Note that the dependency on commons-logging     -->
 <!-- will be imported transitively. You don't have to declare it yourself. -->
 <dependencies>
   <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>jcl-over-slf4j</artifactId>
     <version>1.7.21</version>
   </dependency>
   ... other dependency declarations
 </dependencies>

關於SLF4J API

1.為什麼日誌器介面的列印方法不接收物件型別的訊息,但只接收String型別訊息?

在SLF4J 1.0beta4版裡,Logger interface 中例如debug(),info(),warn()的列印方法被修改以用於只接收String型別的訊息,而不是Object型別的訊息。

因此,DEBUG級別的列印方法設定變為:

debug(Stringmsg);
debug(String format,Objectarg);
debug(String format,Object arg1,Object arg2);
debug(Stringmsg,Throwable t);

之前,上述方法中的第一個引數是Object型別。

這個改變強制使人認為日誌系統是關於裝飾和處理String型別訊息,而不是關於任何(Object)型別。

同樣重要的是,新的一套簽名方法提供過載方法間的透明區別,而以前選擇 被呼叫的Java方法,由於Java的過載規則通常總是不易遵循。

很容易犯錯誤。例如,之前這樣寫是合法的:

logger.debug(newException("some error"));

不幸的是,上述呼叫不會列印堆疊跟蹤的異常。因此,潛在的關鍵資訊片可能會丟失。當第一個引數被限制為String型別時,只有如下方法: debug(Stringmsg,Throwable t);

可用於記錄異常,注意,此方法可確保每個日誌異常是伴隨著一個描述性的訊息。

2.我可以輸出一個沒有伴隨訊息的異常日誌嗎?

簡言之,不可以。

如果e是一個異常,你想輸出一個ERROR級別的異常,你必須增加一個伴隨訊息。例如:

logger.debug(newException("some error"));

你可能合理地辯解不是所有的異常都有一個有含義的訊息伴隨著它們。此外,良好的異常應該已經包含一個自我解釋說明。因此,伴隨的訊息可以被認為是多餘的。

當這些引數是有效引數時,有3個相反的引數也是值得考慮的。首先,在很多場合,但不是所有場合,伴隨訊息可以很好滴傳達有用的資訊,在異常裡補充描述。通常情況下,在其中異常日誌點,開發者獲得比在異常被丟擲點更多的上下文資訊。其次,很難或多或少想象普通訊息,例如”Exception caught”, “Exception follows”,可能會用作給錯誤呼叫的第一個引數(String msg,Throwable t)。第三,許多日誌輸出格式顯示一行訊息,在緊跟著的一行輸出異常。因此,訊息行沒有訊息,看起來不一致。

簡言之,如果允許使用者列印一個沒有伴隨訊息的異常,這將是日誌系統的工作:創造一個訊息。這實際上是java.util.logging包中的throwing方法(String sourceClass, String sourceMethod, Throwable thrown) 所做的。(它取決於自身的伴隨訊息是字串“THROW”。)

開始時可能會奇怪地要求一個伴隨訊息以記錄一個異常。 然而,這是在所有的log4j的衍生系統,如java.util.logging,logkit等,當然還有的log4j本身的通行做法。 如此看來,目前的共識認為需要一個伴隨的訊息是一個很好的做法(TM)。

3.輸出日誌(關閉日誌)的最快方法是什麼?

SLF4J提出一個叫引數化日誌的高階特點,可以顯著提升有缺陷的日誌語句的日誌效能。

對於一些Logger logger,寫作,

logger.debug("Entry number: "+i+" is "+String.valueOf(entry[i]));

引起構造訊息引數的開銷,就是轉換integer i和entry[i]為String,和連線中間的字串。這一點,不管訊息是否記錄。

一種可能的方法,以避免構造引數的開銷是通過測試中包裹日誌語句。例如

if(logger.isDebugEnabled()){
logger.debug("Entry number: "+i+" is "+String.valueOf(entry[i]));
}

如果logger關閉除錯,這樣的話就不會引起構造引數的開銷。在另一方面,如果logger對DEBUG級別開啟,無論logger是否開啟,都會引起開銷,而且是兩次:一次是debugEnable開銷,一次是debug裡的開銷。這是微不足道的開銷,因為評估日誌器花費的時間不到實際過程中記錄語句的時間的1%。

更好的是,使用引數化的訊息

這裡存在一個基於訊息格式的很方便的選擇。假設entry是一個物件,你可以這樣寫:

Object entry =newSomeObject();
logger.debug("The entry is {}.", entry);

在評價是否記錄日誌後,只有當結論是肯定的時候,日誌器實現將格式化訊息,並將’{}’替換為entry的字元值。換句話說,假設日誌宣告是關閉的,這個格式不會引起構建引數的開銷,

下面兩行程式碼將產生確切相同的輸出。然而,假設關閉日誌宣告,第二種形式將將至少超過第一種形式的30倍。

logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);

雙引數 版本也是可用的。例如,你可以這樣寫:

logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);

如果需要傳遞3個或更多的引數時,你要使用Object…variant 列印方法。例如,你可以這樣寫:

logger.debug("The new entry is {}. It replaces {}.", entry,oldEntry);

這中形式引起隱藏的構建一個Objec[](物件陣列)的開銷,通常情況下這個開銷是很小的。一個和兩個引數版本的這種形式不會引起這個隱藏的開銷,同時由於這個原因(效率)會完全存在。只有Object…variant的情況下,Slf4j-api會更小/更簡潔。

陣列形式的引數,包括多維陣列,也是支援的。

SLF4J使用自身的訊息格式實現,這個和java平臺的格式化輸出是不一樣的。已被證明的事實是:SLF4J的實現平臺效能是java平臺的十倍,但開銷會無標準,彈性也會更小。

擺脫“{}”對

“{}”對叫做格式化錨。它用於指定位置:指定需要用訊息樣式替換的引數的地方。

SLF4J值關心格式化錨,這就是“{”字元立即跟著字元“}”。因此,如果你的訊息包含“{”或“}”字元,不需要做任何特殊處理,除非“}”立即跟著“}”字元。例如:

logger.debug("Set {1,2} differs from {}","3");

這將列印“Set {1,2} differs from 3”。

在極其罕見的情況下,“{}”對很自然地出現在你的文字中,你希望禁用格式化錨的特殊意義,你需要將“{”字元替換為“\”,就是反斜槓字元。

只有“\”應該替換。不需要替換“}”字元。例如:

logger.debug("Set \\{} differs from {}","3");

將會列印為“Set {} differs from 3”。注意在java程式碼裡,反斜槓字元需要寫為“\\”。

在少見的情況下:“\{}”在訊息中出現,你可以使用兩個反斜槓,這樣它才會保持它原本的含義。例如:

logger.debug("File name is C:\\\\{}.","file.zip");

將會列印“File name is C:\file.zip”。
4.如何列印單獨(可能是複雜的)物件的字串內容日誌?
需要以字串格式記錄物件的訊息,這是相對少見的格式,那麼可以使用適當級別的引數化列印方法。假設complexObject是一個已確定複雜度的物件,對於一個DEBUG級別的日子語句。你可以這樣寫:

logger.debug("{}",complexObject);

在日誌系統已查明日誌系統已開啟後,它將呼叫complexObject.toString()方法。否則,complexObject.toString()轉換的開銷將有力地避免。
5.為什麼org.slf4j.Logger介面沒有FATAL級別的方法?
標記介面,是org.slf4j包的一部分,指出FATAL級別很大程度上是多餘的。如果一個給定的錯誤需要的注意超過了分配給普通錯誤的注意,使用特殊指定的標記來簡單標記日誌語句就可以命名“FATAL”或其他你喜歡的名字.
例如:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

classBar{
 void foo(){
   Marker fatal =MarkerFactory.getMarker("FATAL");
   Loggerlogger=LoggerFactory.getLogger("aLogger");

   try{
     ... obtain a JDBC connection
   }catch(JDBException e){
     logger.error(fatal,"Failed to obtain JDBC connection", e);
   }
 }
}

雖然標記是SLF4J API的一部分,只有l​​ogback支援現成的標記。例如,如果你新增%marker轉換字到它的樣式中,logback的PatterenLayout將新增標記資料到它的輸出口。標記資料可以用於過濾訊息 ,或者甚至在單個事務結束後 發出 電子郵件。
在日誌框架的組合體中,例如log4j和java.util.logging,是不支援標記的。標記資料將被默默地忽略掉。
相比於五個值,即錯誤、警告、資訊,除錯和跟蹤,這是所允許的日誌級別,為了處理日誌語句,標記增加了一個新的具有無限可能值的維度。目前。只有logback支援標記資料。然而,沒有什麼阻止其他日誌框架使用標記資料。

6.為什麼TRACED級別的日誌只在SLF4J 1.4.0版本介紹?

新增TRACED級別已成為頻繁和激烈討論的要求。通過研究各種專案,我們得出TRACE級別被用於某些類禁用日誌輸出,而不需要為這些類配置日誌。實際上,在log4j和logback裡,TRACE級別預設是禁止,這在大部分其他日誌系統中也是禁止的。在配置檔案裡增加合適的指令,相同的結果也可以獲得到。

因此,在許多情況下,TRACE級別像DEBUG級別一樣帶有相同的語意含義。在這些情況下,TRACE級別只是儲存一些配置指令。在其他更有趣的場合,TRACE級別相比於DEBUG級別帶有不同的含義,Marker物件可以用於傳遞所需的含義。然而,如果用標記你不被打擾,希望使用比DEBUG級別低的日誌級別,那麼TRACE級別可以完成這個任務。

注意,雖然評估禁用日誌請求的開銷是在幾納秒內。在密集的迴圈中,使用TRACE級別(或此種事務下的任何其他日誌級別)是不鼓勵的,這裡日誌請求可能被計算數百萬次。如果日誌請求是開啟的,那麼它將用大量輸出來淹沒目標位置。如果請求被禁用,它將浪費資源。

簡言之,儘管我們仍不鼓勵使用TRACE級別日誌,因為存在可選擇的日誌級別,或者在許多情況裡,TRACE級別的日誌請求是浪費的,由於人們一直在問它,我們決定屈服於大眾的需求。

7.SLF4J日誌API支援I18N(國際化)嗎?

是的,作為的1.5.9版本,SLF4J附帶一個包叫做org.slf4j.cal10n,它增加了本地化/國際化日誌記錄 的支援,在內建薄薄的一層CAL10N API .

8.不通過LoggerFactory裡的靜態方法,可以重新獲取日誌器嗎?

是的, LoggerFactory本質上是一個圍繞ILoggerFactory例項的包裝。使用ILoggerFactory例項是由SLF4J底層的靜態繫結約定確定的。檢視LoggerFactory的getSingletong() 方法獲取詳情 .

然而,沒有什麼能阻止你使用自己的ILoggerFactory例項。注意,通過呼叫LoggerFactory.getILoggerFactory() 方法,你也可以獲得LoggerFactory類使用的ILoggerFactoyr的一個引用。

因此,如果SLF4J繫結約定不符合你的需求,或者你需要額外的拓展性,那麼請考慮使用ILoggerFactoyrj介面,作為你自己創造的日誌API的一個可選擇介面。

9.存在錯誤/丟擲異常時,可以引數化日誌宣告嗎?

是的,像SLF4J1.6.0版本一樣,但這之前的版本里不是這樣。在異常出現的地方,SLF4J API支援引數化,假設異常時最後的引數嗎。因此,

String s = "Hello world";
try {
  Integer i = Integer.valueOf(s);
} catch (NumberFormatException e) {
  logger.error("Failed to format {}", s, e);
 }

如期望的一樣,將會列印NumberFormatException,同時也包括它的棧追蹤資訊。Java編譯器將呼叫傳入一個字串和兩個物件變數的error方法 。SLF4J按照程式猿的最大可能意圖,將像一個丟擲異常一樣解釋NumberFOrmatException例項,而不是簡單的忽略掉。

如果異常不是最後的變數,它將被認為是一個平常的物件,它的棧資訊將不會被列印。當然,這種情況在實際操作中應該不會發生。

實現SLF4J API

1.如何使我的日誌底層框架SLF4J有可相容性?

為SLF4J新增支援是So Easy的一件事。本質上,你複製一個已存在的繫結,裁剪繫結的一點點就完成這個小伎倆(如下解釋)。

假設你的日誌系統有日誌器的概念,稱為MyLogger,你需要為MyLogger到org.slf4.Logger介面提供一個介面卡。參考slf4j,slf4j-jdk14,和slf4j-log4j12模組中介面卡的例子。

一旦你寫完一個合適的介面卡,叫MyLoggerAdapter,你需要提供一個工廠類,它實現org.slf4j.IloggerFactory介面。這個工廠應該返回MyLoggerAdater的例項。使MyLoggerFactoyr是你工廠類的名字。

一旦有了名為MyLoggerAdater的介面卡,和一個名為MyLoggerFactoyr的工廠類,最後剩下的步驟就是改變StaticLoggerBinder類,這樣它會返回一個MyLoggerFactory新例項。你也需要修改loggerFactoryClassStr變數。

對於Marker(標記)或MDC支援,你可以使用已有的NOP(slf4j.nop.jar是sl4f-api.jar其相應的介面實現)實現中的一個。

綜上,為你的日誌系統建立一個SLF4J繫結,步驟如下:

  1. 開始複製一個已有的模組,
  2. 建立一個基於你日誌系統和slf4j.Logger介面間的介面卡
  3. 為上一步驟裡的介面卡建立一個工廠類
  4. 修改StatciLoggerBinder類,以使用上一步驟建立的工廠類。

2.如何使我的日誌系統新增支援Marker介面?

標記設立了一個革命性的概念,這是由logback支援的,但其他已有日誌系統是不支援這個的。所以,SLF4J相容日誌系統允許忽略使用者通過標記資料。

然而,即使標記資料可能被忽略,使用者仍必須被允許指定標記資料。否則,使用者將不能在支援標記的日誌系統和不支援標記的日誌系統間切換。

MakrerIgnoringBase類可作為介面卡或缺少標誌支援的日誌系統的本地實現的基類。在MarkerIgnoringBase類裡,帶有標記資料的方法簡單地呼叫沒有標記引數、丟棄作為引數傳入的任何標記資料的相關方法,你的SLF4J介面卡可以繼承MakrerIgnoringBase類來快速實現org.slf4j.Logger裡的方法,org.slf4j.Logger需要標記作為第一個引數。

3.SLF4J的版本檢查機制如何工作?

SLF4J初始化期間,它執行的版本檢查是可選過程。相容的SLF4J實現可選擇不不參加,在這種情況下,將不會執行版本檢查。

然而,如果SLF4J實現決定參與,那麼它需要宣告一個叫REQUESTED_API_VERSION的變數,同時得在StaticLoggerBinder類的拷貝里宣告。次變數的值應該等於slf4j-api編譯的版本。如果實現被更新到新的slf4j-api版本,那麼你也需要更新REQUESTED_API_VERSION的值。

對每一個版本,SLF4J API都保持相容版本的列表。如果請求的版本在相容列表中沒被找到,SLF4J將發出一個單本不匹配警告。即使SLF4J繫結比SLF4J有一個不同的釋出,假設每6到12個月,你更新SLF4J版本,你仍可以在沒觸發版本不匹配的情況下,參與版本檢查。例如,logback又一個不同的釋出日程,但是仍然會參與版本檢查。

對於SLF4J 1.5.5,所有繫結在SLF4J分佈,例如slf4j-log4j12,slf4j-simple和slf4j-jdk14裡傳遞,宣告REQUESTED_API_VERSION,它的值等於它們的SLF4J版本。它遵循的是,例如如果slf4j-simple-1.5.8與 slf4k-api-1.6.0,jar混合使用,基於1.5.8不在SLF4J版本1.6.x的相容列表,將會觸發版本不匹配警告。

注意,1.5.5之前的SLF4J版本沒有版本檢查機制。只有slf4j-api-1.5.5.jar以後的版本可觸發版本不匹配警告。

關於日誌的一般性問題

1.類的日誌器成員變數應該宣告為靜態變數嗎?

我們通常推薦的是loggers成員宣告為例項變數,而不是靜態變數。進一步分析後,在其他方法上,我們不再推薦一種方法。

下面是每一個方法的有點和缺點。

Advantages for declaring loggers as static宣告logger為靜態變數的優點 Disadvantages for declaring loggers as static宣告logger為靜態變數的缺點
1.Possible to take advantage of repository selectors even for libraries shared between applications. However, repository selectors only work if the underlying logging system is logback-classic. Repository selectors do not work for the SLF4J+log4j combination.2.IOC-friendly1.   通用、很好建立的慣用語句2.   更小的CPU開銷:在主機類初始化過程中,loggers獲取和分配只要一次3.   更小的記憶體開銷:logger宣告每個類消耗一個引用 1.For libraries shared between applications, not possible to take advantage of repository selectors. It should be noted that if the SLF4J binding and the underlying API ships with each application (not shared between applications), then each application will still have its own logging environment.2.not IOC-friendly1.在應用間共享庫,不能發揮倉庫選擇器的優勢。應該注意,如果SLF4J繫結和底層API附帶每一個應用(不在應用間分享),那麼每個應用仍將有自己的日誌環境。2.IOC不友善
Advantages for declaring loggers as instance variables宣告loggers為例項變數的優勢 Disadvantages for declaring loggers as instance variables宣告loggers為例項變數的缺點
  1. Possible to take advantage of repository selectors even for libraries shared between applications. However, repository selectors only work if the underlying logging system is logback-classic. Repository selectors do not work for the SLF4J+log4j combination.
  2. IOC-friendly

1.   可發揮倉庫選擇器的優勢,甚至應用間的共享庫。然而,只有底層日誌系統是logback-classic的,倉庫選擇器才起作用。SLF4J+log4j相結合的方式,倉庫選擇器將不起作用。

2.   IOC友好

1.Less common idiom than declaring loggers as static variables2.higher CPU overhead: loggers are retrieved and assigned for each instance of the hosting class
  1. higher memory overhead: logger declaration will consume one reference per instance of the hosting class

1.   比宣告loggers為靜態變數有更少的慣用語句

2.   更高的CPU開銷:loggers為主機類的每個例項進行獲取和分配

3.   更高的記憶體開銷:logger宣告主機類的每個例項都消耗一個引用

說明

靜態日誌成員為類的所有例項花費一個單獨的變數引用,而例項日誌成員將為類的每個例項花費一個變數引用。對簡單的類進行數以千計次的初始化可能出現一次明顯的區別。

然而,最近的日誌系統,例如log4j或logback,為每個執行在應用伺服器上的應用提供一個不同的logger上下文。因此,即使一份log4j.jar或logback-classic.jar的單拷貝有效地運用在伺服器上,日誌系統能夠在應用間進行區分,同時為每個應用提供一個不同的日誌環境。

更特別的是,每次,通過呼叫LoggerFactory.getLogger()方法可以重新獲得logger,底層日誌系統將給當前應用返回適當的例項。請注意,在相同的應用裡獲取一個給定名字的logger通常會返回相同的logger.對於給定的名字,對不同的應用將返回不同的logger.

如果logger是靜態的,當主機類已載入到記憶體時,那麼它將只被獲得一次。如果主機類只在一個應用裡使用,那麼這裡沒有太多需要關心的地方。然而,如果主機類在幾個應用間共享,那麼所有共享類的例項將記錄到應用的上下文裡,這都發生在第一次載入共享類到記憶體裡的時候-幾乎是不被使用者期望的行為。

不幸的是,對於SLF4J API的非本地實現,名字為slf4j-log4j12,log4j的倉庫選擇器將不能正常地完成它的工作,因為slf4j-log4j12,是非本地SLF4J繫結,將在字典裡儲存logger,短路迴圈依賴logger上下文重新獲取。對於本地SLF4J實現方法,例如logback-classic,倉庫選擇器將如期工作。

Apache Commons wiki有一篇覆蓋這個問題的文章 。

Logger序列化

與靜態變數相反,例項變數預設是序列化的。對於SLF4J版本1.5.3,

Logger例項變數幸好是初始化的。因此,主機類的初始化不再需要任何特別的指令,即使當loggers被宣告為例項變數。在之前的版本里,logger例項需要在主機類裡宣告為transient型別變數。

總結

綜上,宣告logger成員為靜態變數需要更少的CPU時間,有一個更小的記憶體開銷。另一方面,宣告logger成員為例項變數需要更多的CPU時間和更高的記憶體開銷。然而,例項變數使得為每一個應用構建一個不同的logger環境,甚至為共享庫裡宣告的logger成為可能。可能,比之前涉及的因素更重要的是,例項變數是IOC友好的,而靜態變數卻不是。

檢視commons-logging wiki裡相關討論 。

2.類裡的宣告日誌器有推薦的習語嗎?

以下是推薦的記錄宣告成語。原因如上所述,這是留給使用者確定記錄器是否被宣告為靜態變數或沒有。

package some.package;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClass {
   final (static) Logger logger = LoggerFactory.getLogger(MyClass.class);
   ... etc 

}

不幸的是,由於主機類的名稱是記錄器宣告的一部分,上面logger宣告習語是不能在類之間抵抗剪下和貼上。