日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

正確使用日志的10個(gè)技巧

 WindySky 2016-06-24
     做一個(gè)苦逼的Java攻城師, 我們除了關(guān)心系統(tǒng)的架構(gòu)這種high level的問(wèn)題, 還需要了解一些語(yǔ)言的陷阱, 異常的處理, 以及日志的輸出, 這些"雞毛蒜皮"的細(xì)節(jié). 這篇文章是JCP成員, Tomasz Nurkiewicz( http://nurkiewicz./ )總結(jié)的10條如何正確使用日志的技巧(參見(jiàn)原文). 跟那篇"java編程最差實(shí)踐"一樣, 也是針對(duì)一些細(xì)節(jié)的, 因?yàn)槿罩臼俏覀兣挪閱?wèn)題, 了解系統(tǒng)狀況的重要線(xiàn)索. 我覺(jué)得對(duì)我們平常coding非常有借鑒意義. 所以轉(zhuǎn)換成中文, 加深一下印象, 也作為自己工作的一個(gè)參考.

1)選擇正確的Log開(kāi)源框架
在代碼中為了知道程序的行為的狀態(tài), 我們一般會(huì)打印一條日志:

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


在所有的日志框架中, 我認(rèn)為最好的是SLF4J. 比如在Log4J中我們會(huì)這樣寫(xiě):
Java代碼  收藏代碼
  1. log.debug("Found " + records + " records matching filter: '" + filter + "'");  


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


