前言
作爲java程序員,在工作開發中遇到最多的一個問題就是打日誌(log),好的日誌方式可以幫助你事半功倍的監控線上程序運行的鏈路,出現bug時可以快速定位,但是,面對現如今衆多的日誌框架中,如何去選擇哪個日誌框架,成爲困擾很多程序員的一個問題,他們的性能怎們樣,他們有什麼關係,今天我們就來把一把log的那些事…
log框架及發展史
-
Log4j: apache基於java的日誌框架
-
Log4j2: apache基於log4j的升級版本
-
JUL: 2002年,java 1.4發佈,定義了java標準的日誌格式,用java.until.logging日誌框架來打印日誌,但是貌似不太好用(模仿了log4j)
-
Apache Commons Logging: apache旗下的門面日誌
-
Slf4j: (simple log facade for java 簡單的java日誌門面),即它不負責寫日誌,而是**「提供用一個統一的接口,通過jar來決定使用的日誌框架」**
-
Logback: 相對於Slf4j,性能更高、功能更全,logback 分爲三個模塊:logback-core,logback-classic和logback-access。
-
logback-core:模塊爲其他兩個模塊的基礎。
-
logback-classic:模塊可以被看做是log4j的改進版本。此外,logback-classic 本身實現了 SLF4J API,因此可以在 logback 和其他日誌框架(如 log4j 或 java.util.logging(JUL))之間來回切換。
-
logback-access:模塊與 Servlet 容器(如 Tomcat 和 Jetty)集成,以提供 HTTP 訪問日誌功能。
日誌框架的使用
下面代碼展示了幾個日誌框架使用的大概雛形:
// 使用log4j,需要log4j.jar
import org.apache.log4j.Logger;
Logger logger_log4j = Logger.getLogger(Test.class);
logger_log4j.info("Hello World!");
// 使用log4j2,需要log4j-api.jar、log4j-core.jar
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger_log4j2 = LogManager.getLogger(Test.class);
logger_log4j2.info("Hello World!");
// logback,需要logback-classic.jar、logback-core.jar
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
Logger logger_logback = new LoggerContext().getLogger(Test.class);
logger_logback.info("Hello World!");
// java.until.logging,簡稱jul
import java.util.logging.Logger;
Logger logger_jul = Logger.getLogger("java.Test");
如上方式是直接使用某種日誌框架,一但需要變更日誌框架,改動的點比較大,因此有沒有一種方法可以讓用戶在很少改動代碼的情況下靈活的變更日誌框架,答案是:有!
現如今,java的兩大log陣營:Common logging 陣營(jakarta common logiing,JCL) 和 slf4j陣營,JCL和slf4j屬於日誌接口,提供統一的日誌操作規範,大體使用方式如下:
下面我們就以slf4j爲例來講解下日誌門面的使用:
slf4j日誌門面
slf4j官網: http://www.slf4j.org/manual.ht
官方說明(官網是最好的學習資料),特別建議大家詳讀slf4j官方文檔
Declaring project dependencies for logging
Given Maven’s transitive dependency rules, for “regular” projects (not libraries or frameworks) declaring logging dependencies can be accomplished with a single dependency declaration.
LOGBACK-CLASSIC If you wish to use logback-classic as the underlying logging framework, all you need to do is to declare “ch.qos.logback:logback-classic” as a dependency in your pom.xml file as shown below. In addition to logback-classic-1.2.3.jar, this will pull slf4j-api-1.7.28.jar as well as logback-core-1.2.3.jar into your project. Note that explicitly declaring a dependency on logback-core-1.2.3 or slf4j-api-1.7.28.jar is not wrong and may be necessary to impose the correct version of said artifacts by virtue of Maven’s “nearest definition” dependency mediation rule.
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
解釋:使用logback作爲日誌框架,只要maven中加入logback-classic依賴,就會把slf4j-api-1.7.28.jar 和 logback-core-1.2.3.jar同時拉取下來,因爲logback-classic的pom裏面依賴了2個jar
LOG4J If you wish to use log4j as the underlying logging framework, all you need to do is to declare “org.slf4j:slf4j-log4j12” as a dependency in your pom.xml file as shown below. In addition to slf4j-log4j12-1.7.28.jar, this will pull slf4j-api-1.7.28.jar as well as log4j-1.2.17.jar into your project. Note that explicitly declaring a dependency on log4j-1.2.17.jar or slf4j-api-1.7.28.jar is not wrong and may be necessary to impose the correct version of said artifacts by virtue of Maven’s “nearest definition” dependency mediation rule.
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.28</version>
</dependency>
解釋:使用log4j作爲日誌框架,只要maven中加入slf4j-log4j12依賴,同時會拉取slf4j-api-1.7.28.jar 和 log4j-1.2.17.jar兩個jar包,當然在maven中顯示的增加這兩個jar的依賴也是沒有問題的
JAVA.UTIL.LOGGING If you wish to use java.util.logging as the underlying logging framework, all you need to do is to declare “org.slf4j:slf4j-jdk14” as a dependency in your pom.xml file as shown below. In addition to slf4j-jdk14-1.7.28.jar, this will pull slf4j-api-1.7.28.jar into your project. Note that explicitly declaring a dependency on slf4j-api-1.7.28.jar is not wrong and may be necessary to impose the correct version of said artifact by virtue of Maven’s “nearest definition” dependency mediation rule.
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.28</version>
</dependency>
解釋:使用JUL作爲日誌框架,只要maven中加入slf4j-jdk14依賴,同時會拉取*slf4j-api-1.7.28.jar包,當然在maven中顯示的增加這兩個jar的依賴也是沒有問題的
對於slf4j這種日誌門面的使用,我們可以在代碼中配置如下的通用模式,然後具體下面使用何種日誌框架取決於用戶的日誌跨框架依賴
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("Hello World!")
通過上面官方的說明,我們可以知道只要在業務代碼裏面接入一套統一的日誌模板,然後引入日誌框架以及框架和slf4j的橋接依賴,這樣就能靈活的變更日誌框架,那麼什麼是橋接包?先讓來讓我們看看slf4j從LoggerFactory.getLogger()開始,到底幹了什麼。
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
} catch (NoClassDefFoundError ncde) {
}
通過查看源碼,我們可以看出,slf4j的原理就是就是讓ClassLoader從classpath(依賴的jar)中找到**「StaticLoggerBinder」**這個類,然後利用他來返回log4j、logback中的Logger,然後打印日誌。所謂的橋接包,就是實現StaticLoggerBinder類,用來連接slf4j和日誌框架
附
門面日誌和設計模式中的外觀模式如出一轍,本身不提供服務,爲子系統提供統一的入口,封裝子系統的複雜性,便於客戶端調用。slf4j就像是菜鳥驛站,本身沒有快遞服務,但是提供順豐、中通等快遞服務,至於你想用順豐還是用中通,完全取決於你的想法。