今天服務器上報錯,想先去看一下日誌進行排查,結果發現日誌好久都沒有輸出過了。從上午排查到下午,剛剛解決,所以記錄一下,但如今也只是知其然,並不知其因此然,因此若是你們有什麼想法請在下方評論。 html
先說一下環境,服務器是linux,項目是運行在tomcat下的Spring項目,日誌用的是log4j。java
首先,從10月13號開始便沒有新的日誌文件了。假設日誌名爲log.txt(若是你設置了DailyRollingFileAppender,那麼你當天的日誌文件就是log.txt),先備份該文件到其餘目錄下,而後刪除該文件,從新啓動tomcat。這是爲了確認你的log4j配置是否有問題,由於這是最容易出錯的地方。很遺憾,我不是這裏出的問題,由於項目重啓後,日誌文件又從新生成了,但很奇怪的是,日誌文件是空的,其大小爲0.linux
感受本身碰上了很神奇的問題,所以我在本身的本地進行調試,啓動項目後發現,正常的項目啓動日誌是有的:android
15:13:48:0253 INFO [RMI TCP Connection(3)-127.0.0.1] -Root WebApplicationContext: initialization completed in 18479 ms複製代碼
但我本身的一些日誌輸出是不顯示的,好比:web
private static final Logger log = LoggerFactory.getLogger(MyDomain.class);
log.info("show info log");複製代碼
show info log
這句話就不打印,如今證實,個人日誌配置沒有問題,服務器也找到了個人日誌文件,但應該是我本身的Logger是不對應正確的日誌輸出的,由於個人console(控制檯)有顯示。spring
接下來,我就是開始看源碼了。先是LoggerFactory.getLogger(Class
clazz)
方法:api
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;
}複製代碼
好吧,沒什麼用,看不出個人logger
變成了,繼續看getLogger(String name)
方法:tomcat
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}複製代碼
這時我在return iLoggerFactory.getLogger(name);
這行打了斷點,我看到了這樣的東西:服務器
爲何個人iLoggerFactory是用的logback中的實現?其實也是怪我本身大意,我其實依賴了一個基於Spring Boot的項目(雖然我只是用了裏面的一些domain類,但由於設計不當,尚未把這些domain類單獨提成一個_項目),而Spring Boot中通常默認就依賴的logback。經過gradle查看項目的依賴樹,也證明了個人這一猜測(./gradlew 子項目名稱:dependencies):dom
| +--- org.springframework.boot:spring-boot-starter-web:2.0.2.RELEASE
| | +--- org.springframework.boot:spring-boot-starter:2.0.2.RELEASE
| | | +--- org.springframework.boot:spring-boot:2.0.2.RELEASE
| | | | +--- org.springframework:spring-core:5.0.6.RELEASE (*)
| | | | \--- org.springframework:spring-context:5.0.6.RELEASE (*)
| | | +--- org.springframework.boot:spring-boot-autoconfigure:2.0.2.RELEASE
| | | | \--- org.springframework.boot:spring-boot:2.0.2.RELEASE (*)
| | | +--- org.springframework.boot:spring-boot-starter-logging:2.0.2.RELEASE
| | | | +--- ch.qos.logback:logback-classic:1.2.3
| | | | | +--- ch.qos.logback:logback-core:1.2.3
| | | | | \--- org.slf4j:slf4j-api:1.7.25複製代碼
接下來就好辦了,你排除掉ch.qos.logback
的依賴就能夠了,在你的build.gradle
中增長:
configurations {
compile.exclude group: 'ch.qos.logback'
}複製代碼
這個時候你再從新調試一下看看:
完美,如今是log4j中的實現,獲得了我想要的操做。固然了,既然我知道以前項目中的slf4j是logback實現了,那麼我天然也能夠換成logback的配置,但這就須要我將項目換成用Spring Boot啓動,這個改動有點大,若是之後有必要的話,我再將這個exclude刪除,換成Spring Boot的形式。
此次Spring Boot幫咱們默認啓用的是logback,那麼有沒有什麼簡單方法能夠知道呢?若是你的項目出現瞭如下的日誌輸出,說明你的項目當前有不止一個SLF4J的實現組件:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/project.war/WEB-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/project.war/WEB-INF/lib/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]複製代碼
由於在org.slf4j.LoggerFactory
的bind
方法中有關於這方面的輸出:
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()) {
// 查找你的當前項目有幾個slf4j的實現
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
// 若是多餘一個就打印
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
// 這個是具體選了哪個實現(重點關注)
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
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 nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}複製代碼
特別要注意的是StaticLoggerBinder.getSingleton();
這行代碼,StaticLoggerBinder
在logback-classic和slf4j-log4j12這兩個jar包各有一個,所以,Spring boot是自動選擇logback-classic(雖然我在本地運行的時候仍是默認進入的slf4j-log4j12,可是會提醒我Source code does not match the bytecode
,所以我判斷依舊進的是logback-classic),因此只要把logback給exclude掉,就解決了這個問題。
如今看問題,更加關注源代碼,由於這可讓咱們更加快速定位問題,而且也能據此大體猜出其解決方案。但願你們能一塊兒看看源代碼,若是你有什麼發現,能夠在下方留言,我將和你一塊兒討論。
有興趣的話能夠關注個人公衆號或者頭條號,說不定會有意外的驚喜。