從可讀性和系統(tǒng)效率來(lái)說(shuō), SLF4J( http://logback./ )比Log4J都要優(yōu)秀(Log4J涉及到字符串連接和toString()方法的調(diào)用). 這里的{}帶來(lái)的另一個(gè)好處, 我們?cè)诒M量不損失性能的情況, 不必為了不同的日志輸出級(jí)別, 而加上類(lèi)似isDebugEnabled()判斷.

SLF4J只是各種日志實(shí)現(xiàn)的一個(gè)接口抽象(facade), 而最佳的實(shí)現(xiàn)是Logback, 相對(duì)于Log4J的同門(mén)兄弟(皆出自Ceki Gülcü之手), 它在開(kāi)源社區(qū)的活躍度更高.

最后要推薦的是Perf4J( http://perf4j./ ). 用一句話(huà)來(lái)概括就是:
如果把log4j看做System.out.println()的話(huà), 那么Perf4J就是System.currentTimeMillis()

Perf4J可以幫助我們更方便的輸出系統(tǒng)性能的日志信息. 然后借助其他報(bào)表工具將日志以圖表的形式加以展現(xiàn), 從而方便我們分析系統(tǒng)的性能瓶頸. 關(guān)于Perf4J的使用可以參考它的開(kāi)發(fā)者指南(http://perf4j./devguide.html).

下面是一份關(guān)于日志jar包依賴(lài)的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./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>  


下面是測(cè)試代碼:
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");  


上面的代碼, 無(wú)論你采用哪個(gè)log框架輸出日志, 底層采用的都是logback, 至于為什么, 可以看這里(http://www./legacy.html), 另外這里為了在classpath里面不引入common-logging, 用了一個(gè)小技巧, 就是將依賴(lài)版本設(shè)置為99.0-does-not-exist, 關(guān)于這種用法的說(shuō)明可以看這里(http://day-to-day-stuff./2007/10/announcement-version-99-does-not-exist.html), 不過(guò)log4j的作者認(rèn)為最簡(jiǎn)單的做法就是直接去掉對(duì)common-logging的依賴(lài), 相關(guān)內(nèi)容可以看這里的說(shuō)明(http://www./faq.html#excludingJCL)

2) 理解正確的日志輸出級(jí)別
很多程序員都忽略了日志輸出級(jí)別, 甚至不知道如何指定日志的輸出級(jí)別. 相對(duì)于System.out來(lái)說(shuō), 日志框架有兩個(gè)最大的優(yōu)點(diǎn)就是可以指定輸出類(lèi)別(category)和級(jí)別(level). 對(duì)于日志輸出級(jí)別來(lái)說(shuō), 下面是我們應(yīng)該記住的一些原則:
ERROR:系統(tǒng)發(fā)生了嚴(yán)重的錯(cuò)誤, 必須馬上進(jìn)行處理, 否則系統(tǒng)將無(wú)法繼續(xù)運(yùn)行. 比如, NPE, 數(shù)據(jù)庫(kù)不可用等.

WARN:系統(tǒng)能繼續(xù)運(yùn)行, 但是必須引起關(guān)注. 對(duì)于存在的問(wèn)題一般可以分為兩類(lèi): 一種系統(tǒng)存在明顯的問(wèn)題(比如, 數(shù)據(jù)不可用), 另一種就是系統(tǒng)存在潛在的問(wèn)題, 需要引起注意或者給出一些建議(比如, 系統(tǒng)運(yùn)行在安全模式或者訪(fǎng)問(wèn)當(dāng)前系統(tǒng)的賬號(hào)存在安全隱患). 總之就是系統(tǒng)仍然可用, 但是最好進(jìn)行檢查和調(diào)整.

INFO:重要的業(yè)務(wù)邏輯處理完成. 在理想情況下, INFO的日志信息要能讓高級(jí)用戶(hù)和系統(tǒng)管理員理解, 并從日志信息中能知道系統(tǒng)當(dāng)前的運(yùn)行狀態(tài). 比如對(duì)于一個(gè)機(jī)票預(yù)訂系統(tǒng)來(lái)說(shuō), 當(dāng)一個(gè)用戶(hù)完成一個(gè)機(jī)票預(yù)訂操作之后, 提醒應(yīng)該給出"誰(shuí)預(yù)訂了從A到B的機(jī)票". 另一個(gè)需要輸出INFO信息的地方就是一個(gè)系統(tǒng)操作引起系統(tǒng)的狀態(tài)發(fā)生了重大變化(比如數(shù)據(jù)庫(kù)更新, 過(guò)多的系統(tǒng)請(qǐng)求).

DEBUG:主要給開(kāi)發(fā)人員看, 下面會(huì)進(jìn)一步談到.

TRACE: 系統(tǒng)詳細(xì)信息, 主要給開(kāi)發(fā)人員用, 一般來(lái)說(shuō), 如果是線(xiàn)上系統(tǒng)的話(huà), 可以認(rèn)為是臨時(shí)輸出, 而且隨時(shí)可以通過(guò)開(kāi)關(guān)將其關(guān)閉. 有時(shí)候我們很難將DEBUG和TRACE區(qū)分開(kāi), 一般情況下, 如果是一個(gè)已經(jīng)開(kāi)發(fā)測(cè)試完成的系統(tǒng), 再往系統(tǒng)中添加日志輸出, 那么應(yīng)該設(shè)為T(mén)RACE級(jí)別.

以上只是建議, 你也可以建立一套屬于你自己的規(guī)則. 但是一套良好的日志系統(tǒng), 應(yīng)該首先是能根據(jù)情況快速靈活的調(diào)整日志內(nèi)容的輸出.

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


這種做法對(duì)性能的提高幾乎微乎其微(前面在提到SLF4J的時(shí)候已經(jīng)說(shuō)明), 而且是一種過(guò)度優(yōu)化的表現(xiàn). 極少情況下需要這樣寫(xiě), 除非構(gòu)造日志信息非常耗性能. 最后必須記住一點(diǎn): 程序員應(yīng)該專(zhuān)注于日志內(nèi)容, 而將日志的輸出的決定權(quán)交給日志框架去非處理.

3) 你真的知道log輸出的內(nèi)容嗎?
對(duì)于你輸出的每一條log信息, 請(qǐng)仔細(xì)檢查最終輸出的內(nèi)容是否存在問(wèn)題, 其中最重要的就是避免NPE, 比如想下面這樣:

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


這里我們能否保證request不為null? 除了NPE之外, 有時(shí)候我們可能還需要考慮, 是否會(huì)導(dǎo)致OOM? 越界訪(fǎng)問(wèn)? 線(xiàn)程饑餓(log是同步的)? 延遲初始化異常? 日志打爆磁盤(pán)等等. 另外一個(gè)問(wèn)題就是在日志中輸出集合(collection), 有時(shí)候我們輸出的集合內(nèi)容可能是由Hibernate從數(shù)據(jù)庫(kù)中取出來(lái)的, 比如下面這條日志信息:

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


這里最佳的處理方式是僅僅輸出domain對(duì)象的id或者集合的大小(size), 而對(duì)Java來(lái)說(shuō), 不得不要吐槽幾句, 要遍歷訪(fǎng)問(wèn)集合中每一個(gè)元素的getId方法非常繁瑣. 這一點(diǎn)Groovy就做的非常簡(jiǎn)單(users*.id), 不過(guò)我們可以借助Commons Beanutils工具包來(lái)幫我們簡(jiǎn)化:

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


這里的collect方法的實(shí)現(xiàn)如下:

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


不過(guò)不幸的是, 在給Commons Beanutils提了一個(gè)patch(BEANUTILS-375 https://issues./jira/browse/BEANUTILS-375)之后, 并沒(méi)有被接受:(

最后是關(guān)于toString()方法. 為了讓日志更容易理解, 最好為每一個(gè)類(lèi)提供合適的toString()方法. 這里可以借助ToStringBuilder工具類(lèi). 另外一個(gè)就是關(guān)于數(shù)組和某些集合類(lèi)型. 因?yàn)閿?shù)組是使用的默認(rèn)的toString方法. 而某些集合沒(méi)有很好的實(shí)現(xiàn)toString方法. 對(duì)于數(shù)組我們可以使用JDK的Arrays.deepToString()方法(http://docs.oracle.com/javase/6/docs/api/java/util/Arrays.html#deepToString%28java.lang.Object[]%29).

4) 小心日志的副作用

有時(shí)候日志或多或少的會(huì)影響系統(tǒng)的行為, 比如最近碰到的一個(gè)情況就是在某些條件下, Hibernate會(huì)拋出LazyInitializationException. 這是因?yàn)槟承┹敵鋈罩緦?dǎo)致延遲初始化的集合在session建立時(shí)被加載. 而在某些場(chǎng)景下當(dāng)提高日志輸出級(jí)別時(shí), 問(wèn)題就會(huì)消失.

