在看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 oforg.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 oforg.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 totrue
if you want the Log instance name to be included in output messages. Defaults tofalse
.org.apache.commons.logging.simplelog.showShortLogname
- Set totrue
if you want the last component of the name to be included in output messages. Defaults totrue
.org.apache.commons.logging.simplelog.showdatetime
- Set totrue
if you want the current date and time to be included in output messages. Default isfalse
.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 injava.text.SimpleDateFormat
. If the format is not specified or is invalid, the default format is used. The default format isyyyy/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程式就能看到日誌的變化。