什么是日志
簡單的說,日志就是記錄程序的運行軌跡,方便查找關鍵信息,也方便快速定位解決問題。
我們 Java 程序員在開發項目時都是依賴 Eclipse/ Idea 等開發工具的 Debug 調試功能來跟蹤解決 Bug,在開發環境可以這么做,但項目發布到了測試、生產環境呢?你有可能會說可以使用遠程調試,但實際並不能允許讓你這么做。
所以,日志的作用就是在測試、生產環境沒有 Debug 調試工具時開發、測試人員定位問題的手段。日志打得好,就能根據日志的軌跡快速定位並解決線上問題,反之,日志輸出不好不能定位到問題不說反而會影響系統的性能。
優秀的項目都是能根據日志定位問題的,而不是在線調試,或者半天找不到有用的日志而抓狂…
常用日志框架
log4j、Logging、commons-logging、slf4j、logback,開發的同學對這幾個日志相關的技術不陌生吧,為什么有這么多日志技術,它們都是什么區別和聯系呢?相信大多數人搞不清楚它們的關系,下面我將一一介紹一下,以后大家再也不用傻傻分不清楚了。
Logging
這是 Java 自帶的日志工具類,在 JDK 1.5 開始就已經有了,在 java.util.logging
包下。
Log4j
Log4j 是 Apache 的一個開源日志框架,也是市場占有率最多的一個框架。大多數沒用過 Java Logging, 但沒人敢說沒用過 Log4j 吧,反正從我接觸 Java 開始就是這種情況,做 Java 項目必有 Log4j 日志框架。
注意:log4j 在 2015/08/05 這一天被 Apache 宣布停止維護了,用戶需要切換到 Log4j2上面去。
commons-logging
上面介紹的 log4j 是一個具體的日志框架的實現,而 commons-logging 就是日志的門面接口,它也是 apache 最早提供的日志門面接口,用戶可以根據喜好選擇不同的日志實現框架,而不必改動日志定義,這就是日志門面的好處,符合面對接口抽象編程。
Slf4j
全稱:Simple Logging Facade for Java,即簡單日志門面接口,和 Apache 的 commons-logging 是一樣的概念,它們都不是具體的日志框架,你可以指定其他主流的日志實現框架。
Slf4j 也是現在主流的日志門面框架,使用 Slf4j 可以很靈活的使用占位符進行參數占位,簡化代碼,擁有更好的可讀性,這個后面會講到。
Logback
Logback 是 Slf4j
的原生實現框架,同樣也是出自 Log4j
一個人之手,但擁有比 log4j
更多的優點、特性和更做強的性能,現在基本都用來代替 log4j
成為主流。
為什么 Logback 會成為主流?
無論從設計上還是實現上,Logback相對log4j而言有了相對多的改進。不過盡管難以一一細數,這里還是列舉部分理由為什么選擇logback而不是log4j。牢記logback與log4j在概念上面是很相似的,它們都是有同一群開發者建立。所以如果你已經對log4j很熟悉,你也可以很快上手logback。如果你喜歡使用log4j,你也許會迷上使用logback。
更快的執行速度
基於我們先前在log4j上的工作,logback 重寫了內部的實現,在某些特定的場景上面,甚至可以比之前的速度快上10倍。在保證logback的組件更加快速的同時,同時所需的內存更加少。
日志框架總結
commons-loggin
、slf4j
只是一種日志抽象門面,不是具體的日志框架。log4j
、logback
是具體的日志實現框架。- 一般首選強烈推薦使用
slf4j + logback
。當然也可以使用slf4j + log4j
、commons-logging + log4j
這兩種日志組合框架。
從上圖可以看出 slf4j
很強大吧,不但能和各種日志框架對接,還能和日志門面 commons-logging
進行融合。
日志級別詳解
日志的輸出都是分級別的,不同的設置不同的場合打印不同的日志。下面拿最普遍用的 Log4j
日志框架來做個日志級別的說明,這個也比較奇全,其他的日志框架也都大同小異。
Log4j 的級別類 org.apache.log4j.Level
里面定義了日志級別,日志輸出優先級由高到底分別為以下8種。
日志級別 | 描述 |
---|---|
OFF | 關閉:最高級別,不輸出日志。 |
FATAL | 致命:輸出非常嚴重的可能會導致應用程序終止的錯誤。 |
ERROR | 錯誤:輸出錯誤,但應用還能繼續運行。 |
WARN | 警告:輸出可能潛在的危險狀況。 |
INFO | 信息:輸出應用運行過程的詳細信息。 |
DEBUG | 調試:輸出更細致的對調試應用有用的信息。 |
TRACE | 跟蹤:輸出更細致的程序運行軌跡。 |
ALL | 所有:輸出所有級別信息。 |
所以,日志級別優先級標准順序為:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF,all優先級最小,off優先級最大。
如果日志設置為 L
,一個級別為 P
的輸出日志只有當 P >= L
時日志才會輸出。
即如果日志級別 L 設置 INFO,只有 P 的輸出級別為 INFO、WARN,后面的日志才會正常輸出。
打日志的規范准則
最開始也說過了,日志不能亂打,不然起不到日志本應該起到的作用不說,還會造成系統的負擔。在 BAT、華為一些大公司都是對日志規范有要求的,什么時候該打什么日志都是有規范的。
1. 正確的定義日志
private static final Logger LOG = LoggerFactory.getLogger(this.getClass());
通常一個類只有一個 LOG 對象,如果有父類可以將 LOG 定義在父類中。
日志變量類型定義為門面接口(如 slf4j 的 Logger),實現類可以是 Log4j
、Logback
等日志實現框架,不要把實現類定義為變量類型,否則日志切換不方便,也不符合抽象編程思想。
2、使用參數化形式{}
占位,[]
進行參數隔離
LOG.debug("Save order with order no:[{}], and order amount:[{}]");
這種可讀性好,這樣一看就知道[]
里面是輸出的動態參數,{}
用來占位類似綁定變量,而且只有真正准備打印的時候才會處理參數,方便定位問題。
如果日志框架不支持參數化形式,且日志輸出時不支持該日志級別時會導致對象冗余創建,浪費內存,此時就需要使用 isXXEnabled
判斷,如:
if(LOG.isDebugEnabled()){
// 如果日志不支持參數化形式,debug又沒開啟,那字符串拼接就是無用的代碼拼接,影響系統性能
logger.debug("Save order with order no:" + orderNo + ", and order amount:" + orderAmount);
}
至少 debug
級別是需要開啟判斷的,線上日志級別至少應該是 info
以上的。
這里推薦大家用 SLF4J
的門面接口,可以用參數化形式輸出日志,debug
級別也不必用 if
判斷,簡化代碼。
3、輸出不同級別的日志
項目中最常用有日志級別是ERROR
、WARN
、INFO
、DEBUG
四種了,這四個都有怎樣的應用場景呢。
- ERROR(錯誤)
一般用來記錄程序中發生的任何異常錯誤信息(Throwable),或者是記錄業務邏輯出錯。
- WARN(警告)
一般用來記錄一些用戶輸入參數錯誤、
- INFO(信息)
這個也是平時用的最低的,也是默認的日志級別,用來記錄程序運行中的一些有用的信息。如程序運行開始、結束、耗時、重要參數等信息,需要注意有選擇性的有意義的輸出,到時候自己找問題看一堆日志卻找不到關鍵日志就沒意義了。
- DEBUG(調試)
這個級別一般記錄一些運行中的中間參數信息,只允許在開發環境開啟,選擇性在測試環境開啟。
幾個錯誤的打日志方式
1. 不要使用 System.out.print..
輸出日志的時候只能通過日志框架來輸出日志,而不能使用 System.out.print..
來打印日志,這個只會打印到 tomcat
控制台,而不會記錄到日志文件中,不方便管理日志,如果通過服務形式啟動把日志丟棄了那更是找不到日志了。
2. 不要使用 e.printStackTrace()
首先來看看它的源碼:
public void printStackTrace() {
printStackTrace(System.err);
}
它其實也是利用 System.err
輸出到了 tomcat
控制台。
3. 不要拋出異常后又輸出日志
如捕獲異常后又拋出了自定義業務異常,此時無需記錄錯誤日志,由最終捕獲方進行異常處理。不能又拋出異常,又打印錯誤日志,不然會造成重復輸出日志。
try {
// ...
} catch (Exception e) {
// 錯誤
LOG.error("xxx", e);
throw new RuntimeException();
}
4. 不要使用具體的日志實現類
InterfaceImpl interface = new InterfaceImpl();
這段代碼大家都看得懂吧?應該面向接口的對象編程,而不是面向實現,這也是軟件設計模式的原則,正確的做法應該是。
Interface interface = new InterfaceImpl();
日志框架里面也是如此,上面也說了,日志有門面接口,有具體實現的實現框架,所以大家不要面向實現編程。
5. 沒有輸出全部錯誤信息
看以下代碼,這樣不會記錄詳細的堆棧異常信息,只會記錄錯誤基本描述信息,不利於排查問題。
try {
// ...
} catch (Exception e) {
// 錯誤
LOG.error('XX 發生異常', e.getMessage());
// 正確
LOG.error('XX 發生異常', e);
}
6. 不要使用錯誤的日志級別
try {
// ...
} catch (Exception e) {
// 錯誤
LOG.info("XX 發生異常...", e);
}
大家看出了什么問題嗎?用 info
記錄 error
日志,日志輸出到了 info
日志文件中了,同事拼命地在 error
錯誤日志文件里面找怎么能找到呢?
7. 不要在千層循環中打印日志
這個是什么意思,如果你的框架使用了性能不高的 Log4j
框架,那就不要在上千個 for
循環中打印日志,這樣可能會拖垮你的應用程序,如果你的程序響應時間變慢,那要考慮是不是日志打印的過多了。
for(int i=0; i<2000; i++){
LOG.info("XX");
}
最好的辦法是在循環中記錄要點,在循環外面總結打印出來。
8. 禁止在線上環境開啟 debug
這是最后一點,也是最重要的一點。
一是因為項目本身 debug
日志太多,二是各種框架中也大量使用 debug
的日志,線上開啟 debug
不久就會打滿磁盤,影響業務系統的正常運行。
轉自:https://blog.csdn.net/juncle113/article/details/80973547