另一個(gè)副作用就是日志導(dǎo)致系統(tǒng)運(yùn)行越來(lái)越慢. 比如不恰當(dāng)?shù)氖褂胻oString方法或者字符串連接, 使得系統(tǒng)出現(xiàn)性能問(wèn)題, 曾經(jīng)碰到的一個(gè)現(xiàn)象, 某個(gè)系統(tǒng)每隔15分鐘重啟一次, 這個(gè)主要是執(zhí)行l(wèi)og輸出出現(xiàn)線(xiàn)程饑餓導(dǎo)致, 一般情況下, 如果一個(gè)系統(tǒng)一小時(shí)內(nèi)生成的日志有幾百M(fèi)B的時(shí)候, 就要小心了.

而如果因?yàn)槿罩据敵霰旧淼膯?wèn)題導(dǎo)致正常的業(yè)務(wù)邏輯被中斷, 那就更嚴(yán)重了. 比如下面這種代碼, 最好不要這樣寫(xiě):
Java代碼  收藏代碼
  1. try {  
  2.     log.trace("Id=" + request.getUser().getId() + " accesses " + manager.getPage().getUrl().toString())  
  3. } catch(NullPointerException e) {}  



5) 日志信息應(yīng)該簡(jiǎn)潔且可描述

一般, 每一條日志數(shù)據(jù)會(huì)包括描述和上下文兩部分, 比如下面的日志:
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");  



在上面的警告日志中沒(méi)有包含實(shí)際的message type, message id等信息, 只是表明程序有問(wèn)題, 那么是什么導(dǎo)致了問(wèn)題呢? 上下文是什么? 我們什么都不知道.

另外一個(gè)問(wèn)題就是在日志中加上一個(gè)莫名其妙的內(nèi)容, 即所謂的"magic log". 比如有些程序員會(huì)在日志中隨手敲上"&&&!#"這樣一串字符, 用來(lái)幫助他們定位.

