正確使用日誌的10個技巧

https://blog.csdn.net/kobejayandy/article/details/17335215
html

作一個苦逼的Java攻城師, 咱們除了關心繫統的架構這種high level的問題, 還須要瞭解一些語言的陷阱, 異常的處理, 以及日誌的輸出, 這些"雞毛蒜皮"的細節. 這篇文章是JCP成員, Tomasz Nurkiewicz(http://nurkiewicz.blogspot.com/ )總結的10條如何正確使用日誌的技巧(參見原文). 跟那篇"java編程最差實踐"同樣, 也是針對一些細節的, 由於日誌是咱們排查問題, 瞭解系統情況的重要線索. 我以爲對咱們日常coding很是有借鑑意義. 因此轉換成中文, 加深一下印象, 也做爲本身工做的一個參考.java


1)選擇正確的Log開源框架
在代碼中爲了知道程序的行爲的狀態, 咱們通常會打印一條日誌:

Java代碼   收藏代碼
  1. log.info("Happy and carefree logging");  


在全部的日誌框架中, 我認爲最好的是SLF4J. 好比在Log4J中咱們會這樣寫:
Java代碼   收藏代碼
  1. log.debug("Found " + records + " records matching filter: '" + filter + "'");  


而在SLF4J中咱們會這樣寫:
Java代碼   收藏代碼
  1. log.debug("Found {} records matching filter: '{}'", records, filter);  


