天天看點

天啦撸!列印日志竟然隻曉得 Log4j?(2)

04、為什麼選擇 Log4j 而不是 java.util.logging

java.util.logging 屬于原生的日志 API,Log4j 屬于第三方類庫,但我建議使用 Log4j,因為 Log4j 更好用。java.util.logging 的日志級别比 Log4j 更多,但用不着,就變成了多餘。

Log4j 的另外一個好處就是,不需要重新啟動 Java 程式就可以調整日志的記錄級别,非常靈活。可以通過 log4j.properties 檔案來配置 Log4j 的日志級别、輸出環境、日志檔案的記錄方式。

Log4j 還是線程安全的,可以在多線程的環境下放心使用。

先來看一下 java.util.logging 的使用方式:

package com.itwanger;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
/**
 * @author 微信搜「沉默王二」,回複關鍵字 PDF
 */
public class JavaUtilLoggingDemo {
    public static void main(String[] args) throws IOException {
        Logger logger = Logger.getLogger("test");
        FileHandler fileHandler = new FileHandler("javautillog.txt");
        fileHandler.setFormatter(new SimpleFormatter());
        logger.addHandler(fileHandler);
        logger.info("細小的資訊");
    }
}      

程式運作後會在 target 目錄下生成一個名叫 javautillog.txt 的檔案,内容如下所示:

天啦撸!列印日志竟然隻曉得 Log4j?(2)

再來看一下 Log4j 的使用方式。

第一步,在 pom.xml 檔案中引入 Log4j 包:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>      

第二步,在 resources 目錄下建立 log4j.properties 檔案,内容如下所示:

### 設定###

log4j.rootLogger = debug,stdout,D,E

### 輸出資訊到控制台 ###

log4j.appender.stdout = org.apache.log4j.ConsoleAppender

log4j.appender.stdout.Target = System.out

log4j.appender.stdout.layout = org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 輸出DEBUG 級别以上的日志到=debug.log ###

log4j.appender.D = org.apache.log4j.DailyRollingFileAppender

log4j.appender.D.File = debug.log

log4j.appender.D.Append = true

log4j.appender.D.Threshold = DEBUG

log4j.appender.D.layout = org.apache.log4j.PatternLayout

log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 輸出ERROR 級别以上的日志到=error.log ###

log4j.appender.E = org.apache.log4j.DailyRollingFileAppender

log4j.appender.E.File =error.log

log4j.appender.E.Append = true

log4j.appender.E.Threshold = ERROR

log4j.appender.E.layout = org.apache.log4j.PatternLayout

log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

1)配置根 Logger,文法如下所示:

log4j.rootLogger = [ level ] , appenderName, appenderName, …

level 就是日志的優先級,從高到低依次是 ERROR、WARN、INFO、DEBUG。如果這裡定義的是 INFO,那麼低級别的 DEBUG 日志資訊将不會列印出來。

appenderName 就是指把日志資訊輸出到什麼地方,可以指定多個地方,目前的配置檔案中有 3 個地方,分别是 stdout、D、E。

2)配置日志輸出的目的地,文法如下所示:

log4j.appender.appenderName = fully.qualified.name.of.appender.class  

log4j.appender.appenderName.option1 = value1  

…  

log4j.appender.appenderName.option = valueN

Log4j 提供的目的地有下面 5 種:

org.apache.log4j.ConsoleAppender:控制台

org.apache.log4j.FileAppender:檔案

org.apache.log4j.DailyRollingFileAppender:每天産生一個檔案

org.apache.log4j.RollingFileAppender:檔案大小超過門檻值時産生一個新檔案

org.apache.log4j.WriterAppender:将日志資訊以流格式發送到任意指定的地方

3)配置日志資訊的格式,文法如下所示:

log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class  

log4j.appender.appenderName.layout.option1 = value1  

log4j.appender.appenderName.layout.option = valueN

Log4j 提供的格式有下面 4 種:

org.apache.log4j.HTMLLayout:HTML 表格

org.apache.log4j.PatternLayout:自定義

org.apache.log4j.SimpleLayout:包含日志資訊的級别和資訊字元串

org.apache.log4j.TTCCLayout:包含日志産生的時間、線程、類别等等資訊

自定義格式的參數如下所示:

%m:輸出代碼中指定的消息

%p:輸出優先級

%r:輸出應用啟動到輸出該日志資訊時花費的毫秒數

%c:輸出所在類的全名

%t:輸出該日志所在的線程名

%n:輸出一個回車換行符

%d:輸出日志的時間點

%l:輸出日志的發生位置,包括類名、線程名、方法名、代碼行數,比如:method:com.itwanger.Log4jDemo.main(Log4jDemo.java:14)

第三步,寫個使用 Demo:

package com.itwanger;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
/**
 * @author 微信搜「沉默王二」,回複關鍵字 PDF
 */
public class Log4jDemo {
    private static final Logger logger = LogManager.getLogger(Log4jDemo.class);
    public static void main(String[] args) {
        // 記錄debug級别的資訊
        logger.debug("debug.");
        // 記錄info級别的資訊
        logger.info("info.");
        // 記錄error級别的資訊
        logger.error("error.");
    }
}      

