從log4j切換到logback后項(xiàng)目無(wú)法啟動(dòng)的問(wèn)題及解決方法
1、背景
有個(gè)舊項(xiàng)目之前使用的是log4j2來(lái)打印日志的,因?yàn)槟承┰?,同事想換成logback。
換成logback改動(dòng)也很簡(jiǎn)單,大致就一下2步:
1.刪除log4j2.xml配置,新增logback.xml配置。剔除掉log4j相關(guān)的jar
2.引入slf4j (其實(shí)之前使用log4j2的時(shí)候就已經(jīng)引入了,只是有些地方寫(xiě)法不規(guī)范),
代碼【import org.apache.log4j.Logger】改成【import org.slf4j.Logger】(以及其他類(lèi)似修改)
2、現(xiàn)象
全部改了之后,按道理說(shuō),應(yīng)該就可以正常打印了。
但是啟動(dòng)發(fā)現(xiàn),日志報(bào)錯(cuò):
ERROR {org.springframework.web.context.ContextLoader:356} - Context initialization failed java.lang.NoClassDefFoundError: org/apache/log4j/Logger at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) at java.lang.Class.getDeclaredMethods(Class.java:1975) at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:612) at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:524) at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:510) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:241) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1069) ★小技巧: 報(bào)錯(cuò)的日志比較多,甚至有些報(bào)錯(cuò)看起來(lái)有點(diǎn)莫名其妙。 不要慌,先找到最早的案發(fā)現(xiàn)場(chǎng)日志,或者搜一下關(guān)鍵字。 因?yàn)槲覀兏牡氖侨罩荆钥梢栽趫?bào)錯(cuò)信息中搜一下log/log4j 等關(guān)鍵字
3、問(wèn)題排查
看到這里第一反應(yīng)應(yīng)該是有代碼沒(méi)改全了。全局搜一下log4j關(guān)鍵字,果然發(fā)現(xiàn)還有一處:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="config,stat,log4j,wall" /> <!-- 這里的log4j 需要改成 slf4j -->
...
</bean>DruidDatasource中定義了com.alibaba.druid.filter.logging.LogFilter,他有3個(gè)子類(lèi),分別對(duì)應(yīng)不同的日志打印實(shí)現(xiàn)方式
- com.alibaba.druid.filter.logging.Slf4jLogFilter
- com.alibaba.druid.filter.logging.Log4jFilter
- com.alibaba.druid.filter.logging.CommonsLogFilter
那我們?cè)趺粗朗桥渲贸蓅lf4j、Slf4j、還是Slf4jLogger呢?可以看這里druid源碼文件【META-INF/druid-filter.properties】
druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
無(wú)意中發(fā)現(xiàn)commonlogging這一行寫(xiě)重復(fù)了,哈哈,這個(gè)不是我拷貝重的,源碼druid-1.0.18就是搞重復(fù)了!
改完之后,再啟動(dòng)項(xiàng)目,發(fā)現(xiàn)問(wèn)題依舊?。?/p>
4、問(wèn)題分析
估計(jì)還是有別的地方寫(xiě)明了需要使用log4j,為了驗(yàn)證猜想,我設(shè)置了NoClassDefFoundError異常斷點(diǎn),再次debug啟動(dòng)。