從可讀性和系統效率來講, SLF4J( http://logback.qos.ch/ )比Log4J都要優秀(Log4J涉及到字符串鏈接和toString()方法的調用). 這裏的{}帶來的另外一個好處, 咱們在儘可能不損失性能的狀況, 沒必要爲了避免同的日誌輸出級別, 而加上相似isDebugEnabled()判斷.

SLF4J只是各類日誌實現的一個接口抽象(facade), 而最佳的實現是Logback, 相對於Log4J的同門兄弟(皆出自Ceki Gülcü之手), 它在開源社區的活躍度更高.

最後要推薦的是Perf4J( http://perf4j.codehaus.org/ ). 用一句話來歸納就是:
若是把log4j看作System.out.println()的話, 那麼Perf4J就是System.currentTimeMillis()

Perf4J能夠幫助咱們更方便的輸出系統性能的日誌信息. 而後藉助其餘報表工具將日誌以圖表的形式加以展示, 從而方便咱們分析系統的性能瓶頸. 關於Perf4J的使用能夠參考它的開發者指南( http://perf4j.codehaus.org/devguide.html).

下面是一份關於日誌jar包依賴的pom.xml參考模板:
Xml代碼   收藏代碼
  1. <repositories>  
  2.     <repository>  
  3.         <id>Version99</id>  
  4.         <name>Version 99 Does Not Exist Maven repository</name>  
  5.         <layout>default</layout>  
  6.         <url>http://no-commons-logging.zapto.org/mvn2</url>  
  7.     </repository>  
  8. </repositories>  
  9.    
  10.    
  11. <dependency>  
  12.     <groupId>org.slf4j</groupId>  
  13.     <artifactId>slf4j-api</artifactId>  
  14.     <version>1.5.11</version>  
  15. </dependency>  
  16. <dependency>  
  17.     <groupId>ch.qos.logback</groupId>  
  18.     <artifactId>logback-classic</artifactId>  
  19.     <version>0.9.20</version>  
  20. </dependency>  
  21. <dependency>  
  22.     <groupId>org.slf4j</groupId>  
  23.     <artifactId>jul-to-slf4j</artifactId>  
  24.     <version>1.5.11</version>  
  25. </dependency>  
  26. <dependency>  
  27.     <groupId>org.slf4j</groupId>  
  28.     <artifactId>log4j-over-slf4j</artifactId>  
  29.     <version>1.5.11</version>  
  30. </dependency>  
  31. <dependency>  
  32.     <groupId>org.slf4j</groupId>  
  33.     <artifactId>jcl-over-slf4j</artifactId>  
  34.     <version>1.5.11</version>  
  35. </dependency>  
  36. <dependency>  
  37.     <groupId>commons-logging</groupId>  
  38.     <artifactId>commons-logging</artifactId>  
  39.     <version>99.0-does-not-exist</version>  
  40. </dependency>  


下面是測試代碼:
Java代碼   收藏代碼
  1. SLF4JBridgeHandler.install();  
  2.    
  3. org.apache.log4j.Logger.getLogger("A").info("Log4J");  
  4. java.util.logging.Logger.getLogger("B").info("java.util.logging");  
  5. org.apache.commons.logging.LogFactory.getLog("C").info("commons-logging");  
  6. org.slf4j.LoggerFactory.getLogger("D").info("Logback via SLF4J");  


上面的代碼, 不管你採用哪一個log框架輸出日誌, 底層採用的都是logback, 至於爲何, 能夠看這裏( http://www.slf4j.org/legacy.html), 另外這裏爲了在classpath裏面不引入common-logging, 用了一個小技巧, 就是將依賴版本設置爲99.0-does-not-exist, 關於這種用法的說明能夠看這裏( http://day-to-day-stuff.blogspot.com/2007/10/announcement-version-99-does-not-exist.html), 不過log4j的做者認爲最簡單的作法就是直接去掉對common-logging的依賴, 相關內容能夠看這裏的說明( http://www.slf4j.org/faq.html#excludingJCL)

2) 理解正確的日誌輸出級別
不少程序員都忽略了日誌輸出級別, 甚至不知道如何指定日誌的輸出級別. 相對於System.out來講, 日誌框架有兩個最大的優勢就是能夠指定輸出類別(category)和級別(level). 對於日誌輸出級別來講, 下面是咱們應該記住的一些原則:
ERROR:系統發生了嚴重的錯誤, 必須立刻進行處理, 不然系統將沒法繼續運行. 好比, NPE, 數據庫不可用等.

WARN:系統能繼續運行, 可是必須引發關注. 對於存在的問題通常能夠分爲兩類: 一種系統存在明顯的問題(好比, 數據不可用), 另外一種就是系統存在潛在的問題, 須要引發注意或者給出一些建議(好比, 系統運行在安全模式或者訪問當前系統的帳號存在安全隱患). 總之就是系統仍然可用, 可是最好進行檢查和調整.

INFO:重要的業務邏輯處理完成. 在理想狀況下, INFO的日誌信息要能讓高級用戶和系統管理員理解, 並從日誌信息中能知道系統當前的運行狀態. 好比對於一個機票預訂系統來講, 當一個用戶完成一個機票預訂操做以後, 提醒應該給出"誰預訂了從A到B的機票". 另外一個須要輸出INFO信息的地方就是一個系統操做引發系統的狀態發生了重大變化(好比數據庫更新, 過多的系統請求).

DEBUG:主要給開發人員看, 下面會進一步談到.

TRACE: 系統詳細信息, 主要給開發人員用, 通常來講, 若是是線上系統的話, 能夠認爲是臨時輸出, 並且隨時能夠經過開關將其關閉. 有時候咱們很難將DEBUG和TRACE區分開, 通常狀況下, 若是是一個已經開發測試完成的系統, 再往系統中添加日誌輸出, 那麼應該設爲TRACE級別.

以上只是建議, 你也能夠創建一套屬於你本身的規則. 可是一套良好的日誌系統, 應該首先是能根據狀況快速靈活的調整日誌內容的輸出.

最後要提到的就是"臭名昭著"的is*Enabled()條件, 好比下面的寫法:
Java代碼   收藏代碼
  1. if(log.isDebugEnabled())  
  2.     log.debug("Place for your commercial");  


這種作法對性能的提升幾乎微乎其微(前面在提到SLF4J的時候已經說明), 並且是一種過分優化的表現. 極少狀況下須要這樣寫, 除非構造日誌信息很是耗性能. 最後必須記住一點: 程序員應該專一於日誌內容, 而將日誌的輸出的決定權交給日誌框架去非處理.

3) 你真的知道log輸出的內容嗎?
對於你輸出的每一條log信息, 請仔細檢查最終輸出的內容是否存在問題, 其中最重要的就是避免NPE, 好比想下面這樣:

Java代碼   收藏代碼
  1. log.debug("Processing request with id: {}", request.getId());  


這裏咱們可否保證request不爲null? 除了NPE以外, 有時候咱們可能還須要考慮, 是否會致使OOM? 越界訪問? 線程飢餓(log是同步的)? 延遲初始化異常? 日誌打爆磁盤等等. 另一個問題就是在日誌中輸出集合(collection), 有時候咱們輸出的集合內容多是由Hibernate從數據庫中取出來的, 好比下面這條日誌信息:

Java代碼   收藏代碼
  1. log.debug("Returning users: {}", users);  


這裏最佳的處理方式是僅僅輸出domain對象的id或者集合的大小(size), 而對Java來講, 不得不要吐槽幾句, 要遍歷訪問集合中每個元素的getId方法很是繁瑣. 這一點Groovy就作的很是簡單(users*.id), 不過咱們能夠藉助Commons Beanutils工具包來幫咱們簡化:

Java代碼   收藏代碼
  1. log.debug("Returning user ids: {}", collect(users, "id"));  


這裏的collect方法的實現以下:

Java代碼   收藏代碼
  1. public static Collection collect(Collection collection, String propertyName) {  
  2.     return CollectionUtils.collect(collection, new BeanToPropertyValueTransformer(propertyName));  
  3. }  


不過不幸的是, 在給Commons Beanutils提了一個patch(BEANUTILS-375 https://issues.apache.org/jira/browse/BEANUTILS-375)以後, 並無被接受:(

最後是關於toString()方法. 爲了讓日誌更容易理解, 最好爲每個類提供合適的toString()方法. 這裏能夠藉助ToStringBuilder工具類. 另一個就是關於數組和某些集合類型. 由於數組是使用的默認的toString方法. 而某些集合沒有很好的實現toString方法. 對於數組咱們可使用JDK的Arrays.deepToString()方法( http://docs.oracle.com/javase/6/docs/api/java/util/Arrays.html#deepToString%28java.lang.Object[]%29).

4) 當心日誌的反作用

有時候日誌或多或少的會影響系統的行爲, 好比最近碰到的一個狀況就是在某些條件下, Hibernate會拋出LazyInitializationException. 這是由於某些輸出日誌致使延遲初始化的集合在session創建時被加載. 而在某些場景下當提升日誌輸出級別時, 問題就會消失.

另外一個反作用就是日誌致使系統運行愈來愈慢. 好比不恰當的使用toString方法或者字符串鏈接, 使得系統出現性能問題, 曾經碰到的一個現象, 某個系統每隔15分鐘重啓一次, 這個主要是執行log輸出出現線程飢餓致使, 通常狀況下, 若是一個系統一小時內生成的日誌有幾百MB的時候, 就要當心了.

而若是由於日誌輸出自己的問題致使正常的業務邏輯被中斷, 那就更嚴重了. 好比下面這種代碼, 最好不要這樣寫:
Java代碼   收藏代碼
  1. try {  
  2.     log.trace("Id=" + request.getUser().getId() + " accesses " + manager.getPage().getUrl().toString())  
  3. catch(NullPointerException e) {}  



5) 日誌信息應該簡潔且可描述

通常, 每一條日誌數據會包括描述和上下文兩部分, 好比下面的日誌:
Java代碼   收藏代碼
  1. log.debug("Message processed");  
  2. log.debug(message.getJMSMessageID());  
  3.   
  4. log.debug("Message with id '{}' processed", message.getJMSMessageID());  


第一條只有描述, 第二條只有上下文, 第三條纔算完整的一條日誌, 還有下面這種日誌:
Java代碼   收藏代碼
  1. if(message instanceof TextMessage)  
  2.     //...  
  3. else  
  4.     log.warn("Unknown message type");  



在上面的警告日誌中沒有包含實際的message type, message id等信息, 只是代表程序有問題, 那麼是什麼致使了問題呢? 上下文是什麼? 咱們什麼都不知道.

另一個問題就是在日誌中加上一個莫名其妙的內容, 即所謂的"magic log". 好比有些程序員會在日誌中隨手敲上"&&&!#"這樣一串字符, 用來幫助他們定位.

一個日誌文件中的內容應該易讀, 清晰, 可描述. 而不要使用莫名其妙的字符, 日誌要給出當前操做作了什麼, 使用的什麼數據. 好的日誌應該被當作文檔註釋的一部分.

最後一點, 切記不要在日誌中包含密碼和我的隱私信息!

6) 正確的使用輸出模式

log輸出模式能夠幫助咱們在日誌中增長一些清晰的上下文信息. 不過對添加的信息仍是要多加當心. 好比說, 若是你是每小時輸出一個文件, 這樣你的日誌文件名中已經包含了部分日期時間信息, 所以就不必在日誌中再包含這些信息. 另外在多線程環境下也不要在本身在日誌中包含線程名稱, 由於這個也能夠在模式中配置.

根據個人經驗, 一個理想的日誌模式將包含下列信息:
  • 當前時間(不須要包含日誌, 精確到毫秒)
  • 日誌級別(若是你關心這個)
  • 線程名稱
  • 簡單的日誌名(非全限定名的那種)
  • 日誌描述信息


好比像下面這個logback配置:

Xml代碼   收藏代碼
  1. <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">  
  2.     <encoder>  
  3.         <pattern>%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n</pattern>  
  4.     </encoder>  
  5. </appender>  


千萬不要在日誌信息中包含下列內容:
  • 文件名
  • 類名(我想這個應該是全限定名吧)
  • 代碼行號


還有下面這種寫法也是要避免的:

Java代碼   收藏代碼
  1. log.info("");  


由於程序員知道, 在日誌模式中會指定行號, 所以他就能夠根據日誌輸的行號出判斷指定的方法是否被調用了(好比這裏多是authenticate()方法, 進而判斷登陸的用戶已經通過了驗證). 另外, 你們也要清楚一點, 在日誌模式中指定類名, 方法名以及行號會帶來嚴重的性能問題. 下面是我針對這個作的一個簡單的測試, 配置以下:


Xml代碼   收藏代碼
  1. <appender name="CLASS_INFO" class="ch.qos.logback.core.OutputStreamAppender">  
  2.     <encoder>  
  3.         <pattern>%d{HH:mm:ss.SSS} %-5level [%thread][%class{0}.%method
:%line][%logger{0}] %m%n </ pattern >       </ encoder >       < outputStream  class= "org.apache.commons.io.output.NullOutputStream" />   </ appender >   < appender  name= "NO_CLASS_INFO"  class= "ch.qos.logback.core.OutputStreamAppender" >       < encoder >           < pattern >%d{HH:mm:ss.SSS} %-5level [%thread][LoggerTest.testClassInfo
  1. :30][%logger{0}] %m%n</pattern>  
  2.     </encoder>  
  3.     <outputStream class="org.apache.commons.io.output.NullOutputStream"/>  
  4. </appender>  


下面是測試代碼:

Java代碼   收藏代碼
  1. import org.junit.Test;  
  2. import org.perf4j.StopWatch;  
  3. import org.perf4j.slf4j.Slf4JStopWatch;  
  4. import org.slf4j.Logger;  
  5. import org.slf4j.LoggerFactory;  
  6.    
  7. public class LoggerTest {  
  8.    
  9.     private static final Logger log = LoggerFactory.getLogger(LoggerTest.class);  
  10.     private static final Logger classInfoLog = LoggerFactory.getLogger("CLASS_INFO");  
  11.     private static final Logger noClassInfoLog = LoggerFactory.getLogger("NO_CLASS_INFO");  
  12.    
  13.     private static final int REPETITIONS = 15;  
  14.     private static final int COUNT = 100000;  
  15.    
  16.     @Test  
  17.     public void testClassInfo() throws Exception {  
  18.         for (int test = 0; test < REPETITIONS; ++test)  
  19.             testClassInfo(COUNT);  
  20.     }  
  21.    
  22.     private void testClassInfo(final int count) {  
  23.         StopWatch watch = new Slf4JStopWatch("Class info");  
  24.         for (int i = 0; i < count; ++i)  
  25.             classInfoLog.info("Example message");  
  26.         printResults(count, watch);  
  27.     }  
  28.    
  29.     @Test  
  30.     public void testNoClassInfo() throws Exception {  
  31.         for (int test = 0; test < REPETITIONS; ++test)  
  32.             testNoClassInfo(COUNT * 20);  
  33.     }  
  34.    
  35.     private void testNoClassInfo(final int count) {  
  36.         StopWatch watch = new Slf4JStopWatch("No class info");  
  37.         for (int i = 0; i < count; ++i)  
  38.             noClassInfoLog.info("Example message");  
  39.         printResults(count, watch);  
  40.     }  
  41.    
  42.     private void printResults(int count, StopWatch watch) {  
  43.         log.info("Test {} took {}ms (avg. {} ns/log)"new Object[]{  
  44.                 watch.getTag(),  
  45.                 watch.getElapsedTime(),  
  46.                 watch.getElapsedTime() * 1000 * 1000 / count});  
  47.     }  
  48.    
  49. }  


在上面的測試代碼中, CLASS_INFO日誌輸出了1500萬次, 而NO_CLASS_INFO輸出了3億次. 後者採用一個靜態的文原本取代日誌模式中的動態類信息.

從下面的對比圖能夠看出, 直接在日誌模式中指定類名的日誌比使用反射動態獲取類名的要快13倍(平均輸出每條日誌耗時:8.8 vs 115微秒). 對於一個java程序員來講, 一條日誌耗時100微秒是能夠接受的. 這也就是說, 一個後臺應用其中1%的時間消耗在了輸出日誌上. 所以咱們有時候也須要權衡一下, 每秒100條日誌輸出是不是合理的.

最後要提到的是日誌框架中比較高級的功能: Mapped Diagnostic Context. MDC( http://www.slf4j.org/api/org/slf4j/MDC.html )主要用來簡化基於thread-local的map參數管理. 你能夠往這個map中增長任何key-value內容, 而後在隨後的日誌輸出中做爲模式的一部分, 與當前線程一塊兒輸出.

7) 給方法的輸入輸出加上日誌

當咱們在開發過程當中發現了一個bug, 通常咱們會採用debug的方式一步步的跟蹤, 直到定位到最終的問題位置(若是能經過寫一個失敗的單元測試來暴露問題, 那就更帥了^_^). 可是若是實際狀況不容許你debug時, 好比在客戶的系統上幾天前出現的bug. 若是你沒有詳細的日誌的話, 你能找到問題的根源麼?

若是你能根據一些簡單的規則來輸出每一個方法的輸入和輸出(參數和返回值). 你基本上能夠扔掉調試器了. 固然針對每個方法加上日誌必須是合理的: 訪問外部資源(好比數據庫), 阻塞, 等待等等, 這些地方能夠考慮加上日誌. 好比下面的代碼:

Java代碼   收藏代碼
  1. public String printDocument(Document doc, Mode mode) {  
  2.     log.debug("Entering printDocument(doc={}, mode={})", doc, mode);  
  3.     String id = //Lengthy printing operation  
  4.     log.debug("Leaving printDocument(): {}", id);  
  5.     return id;  
  6. }  


由於在方法調用先後加上了日誌, 咱們能夠很是方便的發現代碼的性能問題, 甚至找出死鎖和線程飢餓(starvation)等嚴重問題:這種狀況下都只有輸入(entering)日誌, 不會有輸出(leaving)日誌. 若是方法名類名使用得當, 那麼輸出的日誌信息也將會很是賞心悅目. 由於你能夠根據日誌完整了解系統的運行狀況, 所以分析問題的時候, 也將變得更加垂手可得. 爲了減小日誌代碼, 也能夠採用簡單的AOP來作日誌輸出. 可是也要當心, 這種作法可能產生大量的日誌.

對於這種日誌, 通常採用DEBUG/TRACE級別. 當某些方法的調用很是頻繁, 那麼大量的日誌輸出將會影響到系統的性能, 此時咱們能夠提升相關類的日誌級別或者乾脆去掉日誌輸出. 不過通常狀況下, 仍是建議你們多輸出一些日誌. 另外也能夠將日誌當作一種單元測試. 輸出的日誌將像單元測試同樣, 會覆蓋到整個方法的執行過程. 沒有日誌的系統是不可想象的. 所以經過觀察日誌的輸出將是咱們瞭解系統是在正確的運行仍是掛了的惟一方式.

8) 用日誌檢查外部系統

這裏是針對前面的一種場景: 若是你的系統須要和其餘系統通訊, 那麼須要考慮是否須要用日誌記錄這種交互. 通常狀況下, 若是一個應用須要與多個系統進行集成, 那麼診斷和分析問題將很是困難. 好比在最近的一個項目中, 因爲咱們在Apache CXF web服務上完整的記錄了消息數據(包括SOAP和HTTP頭信息), 使得咱們在系統集成和測試階段很是happy.

若是經過ESB的方式來多個系統進行集成, 那麼能夠在總線(bus)上使用日誌來記錄請求和響應. 這裏能夠參考Mules( http://www.mulesoft.org/)的<log-component/>( http://www.mulesoft.org/documentation/display/MULE2USER/Configuring+Components).

有時候與外部系統進行通訊產生的大量日誌可能讓咱們沒法接受. 另外一方面, 咱們但願打開日誌臨時進行一下測試, 或者在系統出現問題的時候咱們但願打開短暫的輸出日誌. 這樣咱們能夠在輸出日誌和保證系統性能之間取得一個平衡. 這裏咱們須要藉助日誌日別. 好比像下面的這樣作:

Java代碼   收藏代碼
  1. Collection<Integer> requestIds = //...  
  2.    
  3. if(log.isDebugEnabled())  
  4.     log.debug("Processing ids: {}", requestIds);  
  5. else  
  6.     log.info("Processing ids size: {}", requestIds.size());  



在上面的代碼中, 若是日誌級別配置爲DEBUG, 那麼將但應全部的requestIds信息. 可是若是咱們配置INFO級別, 那麼只會輸出requestId的數量. 不過就像咱們前面提到的日誌的反作用那樣, 若是在INFO級別下requestIds爲null將產生NullPointerException.

9) 正確輸出異常日誌

對於日誌輸出的第一條原則就是不要用日誌輸出異常, 而是讓框架或者容器去處理. 記錄異常本不該該是logger的工做. 而許多程序員都會用日誌輸出異常, 而後可能返回一個默認值(null, 0或者空字符串). 也可能在將異常包裝一下再拋出. 好比像下面的代碼這樣:

Java代碼   收藏代碼
  1. log.error("IO exception", e);  
  2. throw new MyCustomException(e);  


這樣作的結果可能會致使異常信息打印兩次(拋出的地方打一次, 捕獲處理的地方再打一次).

可是有時候咱們確實但願打印異常, 那麼應該如何處理呢? 好比下面對NPE的處理:

Java代碼   收藏代碼
  1. try {  
  2.     Integer x = null;  
  3.     ++x;  
  4. catch (Exception e) {  
  5.     log.error(e);        //A  
  6.     log.error(e, e);        //B  
  7.     log.error("" + e);        //C  
  8.     log.error(e.toString());        //D  
  9.     log.error(e.getMessage());        //E  
  10.     log.error(null, e);        //F  
  11.     log.error("", e);        //G  
  12.     log.error("{}", e);        //H  
  13.     log.error("{}", e.getMessage());        //I  
  14.     log.error("Error reading configuration file: " + e);        //J  
  15.     log.error("Error reading configuration file: " + e.getMessage());        //K  
  16.     log.error("Error reading configuration file", e);        //L  
  17. }  


上面的代碼, 正確輸出異常信息的只有G和L, A和B甚至不能在SLF4J中編譯經過, 其餘的都會丟失異常堆棧信息或者打印了不恰當的信息. 這裏只要記住一條, 在日誌中輸出異常信息, 第一個參數必定是一個字符串, 通常都是對問題的描述信息, 而不能是異常message(由於堆棧裏面會有), 第二個參數纔是具體的異常實例.

注: 對於遠程調用類型的服務拋出的異常,必定要注意實現序列化, 不然在客戶端將拋出NoClassDefFoundError異常, 而掩蓋了真實的異常信息

10) 讓日誌易讀,易解析

對日誌感興趣的能夠分爲兩類:
  • 人(好比程序員)
  • 機器(系統管理員寫的shell腳本)


日誌的內容必須照顧到這兩個羣體. 引用鮑勃大叔"Clean Code( http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)"一書的話來講:日誌應該像代碼同樣可讀而且容易理解.

另外一方面, 若是一個系統每小時要輸出幾百MB甚至上G的日誌, 那麼咱們須要藉助grep, sed以及awk來分析日誌. 若是可能, 咱們應該讓日誌儘量的被人和機器理解. 好比, 避免格式化數字, 使用日誌模式則能夠方便用正則表達式進行識別. 若是沒法兼顧, 那麼能夠將數據用兩種格式輸出, 好比像下面這樣:

Java代碼   收藏代碼
  1. log.debug("Request TTL set to: {} ({})"new Date(ttl), ttl);  
  2. // Request TTL set to: Wed Apr 28 20:14:12 CEST 2010 (1272478452437)  
  3.    
  4. final String duration = DurationFormatUtils.formatDurationWords(durationMillis, truetrue);  
  5. log.info("Importing took: {}ms ({})", durationMillis, duration);  
  6. //Importing took: 123456789ms (1 day 10 hours 17 minutes 36 seconds)  


上面的日誌, 既照顧了計算機("ms after 1970 epoch"這種時間格式), 又能更好的讓人能理解("1 day 10 hours 17 minutes 36 seconds") . 另外, 這裏順便廣告一下DurationFormatUtils( http://commons.apache.org/lang/api-release/org/apache/commons/lang/time/DateFormatUtils.html), 一個很是不錯的工具:) 
相關文章
相關標籤/搜索