1)擷取 Logger 對象

要使用 Log4j 的話,需要先擷取到 Logger 對象,它用來負責日志資訊的列印。通常的格式如下所示:

private static final Logger logger = LogManager.getLogger(Log4jDemo.class);

1

2)列印日志

有了 Logger 對象後,就可以按照不同的優先級列印日志了。常見的有以下 4 種:

Logger.debug() ;  

Logger.info() ;  

Logger.warn() ;  

Logger.error() ;

程式運作後會在 target 目錄下生成兩個檔案,一個名叫 debug.log,内容如下所示:

2020-10-20 20:53:27  [ main:0 ] - [ DEBUG ]  debug.

2020-10-20 20:53:27  [ main:3 ] - [ INFO ]  info.

2020-10-20 20:53:27  [ main:3 ] - [ ERROR ]  error.

另外一個名叫 error.log,内容如下所示:

05、列印日志的 8 個小技巧

1)在列印 DEBUG 級别的日志時,切記要使用 isDebugEnabled()!那小夥伴們肯定非常好奇,為什麼要這樣做呢?

先來看一下 isDebugEnabled() 方法的源碼:

 public

 boolean isDebugEnabled() {

   if(repository.isDisabled( Level.DEBUG_INT))

     return false;

   return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());

 }

内部使用了 isDisabled() 方法進行了日志級别的判斷,如果 DEBUG 是禁用的話,就 return false 了。

再來看一下 debug() 方法的源碼:

public
  void debug(Object message) {
    if(repository.isDisabled(Level.DEBUG_INT))
      return;
    if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
      forcedLog(FQCN, Level.DEBUG, message, null);
    }
  }      

咦,不是也用 isDisabled() 方法判斷嗎?難道使用 isDebugEnabled()不是畫蛇添足嗎?直接用 logger.debug() 不香嗎?我來給小夥伴們解釋下。

如果我們在列印日志資訊的時候需要附帶一個方法去擷取參數值,就像下面這樣:

logger.debug("使用者名是:" + getName());

假如 getName() 方法需要耗費的時間長達 6 秒,那完了!盡管配置檔案裡的日志級别定義的是 INFO,getName() 方法仍然會倔強地執行 6 秒,完事後再 debug(),這就很崩了!

明明 INFO 的時候 debug() 是不執行的,意味着 getName() 也不需要執行的,偏偏就執行了 6 秒,是不是很傻?

if(logger.isDebugEnabled()) {

   logger.debug("使用者名是:" + getName());

}

換成上面這種方式,那确定此時 getName() 是不執行的,對吧?

為了程式性能上的考量,isDebugEnabled() 就變得很有必要了!假如說 debug() 的時候沒有傳參,确實是不需要判斷 DEBUG 是否啟用的。

2)慎重選擇日志資訊的列印級别,因為這太重要了!如果隻能通過日志檢視程式發生了什麼問題,那必要的資訊是必須要列印的,但列印得太多,又會影響到程式的性能。

是以,該 INFO 的 info(),該 DEBUG 的 debug(),不要随便用。

3)使用 Log4j 而不是 System.out、System.err 或者 e.printStackTrace() 來列印日志,原因之前講過了,就不再贅述了。

4)使用 log4j.properties 檔案來配置日志,盡管它不是必須項,使用該檔案會讓程式變得更靈活,有一種我的地盤我做主的味道。

5)不要忘記在列印日志的時候帶上類的全名和線程名,在多線程環境下,這點尤為重要,否則定位問題的時候就太難了。

6)列印日志資訊的時候盡量要完整,不要太過于預設,尤其是在遇到異常或者錯誤的時候(資訊要保留兩類:案發現場資訊和異常堆棧資訊,如果不做處理,通過 throws 關鍵字往上抛),免得在找問題的時候都是一些無用的日志資訊。

7)要對日志資訊加以區分,把某一類的日志資訊在輸出的時候加上字首,比如說所有資料庫級别的日志裡添加 DB_LOG,這樣的日志非常大的時候可以通過 grep 這樣的 Linux 指令快速定位。

8)不要在日志檔案中列印密碼、銀行賬号等敏感資訊。

06、 總結

列印日志真的是一種藝術活,搞不好會嚴重影響伺服器的性能。最可怕的是,記錄了日志,但最後發現屁用沒有,那簡直是蒼了個天啊!尤其是在生産環境下,問題沒有記錄下來,但重制有一定的随機性,到那時候,真的是叫天天不應,叫地地不靈啊!

嗯哼,其實我已經寫完了整個日志系統,包括 Log4j、 SLF4J、Logback、Log4j 它弟 Log4j 2,但我覺得分開來發的話,更利于 SEO(瞧我這為了流量的心機,手動狗頭)。如果你确實需要看完整版的話,我也貼心地為你準備了,點選下面的連結就可以下載下傳 PDF:

https://pan.baidu.com/s/1dPwsQhT5OMVapE7hGi7vww

提取碼:fxxy