Java中各種Log的使用

拽根胡來發表於2019-11-10

在看Spring原始碼的時候發現Spring中使用的Log是commons.logging中的Log,而不是我們常用的log4j。實際上commons.logging中的Log只提供一個呼叫Log的介面,並沒有任何具體的實現,當我們呼叫commons.logging.LogFactory#getLog生成一個Log物件的時候,LogFactory會檢查當前環境是否含有其他的Log實現。如果有log4j的話會直接使用log4j中的LogManager來生成Log。如果沒有其他的任何Log實現,則會呼叫jdk提供的Log實現。

commons.logging似乎只適用於Spring這樣的框架型應用,使用的時候可以直接利用專案中原有的配置(比如log4j)。在其他常規應用中應該直接使用log4j這樣的具體Log實現,而不是commons.logging中的Log。

本文分析了直接使用commons.logging中的Log和配合使用其他Log工具的不同情況,給出了常用Log類的使用方法。

直接使用commons.logging中的Log

先在pom依賴中新增commons.logging:

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
</dependency>
複製程式碼

然後在程式中直接呼叫:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LogExample {
    private static Log logger = LogFactory.getLog(LogExample.class.getName());
    public static void main(String[] args){
        System.out.println(logger.getClass());
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}
複製程式碼

控制檯輸出的結果:

class org.apache.commons.logging.impl.Jdk14Logger
十一月 09, 2019 7:08:56 下午 j.LogExample main
嚴重: This is an error information!
十一月 09, 2019 7:08:56 下午 j.LogExample main
資訊: This is an info information!
十一月 09, 2019 7:08:56 下午 j.LogExample main
警告: This is an warn information!
十一月 09, 2019 7:08:56 下午 j.LogExample main
嚴重: This is a fatal information!
複製程式碼

可以看到

  • 預設列印info及以上級別的資訊。debug<info<warn<error<fatal。
  • 使用的是Logger類是Jdk14Logger。此時沒有配置任何其他Log類,因此使用的是jdk自帶的一個Log實現。

配合使用log4j

在pom中新增log4j的依賴:

<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
  <type>bundle</type>
</dependency>
複製程式碼

原來的程式碼不修改,執行結果為:

class org.apache.commons.logging.impl.Log4JLogger
log4j:WARN No appenders could be found for logger (j.LogExample).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
複製程式碼

可以看到

  • commons.logging會掃描當前環境的相關依賴,如果有log4j則會直接使用log4j的Log實現。
  • 使用log4j需要先配置appender,如果沒有配置則不會有任何輸出。一般至少應該配置一個appender用於標準輸出(System.out)

新增log4j.properties配置檔案

在classpath中建立檔案log4j.properties(如果是intellij的話,將該檔案建立到被標記了resources的資料夾下)。寫入配置資訊:

log4j.rootLogger = DEBUG,console,file

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%-5p] %d [%t] %l: %m %n

log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.ImmediateFlush=true
log4j.appender.file.Append=true
log4j.appender.file.File=log.txt
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%-5p] %d [%t] %l: %m %n
複製程式碼

可以看到上面的檔案中配置了兩個appender,一個console用於控制檯列印(ConsoleAppender),一個file用於檔案輸出(FileAppender)。日誌輸出的最低階別為DEBUG。配置方式具體參考Log4j.properties配置詳解

在原始檔不修改的情況下再次執行,得到的結果為:

class org.apache.commons.logging.impl.Log4JLogger
[DEBUG] 2019-11-09 20:00:55,427 [main] j.LogExample.main(LogExample.java:10): This is a debug information! 
[ERROR] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:11): This is an error information! 
[INFO ] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:12): This is an info information! 
[WARN ] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:13): This is an warn information! 
[FATAL] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:14): This is a fatal information! 
複製程式碼

使用其他檔案配置

檢視log4j的原始碼,在LogManager類中看到預設設定了兩種檔案自動配置的方式,分別為log4j.properties和log4j.xml:

static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  
複製程式碼

預設是先檢查log4j.xml,然後再檢查log4j.properties。

如果不使用預設的路徑,需要修改檔案的路徑,則可以使用PropertyConfigurator.configure("config/log4j.my.properties");這樣的方式來配置。注意,config是專案根目錄的一個資料夾,而不是resources目錄中的資料夾。

使用PropertyConfigurator.configure配置的時候是根據字串引數建立一個FileInputStream,因此是直接從檔案系統中讀取;而預設讀取配置檔案的方式是使用java.lang.ClassLoader中的getResource方法來讀取,讀取的位置跟classpath有關,(Maven專案中)通常在target資料夾裡面。在Intellij中如果將某個資料夾(比如resources)標記為resource root,則該資料夾下的所有內容都會直接拷貝到target中,也就是classpath目錄下。

原來的程式碼改為:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;