一個(gè)日志文件中的內(nèi)容應(yīng)該易讀, 清晰, 可描述. 而不要使用莫名其妙的字符, 日志要給出當(dāng)前操作做了什么, 使用的什么數(shù)據(jù). 好的日志應(yīng)該被看成文檔注釋的一部分.

最后一點(diǎn), 切記不要在日志中包含密碼和個(gè)人隱私信息!

6) 正確的使用輸出模式

log輸出模式可以幫助我們?cè)谌罩局性黾右恍┣逦纳舷挛男畔? 不過(guò)對(duì)添加的信息還是要多加小心. 比如說(shuō), 如果你是每小時(shí)輸出一個(gè)文件, 這樣你的日志文件名中已經(jīng)包含了部分日期時(shí)間信息, 因此就沒(méi)必要在日志中再包含這些信息. 另外在多線(xiàn)程環(huán)境下也不要在自己在日志中包含線(xiàn)程名稱(chēng), 因?yàn)檫@個(gè)也可以在模式中配置.

根據(jù)我的經(jīng)驗(yàn), 一個(gè)理想的日志模式將包含下列信息:
  • 當(dāng)前時(shí)間(不需要包含日志, 精確到毫秒)
  • 日志級(jí)別(如果你關(guān)心這個(gè))
  • 線(xiàn)程名稱(chēng)
  • 簡(jiǎn)單的日志名(非全限定名的那種)
  • 日志描述信息


比如像下面這個(gè)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>  


千萬(wàn)不要在日志信息中包含下列內(nèi)容:
  • 文件名
  • 類(lèi)名(我想這個(gè)應(yīng)該是全限定名吧)
  • 代碼行號(hào)


還有下面這種寫(xiě)法也是要避免的:

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


因?yàn)槌绦騿T知道, 在日志模式中會(huì)指定行號(hào), 因此他就可以根據(jù)日志輸?shù)男刑?hào)出判斷指定的方法是否被調(diào)用了(比如這里可能是authenticate()方法, 進(jìn)而判斷登錄的用戶(hù)已經(jīng)經(jīng)過(guò)了驗(yàn)證). 另外, 大家也要清楚一點(diǎn), 在日志模式中指定類(lèi)名, 方法名以及行號(hào)會(huì)帶來(lái)嚴(yán)重的性能問(wèn)題. 下面是我針對(duì)這個(gè)做的一個(gè)簡(jiǎn)單的測(cè)試, 配置如下:


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>  
  4.     </encoder>  
  5.     <outputStream class="org.apache.commons.io.output.NullOutputStream"/>  
  6. </appender>  
  7. <appender name="NO_CLASS_INFO" class="ch.qos.logback.core.OutputStreamAppender">  
  8.     <encoder>  
  9.         <pattern>%d{HH:mm:ss.SSS} %-5level [%thread][LoggerTest.testClassInfo\(\):30][%logger{0}] %m%n</pattern>  
  10.     </encoder>  
  11.     <outputStream class="org.apache.commons.io.output.NullOutputStream"/>  
  12. </appender>  


下面是測(cè)試代碼:

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. }  


在上面的測(cè)試代碼中, CLASS_INFO日志輸出了1500萬(wàn)次, 而NO_CLASS_INFO輸出了3億次. 后者采用一個(gè)靜態(tài)的文本來(lái)取代日志模式中的動(dòng)態(tài)類(lèi)信息.

從下面的對(duì)比圖可以看出, 直接在日志模式中指定類(lèi)名的日志比使用反射動(dòng)態(tài)獲取類(lèi)名的要快13倍(平均輸出每條日志耗時(shí):8.8 vs 115微秒). 對(duì)于一個(gè)java程序員來(lái)說(shuō), 一條日志耗時(shí)100微秒是可以接受的. 這也就是說(shuō), 一個(gè)后臺(tái)應(yīng)用其中1%的時(shí)間消耗在了輸出日志上. 因此我們有時(shí)候也需要權(quán)衡一下, 每秒100條日志輸出是否是合理的.

