做爲一個Java程序員,確定對於日誌記錄不會陌生,不管項目大小,日誌記錄都是必須的;由於好的日誌能夠很容易的幫助咱們定位一些生產問題。html
我懷念的是
無話不說System.out.println("這裏是重要的日誌");java
我懷念的是
一塊兒做夢System.err.println("這裏是錯誤的日誌");程序員
對於平常開發來講,其實System.out.println挺好用的,可是爲何在實際的開發應用中不使用這個來輸出日誌呢?api
System.out.println()除了使用方便一點以外,其餘感受真的沒有什麼多大的用處。方便在哪兒呢?在Eclipse中你只須要輸入syso【IDEA中輸出sout便可】,而後按下代碼提示鍵,這個方法就會自動出來了,相信這也是不少Java新手對它鍾情的緣由,由於我一開始也是這麼幹的,直到...【此處省略淚水】。那缺點又在哪兒了呢?這個就太多了,好比日誌打印不可控制、打印時間沒法肯定、不能添加過濾器、日誌沒有級別區分……bash
記得我最開始接觸的是log4j,log4j做爲Apache的一個開放源代碼的項目,經過使用log4j,咱們能夠控制日誌信息輸送的目的地是控制檯、文件等咱們指望它輸出到的地方;咱們也能夠控制每一條日誌的輸出格式;經過定義每一條日誌信息的級別,咱們可以更加細緻地控制日誌的生成過程。重要的是,這些能夠經過一個配置文件來靈活地進行配置,而不須要修改應用的代碼。併發
確實,log4j做爲最早比較流行的日誌框架,給咱們在應用開發和維護帶來了很大的便捷,那麼爲何這樣優秀的框架最後仍是慢慢的走下「神壇」呢?而逐漸被logback代替呢?下面是摘自網上一位大佬對於這個問題的解釋:app
不管從設計上仍是實現上,Logback相對log4j而言有了相對多的改進。不過儘管難以一一細數,這裏仍是列舉部分理由爲何選擇logback而不是log4j。牢記logback與log4j在概念上面是很類似的,它們都是有同一羣開發者創建。框架
更快的執行速度eclipse
基於咱們先前在log4j上的工做,logback 重寫了內部的實現,在某些特定的場景上面,甚至能夠比以前的速度快上10倍。在保證logback的組件更加快速的同時,同時所需的內存更加少。ide
充分的測試
Logback 歷經了幾年,數不清小時數的測試。儘管log4j也是測試過的,可是Logback的測試更加充分,跟log4j不在同一個級別。咱們認爲,這正是人們選擇Logback而不是log4j的最重要的緣由。人們都但願即便在惡劣的條件下,你的日記框架依然穩定而可靠。
slf4j:The Simple Logging Facade for Java 即java的簡單日誌門面
簡答的講就是slf4j是一系列的日誌接口,slf4j做爲一個日誌的抽象行爲存在,可是並無提供真正的實現。
slf4j爲各類日誌框架提供了一個統一的界面,使用戶能夠用統一的接口記錄日誌,可是動態地決定真正的實現框架。logback,log4j,common-logging等框架都實現了這些接口。
想了好久都不知道從哪裏開頭寫比較合適,slf4j包中總共29個類【1.7.21版本】,不可能一一列舉。因此就從咱們熟知的這個語句來講。
private static final Logger logger =
LoggerFactory.getLogger(DemoTest.class);
複製代碼
上面這段代碼其實就是咱們平時在代碼裏面申明一個日誌對象的經常使用方式。
先來看下Logger接口提供的方法;
package org.slf4j;
public interface Logger {
//根Logger
String ROOT_LOGGER_NAME = "ROOT";
String getName();
//判斷記錄器Trace跟蹤是否激活;Trace跟蹤激活後通常會打印比較詳細的信息。
boolean isTraceEnabled();
//trace級別
void trace(String var1);
void trace(String var1, Object var2);
void trace(String var1, Object var2, Object var3);
void trace(String var1, Object... var2);
void trace(String var1, Throwable var2);
boolean isTraceEnabled(Marker var1);
void trace(Marker var1, String var2);
void trace(Marker var1, String var2, Object var3);
void trace(Marker var1, String var2, Object var3, Object var4);
void trace(Marker var1, String var2, Object... var3);
void trace(Marker var1, String var2, Throwable var3);
//進行預先判斷,提高系統性能
boolean isDebugEnabled();
void debug(String var1);
void debug(String var1, Object var2);
void debug(String var1, Object var2, Object var3);
void debug(String var1, Object... var2);
void debug(String var1, Throwable var2);
boolean isDebugEnabled(Marker var1);
void debug(Marker var1, String var2);
void debug(Marker var1, String var2, Object var3);
void debug(Marker var1, String var2, Object var3, Object var4);
void debug(Marker var1, String var2, Object... var3);
void debug(Marker var1, String var2, Throwable var3);
//info級別
boolean isInfoEnabled();
void info(String var1);
void info(String var1, Object var2);
void info(String var1, Object var2, Object var3);
void info(String var1, Object... var2);
void info(String var1, Throwable var2);
boolean isInfoEnabled(Marker var1);
void info(Marker var1, String var2);
void info(Marker var1, String var2, Object var3);
void info(Marker var1, String var2, Object var3, Object var4);
void info(Marker var1, String var2, Object... var3);
void info(Marker var1, String var2, Throwable var3);
//warn級別
boolean isWarnEnabled();
void warn(String var1);
void warn(String var1, Object var2);
void warn(String var1, Object... var2);
void warn(String var1, Object var2, Object var3);
void warn(String var1, Throwable var2);
boolean isWarnEnabled(Marker var1);
void warn(Marker var1, String var2);
void warn(Marker var1, String var2, Object var3);
void warn(Marker var1, String var2, Object var3, Object var4);
void warn(Marker var1, String var2, Object... var3);
void warn(Marker var1, String var2, Throwable var3);
//error級別
boolean isErrorEnabled();
void error(String var1);
void error(String var1, Object var2);
void error(String var1, Object var2, Object var3);
void error(String var1, Object... var2);
void error(String var1, Throwable var2);
boolean isErrorEnabled(Marker var1);
void error(Marker var1, String var2);
void error(Marker var1, String var2, Object var3);
void error(Marker var1, String var2, Object var3, Object var4);
void error(Marker var1, String var2, Object... var3);
void error(Marker var1, String var2, Throwable var3);
}
複製代碼
以isDebugEnabled來講明下這個isXXXXEnabled方法的做用。
logger.debug("the debug msg is " +doMethod());
假設咱們的日誌級別設置爲info,那這句話不會輸出日誌,但這個方法仍是會調用(預判斷做用)。要調用這個方法,必須提供參數。doMethod()方法返回的結果就是參數的一部分。doMethod()要執行n秒鐘,n秒鐘後,進入到debug()方法裏;
可是日誌級別爲info。結果是日誌雖然沒有輸出,卻花費了n秒鐘來構造參數。很顯然這裏得不償失的。儘管實際應用中幾乎不可能有這種花n秒鐘來構造這樣一個參數的狀況,但若是併發數大的話,這樣寫仍是會影響系統的性能的。這個時候,就應該寫成:
if(logger.isDebugEnabled()){
logger.debug("the debug msg is " + doMethod());
}
複製代碼
接下來講LoggerFactory這個類;先從getLogger這個方法爲入口來看下:
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
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 http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
}
}
return logger;
}
複製代碼
getLogger方法提供了兩種重載方式,一種是傳入一個name,用於標註當前日誌的名字。另一個是提供一個Class對象,其實裏面也是經過clazz.getName()來做爲日誌的名稱。
從上面的代碼中能夠比較明顯的看到ILoggerFactory這個接口。
package org.slf4j;
public interface ILoggerFactory {
Logger getLogger(String var1);
}
複製代碼
ILoggerFactory這個接口實際上就是爲不一樣接入的日誌實現提供了統一的頂層類型;每一個日誌框架都須要實現ILoggerFactory接口,來講明本身是怎麼提供Logger的。像log4j、logback可以提供父子層級關係的Logger,就是在ILoggerFactory的實現類裏實現的。同時,它們也須要實現Logger接口,以完成記錄日誌。
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle 複製代碼
上面提到過,對於不一樣的日誌框架的實現都實現了ILoggerFactory接口。
@Override
public final Logger getLogger(final String name) {
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}
// if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
int i = 0;
Logger logger = root;
// check if the desired logger exists, if it does, return it
// without further ado.
Logger childLogger = (Logger) loggerCache.get(name);
// if we have the child, then let us return it without wasting time
if (childLogger != null) {
return childLogger;
}
// if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h + 1;
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}
複製代碼
關於logback的源碼能夠參考這個系列的文章:logback源碼系列文章
我本身在日誌這塊遇到的最多的坑就是日誌jar的依賴衝突;由於項目用到的不少框架或者三方依賴中可能本身都集成了日誌框架,有的時候咱們本身的項目中也會有本身的一套日誌方案,因此就很大程度上致使了這種依賴衝突的發生。幸運的是,項目中關於日誌包的依賴衝突解決也是很容易排除的【idea和eclipse都很方便排查】
另一個就是使用slf4j以後,同時引入了不一樣的框架實現;好比同時引入了log4j和logback。
從上面getLogger的方法中能夠看到,在獲取Logger的時候都須要去先取得ILoggerFactory:
ILoggerFactory iLoggerFactory = getILoggerFactory();
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == 0) {
Class var0 = LoggerFactory.class;
synchronized(LoggerFactory.class) {
if (INITIALIZATION_STATE == 0) {
INITIALIZATION_STATE = 1;
//初始化
performInitialization();
}
}
}
switch(INITIALIZATION_STATE) {
case 1:
return SUBST_FACTORY;
case 2:
throw new IllegalStateException("org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
case 3:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case 4:
return NOP_FALLBACK_FACTORY;
default:
throw new IllegalStateException("Unreachable code");
}
}
複製代碼
在getILoggerFactory中會經過performInitialization方法來作初始化判斷;而在performInitialization中又調用了bind方法,bind方法中的異常或者錯誤信息打印不少狀況下咱們都會遇到:
private static final void bind() {
String msg;
try {
Set<URL> staticLoggerBinderPathSet = null;
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = 3;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError var2) {
msg = var2.getMessage();
if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
failedBinding(var2);
throw var2;
}
INITIALIZATION_STATE = 4;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
} catch (NoSuchMethodError var3) {
msg = var3.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = 2;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw var3;
} catch (Exception var4) {
failedBinding(var4);
throw new IllegalStateException("Unexpected initialization failure", var4);
}
}
複製代碼
reportMultipleBindingAmbiguity方法用來提示多重綁定,並經過日誌告訴你具體是有那幾個綁定了。
Class path contains multiple SLF4J bindings.
reportActualBinding這個是綁定成功的時候,會經過日誌顯示具體綁定成功的是哪個日誌框架
Actual binding is of type [XXXX]
從下面這個異常提及:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for
further details.
複製代碼
這個問題是由於加載類文件org.slf4j.impl.StaticLoggerBinder時失敗;爲何呢?下面是官網對於這個緣由的解釋:
This error is reported when the org.slf4j.impl.StaticLoggerBinder class could not be loaded into memory. This happens when no appropriate SLF4J binding could be found on the class path. Placing one (and only one) of slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar on the class path should solve the problem.
這個錯誤發生的緣由是StaticLoggerBinder類不能被加載到內存中。發生這種狀況時,沒法找到合適的SLF4J綁定類路徑。slf4j-nop放置一個(且只有一個)。slf4j-simple jar。slf4j-log4j12 jar。slf4j-jdk14 jar。jar或logback-classic。jar的類路徑應該解決這個問題。
也就是說沒有找到具體的日誌框架來綁定實現。因此咱們須要引入一個具體的日誌實現jar就能夠了。
網上看到的一片關於slf4j的文章,感受挺好的,分享給你們:slf4j源碼剖析
新的環境還在適應中,博客的更新頻次也直線降低,慢慢學,慢慢寫吧。也但願小夥伴們多多見諒!