从log4j切换到logback后项目无法启动的问题及解决方法

 更新时间:2023年01月17日 14:45:03   作者:幻影  
这篇文章主要介绍了从log4j切换到logback后项目无法启动的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

1、背景

有个旧项目之前使用的是log4j2来打印日志的,因为某些原因,同事想换成logback。

换成logback改动也很简单,大致就一下2步:

1.删除log4j2.xml配置,新增logback.xml配置。剔除掉log4j相关的jar

2.引入slf4j (其实之前使用log4j2的时候就已经引入了,只是有些地方写法不规范),

代码【import org.apache.log4j.Logger】改成【import org.slf4j.Logger】(以及其他类似修改)

2、现象

全部改了之后,按道理说,应该就可以正常打印了。

但是启动发现,日志报错:

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) ★小技巧: 报错的日志比较多,甚至有些报错看起来有点莫名其妙。 不要慌,先找到最早的案发现场日志,或者搜一下关键字。 因为我们改的是日志,所以可以在报错信息中搜一下log/log4j 等关键字

3、问题排查

看到这里第一反应应该是有代码没改全了。全局搜一下log4j关键字,果然发现还有一处:

<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个子类,分别对应不同的日志打印实现方式

  • com.alibaba.druid.filter.logging.Slf4jLogFilter
  • com.alibaba.druid.filter.logging.Log4jFilter
  • com.alibaba.druid.filter.logging.CommonsLogFilter

那我们怎么知道是配置成slf4j、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

无意中发现commonlogging这一行写重复了,哈哈,这个不是我拷贝重的,源码druid-1.0.18就是搞重复了!

改完之后,再启动项目,发现问题依旧啊!

4、问题分析

估计还是有别的地方写明了需要使用log4j,为了验证猜想,我设置了NoClassDefFoundError异常断点,再次debug启动。

进入断点的时候,就发现还有个 HttpSessionManager 代码中写死了【import org.apache.log4j.Logger;】

这个是个第三方的jar,代码是改不了的。就只能另寻他法了。

其实像这种情况,代码写死了使用log4j,想统一成slf4j,slf4j已经提供了解决方法。那就是引入log4j-over-slf4j。

使用log4j-over-slf4j取代log4j,这样log4j接口输出的日志就会通过log4j-over-slf4j路由到SLF4J上,这样即使系统(包含使用的第三方jar库,比如dubbo)都可以将日志最终路由到SLF4J上,进而集中输出

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.1</version>
</dependency>

引入log4j-over-slf4j之后,再启动,就ok了~

5、问题延伸

再回过头看一下,log4j-over-slf4j到底给我们做了什么?

1.定义了【org.apache.log4j.Logger】对象,确保使用了log4j的老项目代码不至于编译不通过

package org.apache.log4j;
import org.slf4j.Marker;

public class Logger extends Category {
    ...
}

2.将【org.apache.log4j.Logger】的打印动作偷偷转移到slf4j上。

Logger继承自Category,并且实现了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);
            }
        }
    }            
} 

其实,类似的还有【jcl-over-slf4j】也是起到相同的作用。

例如:把Commons logging,log4j和java.util.logging桥接到SLF4J,底层使用logback的case。其他示例

到此这篇关于从log4j切换到logback后项目无法启动的问题及解决方法的文章就介绍到这了,更多相关log4j切换到logback后项目无法启动内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Netty学习之理解selector原理示例

    Netty学习之理解selector原理示例

    这篇文章主要为大家介绍了Netty学习之理解selector原理示例使用分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
    2023-07-07
  • MyBatis Mapper映射器的具体用法

    MyBatis Mapper映射器的具体用法

    映射器是MyBatis中最重要的文件,映射器由Java接口和XML文件共同组成,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • 使用SpringBoot实现Redis多数据库缓存

    使用SpringBoot实现Redis多数据库缓存

    在我的系统中,为了优化用户行为数据的存储与访问效率,我引入了Redis缓存,并将数据分布在不同的Redis数据库中,通过这种方式,可以减少单一数据库的负载,提高系统的整体性能,所以本文给大家介绍了使用SpringBoot实现Redis多数据库缓存,需要的朋友可以参考下
    2024-06-06
  • java项目导出为.exe执行文件的方法步骤

    java项目导出为.exe执行文件的方法步骤

    最近做了个项目,想要转换成可执行文件,那么java项目如何导出为.exe执行文件,本文就介绍一下,主要使用jar2exe软件,感兴趣的可以了解一下
    2021-05-05
  • java中注解机制及其原理的详解

    java中注解机制及其原理的详解

    这篇文章主要介绍了java中注解机制及其原理的详解的相关资料,希望通过本文能帮助到大家,让大家理解掌握这部分内容,需要的朋友可以参考下
    2017-10-10
  • spring+springmvc整合mabytis时mapper注入失败问题解决方法

    spring+springmvc整合mabytis时mapper注入失败问题解决方法

    这篇文章主要介绍了spring+springmvc整合mabytis时mapper注入失败问题解决方法 ,需要的朋友可以参考下
    2017-08-08
  • springboot结合maven配置不同环境的profile方式

    springboot结合maven配置不同环境的profile方式

    这篇文章主要介绍了springboot结合maven配置不同环境的profile方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • 用Maven插件生成Mybatis代码的实现方法

    用Maven插件生成Mybatis代码的实现方法

    本文主要介绍 Maven插件生成Mybatis代码,现在做开发的朋友有好多用Maven 来管理代码,这里给大家举个例子,有需要的同学可以看下
    2016-07-07
  • Mybatis实现增删改查及分页查询的方法

    Mybatis实现增删改查及分页查询的方法

    MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持 久层框架,通过本文给大家介绍Mybatis实现增删改查及分页查询的方法,感兴趣的朋友一起学习吧
    2016-01-01
  • JAVA中JSONObject对象和Map对象之间的相互转换

    JAVA中JSONObject对象和Map对象之间的相互转换

    这篇文章主要介绍了JAVA中JSONObject对象和Map对象之间的相互转换,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01

最新评论