最后要提到的是日志框架中比較高級(jí)的功能: Mapped Diagnostic Context. MDC(http://www./api/org/slf4j/MDC.html )主要用來(lái)簡(jiǎn)化基于thread-local的map參數(shù)管理. 你可以往這個(gè)map中增加任何key-value內(nèi)容, 然后在隨后的日志輸出中作為模式的一部分, 與當(dāng)前線(xiàn)程一起輸出.

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

當(dāng)我們?cè)陂_(kāi)發(fā)過(guò)程中發(fā)現(xiàn)了一個(gè)bug, 一般我們會(huì)采用debug的方式一步步的跟蹤, 直到定位到最終的問(wèn)題位置(如果能通過(guò)寫(xiě)一個(gè)失敗的單元測(cè)試來(lái)暴露問(wèn)題, 那就更帥了^_^). 但是如果實(shí)際情況不允許你debug時(shí), 比如在客戶(hù)的系統(tǒng)上幾天前出現(xiàn)的bug. 如果你沒(méi)有詳細(xì)的日志的話(huà), 你能找到問(wèn)題的根源么?

如果你能根據(jù)一些簡(jiǎn)單的規(guī)則來(lái)輸出每個(gè)方法的輸入和輸出(參數(shù)和返回值). 你基本上可以扔掉調(diào)試器了. 當(dāng)然針對(duì)每一個(gè)方法加上日志必須是合理的: 訪(fǎng)問(wèn)外部資源(比如數(shù)據(jù)庫(kù)), 阻塞, 等待等等, 這些地方可以考慮加上日志. 比如下面的代碼:

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. }  


因?yàn)樵诜椒ㄕ{(diào)用前后加上了日志, 我們可以非常方便的發(fā)現(xiàn)代碼的性能問(wèn)題, 甚至找出死鎖和線(xiàn)程饑餓(starvation)等嚴(yán)重問(wèn)題:這種情況下都只有輸入(entering)日志, 不會(huì)有輸出(leaving)日志. 如果方法名類(lèi)名使用得當(dāng), 那么輸出的日志信息也將會(huì)非常賞心悅目. 因?yàn)槟憧梢愿鶕?jù)日志完整了解系統(tǒng)的運(yùn)行情況, 因此分析問(wèn)題的時(shí)候, 也將變得更加輕而易舉. 為了減少日志代碼, 也可以采用簡(jiǎn)單的AOP來(lái)做日志輸出. 但是也要小心, 這種做法可能產(chǎn)生大量的日志.

對(duì)于這種日志, 一般采用DEBUG/TRACE級(jí)別. 當(dāng)某些方法的調(diào)用非常頻繁, 那么大量的日志輸出將會(huì)影響到系統(tǒng)的性能, 此時(shí)我們可以提高相關(guān)類(lèi)的日志級(jí)別或者干脆去掉日志輸出. 不過(guò)一般情況下, 還是建議大家多輸出一些日志. 另外也可以將日志看成一種單元測(cè)試. 輸出的日志將像單元測(cè)試一樣, 會(huì)覆蓋到整個(gè)方法的執(zhí)行過(guò)程. 沒(méi)有日志的系統(tǒng)是不可想象的. 因此通過(guò)觀察日志的輸出將是我們了解系統(tǒng)是在正確的運(yùn)行還是掛了的唯一方式.

8) 用日志檢查外部系統(tǒng)

這里是針對(duì)前面的一種場(chǎng)景: 如果你的系統(tǒng)需要和其他系統(tǒng)通信, 那么需要考慮是否需要用日志記錄這種交互. 一般情況下, 如果一個(gè)應(yīng)用需要與多個(gè)系統(tǒng)進(jìn)行集成, 那么診斷和分析問(wèn)題將非常困難. 比如在最近的一個(gè)項(xiàng)目中, 由于我們?cè)贏pache CXF web服務(wù)上完整的記錄了消息數(shù)據(jù)(包括SOAP和HTTP頭信息), 使得我們?cè)谙到y(tǒng)集成和測(cè)試階段非常happy.

