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。其他示例

最新文章

  1. C++学习笔记30:模板与型式参数化
  2. Javascript事件模型系列(二)事件的捕获-冒泡机制及事件委托机制
  3. 【WP 8.1开发】上下文菜单
  4. [转]Unity: make your lists functional with ReorderableList
  5. servlet的配置和上下文
  6. POJ 1986 Distance Queries (最近公共祖先,tarjan)
  7. Deep Learning and the Triumph of Empiricism
  8. 【HDOJ】2430 Beans
  9. setcookie 设置无效
  10. thinkphp5.1 使用success();和error();要注意的点
  11. C#设计模式(11)——外观模式(Facade Pattern)(转)
  12. Badge
  13. JavaScript中的短路
  14. android下的样式
  15. loj SDOI2017数字表格
  16. Intersecting Lines
  17. java aop的理解
  18. idea部署tomcat-404错误
  19. ArcGIS 后台服务器抛出异常
  20. Matlab查看数值不用科学计数法显示

热门文章

  1. JUC(8)Stream流式计算
  2. Springboot JSON 转换:Jackson篇
  3. JWT基础概念详解
  4. LcdTools如何导出内置画面为bmp图片
  5. Blog2:nchu-software-oop-2022-4+5+期中
  6. MongoDB导入导出备份数据
  7. C#和Halcon交互实现图片的放大和缩小
  8. Jenkinsfile 同时检出多个 Git 仓库
  9. Go语言核心36讲10
  10. 远程桌面报错credssp