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 {
...
}
  1. 将【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后项目无法启动的更多相关文章

  1. mac下 安装tomcat 后项目无法启动以及 错误 找不到或无法加载主类

    按照网上的步骤,在mac上安装tomcat后,写个简单的测试类报错:错误 找不到或无法加载主类 Class JavaLaunchHelper is implemented in both /Libra ...

  2. 老板下了死命令,要把日志系统切换到Logback

    Log4j 介绍过了,SLF4J 也介绍过了,那接下来,你懂的,Logback 就要隆重地登场了,毕竟它哥仨有一个爹,那就是巨佬 Ceki Gulcu. 就在昨天,老板听我说完 Logback 有多牛 ...

  3. Log4j、Log4j 2、Logback、SFL4J、JUL、JCL的比较

    Log4j.Log4j 2.Logback.SFL4J.JUL.JCL的比较 之前就知道有好几种日志框架,但是一直都是听别人讲,在什么时候该用何种logger,哪种logger比较好……一直对Log4 ...

  4. 从Log4j迁移到LogBack的理由

    英文原文:Reasons to prefer logback over log4j 无论从设计上还是实现上,Logback相对log4j而言有了相对多的改进.不过尽管难以一一细数,这里还是列举部分理由 ...

  5. log4j升级到logback

    虽然现在log4j已经基本上不更新很久了,但实际上升级log4j到logback最大的难度并不在于本身的替换,而是现有大量的三方jar依然使用log4j,以至于无法100%的exclude掉,所以很有 ...

  6. unity 代码有调整,重新导出 iOS 最烦的就是 覆盖导出后项目不能打开

    unity  代码有调整,重新导出 iOS 最烦的就是 覆盖导出后项目不能打开,原因是 editor 里面的脚本,破坏了 Unity-iPhone.xcodeproj 里面的结构,具体是什么原因,也不 ...

  7. 微擎修改 icon.jpg 后项目主页未变

    微擎修改 icon.jpg 后项目主页Logo未变 产生原因: 设置了自定义图标,但系统未找到该图标,就选择使用默认的起始图标 解决办法: 在项目根目录位置上传一个图标名为 icon-custom.j ...

  8. Spring Boot项目指定启动后执行的操作

    Spring Boot项目指定启动后执行的操作: (1)实现CommandLineRunner 接口 (2)重写run方法 (3)声明执行顺序@Order(1),数值越小,优先级越高 (4)如果需要注 ...

  9. 【项目启动】 tomcat启动,项目无法启动,无法正常访问/项目可以启动,报错:java.lang.ClassNotFoundException: ContextLoaderListener

    使用maven搭建项目(这个错误和是不是使用maven搭建项目没有关系),然后部署到tomcat中运行. 出现问题1: tomcat跑起来了,但是启动时间很短,没有报错,项目不能正常访问 项目启动时间 ...

  10. zw字王《中华大字库》2018版升级项目正式启动

    zw字王<中华大字库>2018版升级项目正式启动 https://www.cnblogs.com/ziwang/p/9500537.html 这次升级是和字库协会一起合作,首批推出的字体, ...

随机推荐

  1. 这次彻底读透 Redis

    1. Redis 管道 我们通常使用 Redis 的方式是,发送命令,命令排队,Redis 执行,然后返回结果,这个过程称为Round trip time(简称RTT, 往返时间).但是如果有多条命令 ...

  2. XAF新手入门 - 类型子系统(Types Info Subsystem)

    类型子系统概述 类型子系统是XAF的核心概念,但我们平时却很少关注它,它集中存储了模块中的类型,它是生成应用程序模型(Application Model)的基础,它与XAF中其它的概念都有所关联,了解 ...

  3. KNN算法之集美大学

     在本篇文章中,我即将以在集美大学收集到的一些数据集为基础,使用KNN算法进行一系列的操作 一.KNN算法 首先,什么是KNN算法呢,这得用到老祖宗说的一句话"近朱者赤近墨者黑", ...

  4. CSS 动画一站式指南

    CSS 动画一站式指南 目录 CSS 动画一站式指南 1. CSS 动画 1.1 变换 1.1.1 变换属性介绍 1.1.2 变换动画实践 1.2 过渡 1.2.1 过渡属性介绍 1.2.2 过渡动画 ...

  5. CSS基础知识筑基

    01.CSS 简介 CSS 指层叠样式表 (Cascading Style Sheets),对HTML网页内容进行统一外观样式设计和管理,给网页进行各种装饰,让她变得美观,是HTML的化妆师.(Cas ...

  6. MySQL该使用哪种CPU架构服务器?

    1. 摘要 近期,阿里云推出基于 ARM 架构的 RDS MySQL 和 RDS PostgreSQL 实例,现处于邀测阶段,阿里云宣传 ARM 架构的亮点是:在价格下降13%的基础上,平均性能 AR ...

  7. Spring Cloud Circuit Breaker 使用示例

    Spring Cloud Circuit Breaker 使用示例 作者: Grey 原文地址: 博客园:Spring Cloud Circuit Breaker 使用示例 CSDN:Spring C ...

  8. 金山云:基于 JuiceFS 的 Elasticsearch 温冷热数据管理实践

    01 Elasticsearch 广泛使用带来的成本问题 Elasticsearch(下文简称"ES")是一个分布式的搜索引擎,还可作为分布式数据库来使用,常用于日志处理.分析和搜 ...

  9. 网络yum源下载

    思路一: 按照本地网罗源,然后使用reposync直接将源同步下载到本地 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/re ...

  10. windows每日定时计划任务

    若要计划安全脚本 ,Sec.vbs,每天在下午 5:00 到上午 7:59 之间每隔 100 分钟在本地计算机上运行一次,请键入: schtasks /create /tn Security Scri ...