如果通過(guò)ESB的方式來(lái)多個(gè)系統(tǒng)進(jìn)行集成, 那么可以在總線(xiàn)(bus)上使用日志來(lái)記錄請(qǐng)求和響應(yīng). 這里可以參考Mules(http://www./)的<log-component/>(http://www./documentation/display/MULE2USER/Configuring+Components).

有時(shí)候與外部系統(tǒng)進(jìn)行通信產(chǎn)生的大量日志可能讓我們無(wú)法接受. 另一方面, 我們希望打開(kāi)日志臨時(shí)進(jìn)行一下測(cè)試, 或者在系統(tǒng)出現(xiàn)問(wèn)題的時(shí)候我們希望打開(kāi)短暫的輸出日志. 這樣我們可以在輸出日志和保證系統(tǒng)性能之間取得一個(gè)平衡. 這里我們需要借助日志日別. 比如像下面的這樣做:

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());  



在上面的代碼中, 如果日志級(jí)別配置為DEBUG, 那么將但應(yīng)所有的requestIds信息. 但是如果我們配置INFO級(jí)別, 那么只會(huì)輸出requestId的數(shù)量. 不過(guò)就像我們前面提到的日志的副作用那樣, 如果在INFO級(jí)別下requestIds為null將產(chǎn)生NullPointerException.

9) 正確輸出異常日志

對(duì)于日志輸出的第一條原則就是不要用日志輸出異常, 而是讓框架或者容器去處理. 記錄異常本不應(yīng)該是logger的工作. 而許多程序員都會(huì)用日志輸出異常, 然后可能返回一個(gè)默認(rèn)值(null, 0或者空字符串). 也可能在將異常包裝一下再拋出. 比如像下面的代碼這樣:

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


這樣做的結(jié)果可能會(huì)導(dǎo)致異常信息打印兩次(拋出的地方打一次, 捕獲處理的地方再打一次).

但是有時(shí)候我們確實(shí)希望打印異常, 那么應(yīng)該如何處理呢? 比如下面對(duì)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中編譯通過(guò), 其他的都會(huì)丟失異常堆棧信息或者打印了不恰當(dāng)?shù)男畔? 這里只要記住一條, 在日志中輸出異常信息, 第一個(gè)參數(shù)一定是一個(gè)字符串, 一般都是對(duì)問(wèn)題的描述信息, 而不能是異常message(因?yàn)槎褩@锩鏁?huì)有), 第二個(gè)參數(shù)才是具體的異常實(shí)例.

注: 對(duì)于遠(yuǎn)程調(diào)用類(lèi)型的服務(wù)拋出的異常,一定要注意實(shí)現(xiàn)序列化, 否則在客戶(hù)端將拋出NoClassDefFoundError異常, 而掩蓋了真實(shí)的異常信息

10) 讓日志易讀,易解析

對(duì)日志感興趣的可以分為兩類(lèi):
  • 人(比如程序員)
  • 機(jī)器(系統(tǒng)管理員寫(xiě)的shell腳本)


日志的內(nèi)容必須照顧到這兩個(gè)群體. 引用鮑勃大叔"Clean Code(http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)"一書(shū)的話(huà)來(lái)說(shuō):日志應(yīng)該像代碼一樣可讀并且容易理解.

另一方面, 如果一個(gè)系統(tǒng)每小時(shí)要輸出幾百M(fèi)B甚至上G的日志, 那么我們需要借助grep, sed以及awk來(lái)分析日志. 如果可能, 我們應(yīng)該讓日志盡可能的被人和機(jī)器理解. 比如, 避免格式化數(shù)字, 使用日志模式則可以方便用正則表達(dá)式進(jìn)行識(shí)別. 如果無(wú)法兼顧, 那么可以將數(shù)據(jù)用兩種格式輸出, 比如像下面這樣:

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, true, true);  
  5. log.info("Importing took: {}ms ({})", durationMillis, duration);  
  6. //Importing took: 123456789ms (1 day 10 hours 17 minutes 36 seconds)  


上面的日志, 既照顧了計(jì)算機(jī)("ms after 1970 epoch"這種時(shí)間格式), 又能更好的讓人能理解("1 day 10 hours 17 minutes 36 seconds") . 另外, 這里順便廣告一下DurationFormatUtils(http://commons./lang/api-release/org/apache/commons/lang/time/DateFormatUtils.html), 一個(gè)非常不錯(cuò)的工具:) 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多