進(jìn)入斷點(diǎn)的時(shí)候,就發(fā)現(xiàn)還有個(gè) HttpSessionManager 代碼中寫(xiě)死了【import org.apache.log4j.Logger;】
這個(gè)是個(gè)第三方的jar,代碼是改不了的。就只能另尋他法了。
其實(shí)像這種情況,代碼寫(xiě)死了使用log4j,想統(tǒng)一成slf4j,slf4j已經(jīng)提供了解決方法。那就是引入log4j-over-slf4j。
使用log4j-over-slf4j取代log4j,這樣log4j接口輸出的日志就會(huì)通過(guò)log4j-over-slf4j路由到SLF4J上,這樣即使系統(tǒng)(包含使用的第三方j(luò)ar庫(kù),比如dubbo)都可以將日志最終路由到SLF4J上,進(jìn)而集中輸出
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.1</version>
</dependency>
引入log4j-over-slf4j之后,再啟動(dòng),就ok了~
5、問(wèn)題延伸
再回過(guò)頭看一下,log4j-over-slf4j到底給我們做了什么?
1.定義了【org.apache.log4j.Logger】對(duì)象,確保使用了log4j的老項(xiàng)目代碼不至于編譯不通過(guò)
package org.apache.log4j;
import org.slf4j.Marker;
public class Logger extends Category {
...
}2.將【org.apache.log4j.Logger】的打印動(dòng)作偷偷轉(zhuǎn)移到slf4j上。
Logger繼承自Category,并且實(shí)現(xiàn)了info、warn、error等打印日志的方法
package org.apache.log4j;
import java.util.Enumeration;
import org.apache.log4j.helpers.NullEnumeration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.slf4j.spi.LocationAwareLogger;
public class Category {
private static final String CATEGORY_FQCN = Category.class.getName();
private String name;
protected Logger slf4jLogger;
private LocationAwareLogger locationAwareLogger;
private static Marker FATAL_MARKER = MarkerFactory.getMarker("FATAL");
Category(String name) {
this.name = name;
this.slf4jLogger = LoggerFactory.getLogger(name);
if (this.slf4jLogger instanceof LocationAwareLogger) {
this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
}
}
public Level getEffectiveLevel() {
if (this.slf4jLogger.isTraceEnabled()) {
return Level.TRACE;
} else if (this.slf4jLogger.isDebugEnabled()) {
return Level.DEBUG;
} else if (this.slf4jLogger.isInfoEnabled()) {
return Level.INFO;
} else {
return this.slf4jLogger.isWarnEnabled() ? Level.WARN : Level.ERROR;
}
}
public void info(Object message) {
this.differentiatedLog((Marker)null, CATEGORY_FQCN, 20, message, (Throwable)null);
}
void differentiatedLog(Marker marker, String fqcn, int level, Object message, Throwable t) {
String m = this.convertToString(message);
if (this.locationAwareLogger != null) {
this.locationAwareLogger.log(marker, fqcn, level, m, (Object[])null, t);
} else {
switch(level) {
case 0:
this.slf4jLogger.trace(marker, m);
break;
case 10:
this.slf4jLogger.debug(marker, m);
break;
case 20:
this.slf4jLogger.info(marker, m);
break;
case 30:
this.slf4jLogger.warn(marker, m);
break;
case 40:
this.slf4jLogger.error(marker, m);
}
}
}
}
其實(shí),類(lèi)似的還有【jcl-over-slf4j】也是起到相同的作用。
例如:把Commons logging,log4j和java.util.logging橋接到SLF4J,底層使用logback的case。其他示例

到此這篇關(guān)于從log4j切換到logback后項(xiàng)目無(wú)法啟動(dòng)的問(wèn)題及解決方法的文章就介紹到這了,更多相關(guān)log4j切換到logback后項(xiàng)目無(wú)法啟動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Cloud Toolkit在IDEA中極速創(chuàng)建dubbo工程
這篇文章主要介紹了使用Cloud Toolkit在IDEA中極速創(chuàng)建dubbo工程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Springboot啟動(dòng)同時(shí)創(chuàng)建數(shù)據(jù)庫(kù)和表實(shí)現(xiàn)方法
這篇文章主要介紹了Springboot啟動(dòng)同時(shí)創(chuàng)建數(shù)據(jù)庫(kù)和表,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01
Java中關(guān)于char類(lèi)型變量能夠輸出中文的問(wèn)題
這篇文章主要介紹了Java中關(guān)于char類(lèi)型變量能夠輸出中文的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
關(guān)于Prometheus + Spring Boot 應(yīng)用監(jiān)控的問(wèn)題
這篇文章主要介紹了關(guān)于Prometheus + Spring Boot 應(yīng)用監(jiān)控的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
java實(shí)現(xiàn)web實(shí)時(shí)消息推送的七種方案
這篇文章主要為大家介紹了java實(shí)現(xiàn)web實(shí)時(shí)消息推送的七種方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Spring通過(guò)配置文件管理Bean對(duì)象的方法
這篇文章主要介紹了Spring通過(guò)配置文件管理Bean對(duì)象的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
使用redis的increment()方法實(shí)現(xiàn)計(jì)數(shù)器功能案例
這篇文章主要介紹了使用redis的increment()方法實(shí)現(xiàn)計(jì)數(shù)器功能案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Java中&和&&以及|和||的區(qū)別、應(yīng)用場(chǎng)景和代碼示例
這篇文章主要介紹了Java中的邏輯運(yùn)算符&、&&、|和||的區(qū)別,包括它們?cè)诓紶柡驼麛?shù)類(lèi)型上的應(yīng)用,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03
關(guān)于feign調(diào)用的參數(shù)傳遞問(wèn)題(@RequestBody和@RequestParam)
這篇文章主要介紹了關(guān)于feign調(diào)用的參數(shù)傳遞問(wèn)題(@RequestBody和@RequestParam),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

