Java 如何正確地輸出日誌

著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

什麼是日誌

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

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

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

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

經常使用日誌框架

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

Logging

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

更多關於 Java Logging 的介紹能夠看官方文檔tomcat

Log4j

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

注意:log4j 在 2015/08/05 這一天被 Apache 宣佈中止維護了,用戶須要切換到 Log4j2上面去。微信

下面是官方宣佈原文:oracle

On August 5, 2015 the Logging Services Project Management Committee announced that Log4j 1.x had reached end of life. For complete text of the announcement please see the Apache Blog. Users of Log4j 1 are recommended to upgrade to Apache Log4j 2.
複製代碼

Log4j2 的官方地址

commons-logging

上面介紹的 log4j 是一個具體的日誌框架的實現,而 commons-logging 就是日誌的門面接口,它也是 apache 最先提供的日誌門面接口,用戶能夠根據喜愛選擇不一樣的日誌實現框架,而沒必要改動日誌定義,這就是日誌門面的好處,符合面對接口抽象編程。

更多的詳細說明能夠參考官方說明

Slf4j

全稱:Simple Logging Facade for Java,即簡單日誌門面接口,和 Apache 的 commons-logging 是同樣的概念,它們都不是具體的日誌框架,你能夠指定其餘主流的日誌實現框架。

Slf4j 的官方地址

Slf4j 也是如今主流的日誌門面框架,使用 Slf4j 能夠很靈活的使用佔位符進行參數佔位,簡化代碼,擁有更好的可讀性,這個後面會講到。

Logback

Logback 是 Slf4j 的原生實現框架,一樣也是出自 Log4j 一我的之手,但擁有比 log4j 更多的優勢、特性和更作強的性能,如今基本都用來代替 log4j 成爲主流。

Logback 的官方地址

爲何 Logback 會成爲主流?

不管從設計上仍是實現上,Logback相對log4j而言有了相對多的改進。不過儘管難以一一細數,這裏仍是列舉部分理由爲何選擇logback而不是log4j。牢記logback與log4j在概念上面是很類似的,它們都是有同一羣開發者創建。因此若是你已經對log4j很熟悉,你也能夠很快上手logback。若是你喜歡使用log4j,你也許會迷上使用logback。

更快的執行速度

基於咱們先前在log4j上的工做,logback 重寫了內部的實現,在某些特定的場景上面,甚至能夠比以前的速度快上10倍。在保證logback的組件更加快速的同時,同時所需的內存更加少。

更多請參考《從Log4j遷移到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

若是日誌設置爲 L ,一個級別爲 P 的輸出日誌只有當 P >= L 時日誌纔會輸出。

即若是日誌級別 L 設置 INFO,只有 P 的輸出級別爲 INFO、WARN,後面的日誌纔會正常輸出。

具體的輸出關係能夠參考下圖:

知道了日誌級別,這還只是基礎,如何瞭解打日誌的規範,以及如何正確地打日誌姿式呢?!

打日誌的規範準則

最開始也說過了,日誌不能亂打,否則起不到日誌本應該起到的做用不說,還會形成系統的負擔。在 BAT、華爲一些大公司都是對日誌規範有要求的,何時該打什麼日誌都是有規範的。

阿里去年發佈的《Java 開發手冊》,裏面有一章節就是關於日誌規範的,讓咱們再來回顧下都有什麼內容。

下面是阿里的《Java開發手冊》終極版日誌規約篇。

規範有不少,這裏就再也不一一詳述了,完整終極版能夠在微信公衆號 "Java 技術棧" 中回覆 "手冊" 獲取。這裏只想告訴你們,在大公司打日誌都是有嚴格規範的,不是你隨便打就行的。

阿里是一線互聯網公司,所制定的日誌規範也都符合咱們的要求,頗有參考意義,能把阿里這套日誌規約普及也真很不錯了。

項目中該如何正確的打日誌?

  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 不久就會打滿磁盤,影響業務系統的正常運行。

相關文章
相關標籤/搜索