public class LogExample {
    private static Log logger = LogFactory.getLog(LogExample.class.getName());
    static{
        PropertyConfigurator.configure("config/log4j.my.properties");
    }
    public static void main(String[] args){
        System.out.println(logger.getClass());
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}
複製程式碼

單獨使用Java程式碼配置log4j

一般較少使用程式碼配置。相比於配置檔案而言更麻煩且不靈活。

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

import java.io.IOException;

public class LogExampleConfigureByJava {
    private static Logger logger = Logger.getLogger(LogExampleConfigureByJava.class.getName());

    static {
        logger.setLevel(Level.DEBUG);
        logger.addAppender(new ConsoleAppender(new PatternLayout("%-6r [%p] %c - %m%n")));
    }

    public static void main(String[] args) {
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}
複製程式碼

其他commons.logging支援的Log工具(不適用於spring-jcl-5.1)

在commons.logging.impl.LogFactoryImpl中可以看到commons.logging支援的Log類:

/** Log4JLogger class name */
private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
/** Jdk14Logger class name */
private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger";
/** Jdk13LumberjackLogger class name */
private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger";
/** SimpleLog class name */
private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog";
複製程式碼

也就是:

  • Log4JLogger
  • Jdk14Logger
  • Jdk13LumberjackLogger
  • SimpleLog

Use the org.apache.commons.logging.Log system property to identify the requested implementation class. If Log4J is available, return an instance of org.apache.commons.logging.impl.Log4JLogger. If JDK 1.4 or later is available, return an instance of* org.apache.commons.logging.impl.Jdk14Logger. Otherwise, return an instance of org.apache.commons.logging.impl.SimpleLog.

LogFactoryImpl掃描的順序為:如果log4j可用的話,會使用Log4JLogger;否則,如果jdk版本為1.4以上的話,會使用Jdk14Logger;否則,會使用SimpleLog。

SimpleLog

SimpleLog使用起來很簡單,格式也很簡單:

import org.apache.commons.logging.impl.SimpleLog;

public class SimpleLogExample {
    private static SimpleLog logger = new SimpleLog(SimpleLogExample.class.getName());

    static {
        logger.setLevel(SimpleLog.LOG_LEVEL_DEBUG);
    }

    public static void main(String[] args) {
        System.out.println(logger.getClass());
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}
複製程式碼

執行結果:

class org.apache.commons.logging.impl.SimpleLog
[ERROR] SimpleLogExample - This is an error information!
[INFO] SimpleLogExample - This is an info information!
[WARN] SimpleLogExample - This is an warn information!
[FATAL] SimpleLogExample - This is a fatal information!
複製程式碼

預設情況下,SimpleLog會將所有log資訊(可設定level篩選,預設為info)列印到System.err,輸出格式比較簡單,不能使用程式碼配置輸出格式。

SimpleLog可以使用配置檔案來配置(同樣只能輸出到System.err),參考官方文件,配置檔案地址為simplelog.properties,使用ClassLoader載入,配置項有:

  • org.apache.commons.logging.simplelog.defaultlog - Default logging detail level for all instances of SimpleLog. Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, defaults to "info".
  • org.apache.commons.logging.simplelog.log.xxxxx - Logging detail level for a SimpleLog instance named "xxxxx". Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, the default logging detail level is used.
  • org.apache.commons.logging.simplelog.showlogname - Set to true if you want the Log instance name to be included in output messages. Defaults to false.
  • org.apache.commons.logging.simplelog.showShortLogname - Set to true if you want the last component of the name to be included in output messages. Defaults to true.
  • org.apache.commons.logging.simplelog.showdatetime - Set to true if you want the current date and time to be included in output messages. Default is false.
  • org.apache.commons.logging.simplelog.dateTimeFormat - The date and time format to be used in the output messages. The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. If the format is not specified or is invalid, the default format is used. The default format is yyyy/MM/dd HH:mm:ss:SSS zzz.

其他commons.logging支援的Log工具(spring-jcl-5.1)

在Spring中,日誌相關的工具放在jcl(Jakarta Commons Logging API)包裡面。

JCL會掃描當前系統中的slf4j和log4j兩種Log實現,優先考慮slf4j。我在系統中配置好了log4j的各項引數(log4j.properties),使用commons.loggging.Log執行起來後發現跟預期不符,把Logger類名列印出來才發現實際呼叫的是slf4j。另外,jcl中的SimpleLog和上面提到的SimpleLog是不一樣的,jcl中的SimpleLog不包含任何實現,對於任何log資訊都不做處理(甚至不列印出來)。

如果要使用log4j的話可以顯式呼叫log4j包裡面的Logger#getLogger。

如果是要使用Spring Boot自帶的log工具(slf4j或其他自動查詢到的Log實現),需要在application.properties中統一配置,比如:

logging.path=logs
logging.file=${logging.path}/log.log
logging.level.root=info
logging.level.com.example=warn
logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger.%M - %msg%n
logging.pattern.file=%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n
複製程式碼

設定之後直接執行SprringBoot程式就能看到日誌的變化。

參考

相關文章