java程序如何正確的打日誌

什麼是日誌

簡單的說,日誌就是記錄程序的運行軌跡,方便查找關鍵信息,也方便快速定位解決問題。css

咱們 Java 程序員在開發項目時都是依賴 Eclipse/ Idea 等開發工具的 Debug 調試功能來跟蹤解決 Bug,在開發環境能夠這麼作,但項目發佈到了測試、生產環境呢?你有可能會說可使用遠程調試,但實際並不能容許讓你這麼作。java

因此,日誌的做用就是在測試、生產環境沒有 Debug 調試工具時開發、測試人員定位問題的手段。日誌打得好,就能根據日誌的軌跡快速定位並解決線上問題,反之,日誌輸出很差不能定位到問題不說反而會影響系統的性能。程序員

優秀的項目都是能根據日誌定位問題的,而不是在線調試,或者半天找不到有用的日誌而抓狂…apache

經常使用日誌框架

log4j、Logging、commons-logging、slf4j、logback,開發的同窗對這幾個日誌相關的技術不陌生吧,爲何有這麼多日誌技術,它們都是什麼區別和聯繫呢?相信大多數人搞不清楚它們的關係,下面我將一一介紹一下,之後你們不再用傻傻分不清楚了。編程

Logging

這是 Java 自帶的日誌工具類,在 JDK 1.5 開始就已經有了,在 java.util.logging 包下。設計模式

Log4j

Log4j 是 Apache 的一個開源日誌框架,也是市場佔有率最多的一個框架。大多數沒用過 Java Logging, 但沒人敢說沒用過 Log4j 吧,反正從我接觸 Java 開始就是這種狀況,作 Java 項目必有 Log4j 日誌框架。tomcat

注意: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的組件更加快速的同時,同時所需的內存更加少。

更多請參考《從Log4j遷移到LogBack的理由 》

日誌框架總結

  1. commons-logginslf4j 只是一種日誌抽象門面,不是具體的日誌框架。
  2. log4jlogback 是具體的日誌實現框架。
  3. 通常首選強烈推薦使用 slf4j + logback。固然也可使用slf4j + log4jcommons-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),實現類能夠是 Log4jLogback等日誌實現框架,不要把實現類定義爲變量類型,不然日誌切換不方便,也不符合抽象編程思想。

二、使用參數化形式{}佔位,[] 進行參數隔離
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 判斷,簡化代碼。

三、輸出不一樣級別的日誌

項目中最經常使用有日誌級別是ERRORWARNINFODEBUG四種了,這四個都有怎樣的應用場景呢。

  • 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

相關文章
相關標籤/搜索