閱讀84 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《SLF4J官方文檔》SLF4J-FAQ 常見問題解答(二)

替代3空構件

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

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

下麵的聲明添加version99庫設定的Maven的搜索遠程資源庫。 該存儲庫中包含的commons-logging和log4j的空構件。 順便說一句,如果你使用的version99庫,請在<version99 AT qos.ch>給我們一行。

1 <repositories>
2   <repository>
3     <id>version99</id>
4     <!-- highly available repository serving empty artifacts -->
5     <url>http://version99.qos.ch/</url>
6   </repository>
7 </repositories>

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

01 <dependencyManagement>
02    <dependencies>
03      <dependency>
04        <groupId>commons-logging</groupId>
05        <artifactId>commons-logging</artifactId>
06        <version><strong>99-empty</strong></version>
07      </dependency>
08      ... other declarations...
09    </dependencies>
10  </dependencyManagement>
11  
12  <!-- Do not forget to declare a dependency on jcl-over-slf4j in the        -->
13  <!-- dependencies section. Note that the dependency on commons-logging     -->
14  <!-- will be imported transitively. You don't have to declare it yourself. -->
15  <dependencies>
16    <dependency>
17      <groupId>org.slf4j</groupId>
18      <artifactId>jcl-over-slf4j</artifactId>
19      <version>1.7.21</version>
20    </dependency>
21    ... other dependency declarations
22  </dependencies>

 

關於SLF4J API

1.為什麼日誌器接口的打印方法不接收對象類型的消息,但隻接收String類型消息?

在SLF4J 1.0beta4版裏,Logger interface 中例如debug(),info(),warn()的打印方法被修改以用於隻接收String類型的消息,而不是Object類型的消息。

因此,DEBUG級別的打印方法設置變為:

1 debug(Stringmsg);
2 debug(String format,Objectarg);
3 debug(String format,Object arg1,Object arg2);
4 debug(Stringmsg,Throwable t);

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

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

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

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

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

 

不幸的是,上述調用不會打印堆棧跟蹤的異常。因此,潛在的關鍵信息片可能會丟失。當第一個參數被限製為String類型時,隻有如下方法: debug(Stringmsg,Throwable t);

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

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

簡言之,不可以。

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

1 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,寫作,

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

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

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

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

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

更好的是,使用參數化的消息

這裏存在一個基於消息格式的很方便的選擇。假設entry是一個對象,你可以這樣寫:

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

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

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

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

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

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

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

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

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

數組形式的參數,包括多維數組,也是支持的。

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

擺脫“{}”對

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

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

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

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

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

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

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

 

將會打印為“Set {} differs from 3”。注意在java代碼裏,反斜杠字符需要寫為“\\”。

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

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

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

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

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

01 import org.slf4j.Logger;
02 import org.slf4j.LoggerFactory;
03 import org.slf4j.Marker;
04 import org.slf4j.MarkerFactory;
05  
06 classBar{
07  void foo(){
08    Marker fatal =MarkerFactory.getMarker("FATAL");
09    Loggerlogger=LoggerFactory.getLogger("aLogger");
10  
11    try{
12      ... obtain a JDBC connection
13    }catch(JDBException e){
14      logger.error(fatal,"Failed to obtain JDBC connection", e);
15    }
16  }
17 }

雖然標記是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支持參數化,假設異常時最後的參數嗎。因此,

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

如期望的一樣,將會打印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.類裏的聲明日誌器有推薦的習語嗎?

以下是推薦的記錄聲明成語。原因如上所述,這是留給用戶確定記錄器是否被聲明為靜態變量或沒有。

1 package some.package;
2 import org.slf4j.Logger;
3 import org.slf4j.LoggerFactory;
4  
5 public class MyClass {
6    final (static) Logger logger = LoggerFactory.getLogger(MyClass.class);
7    ... etc
8  
9 }

不幸的是,由於主機類的名稱是記錄器聲明的一部分,上麵logger聲明習語是不能在類之間抵抗剪切和粘貼。

轉載自 並發編程網 - ifeve.com

最後更新:2017-05-19 16:01:59

  上一篇:go  《Maven官方文檔》創建Archetype
  下一篇:go  《SLF4J官方文檔》SLF4J-FAQ 常見問題解答(一)