log4j-over-slf4j的jar包适配原理解析

如果想把项目中的日志实现统一成slf4j的话,则需要把第三方一些依赖包中的日志包去掉,例如Spring中的jcl,或者其他的像早期的log4j,如果直接排除,则程序肯定会运行报错,此时需要引入适配包,这个适配包就是一个狸猫换太子包,这个包有着和jcl和log4j一摸一样的包名和类名,所以在程序动态运行过程中,只需要关心classpath下有没有这个类即可,并不需要知道这个类在哪个jar包,正因如此,才能实现狸猫换太子的功能。下面写一个测试程序:

  • 项目目录结构如下
    在这里插入图片描述

最开始是先有的jcl这个包,然后这个包中依赖log4j这个包,在这个包中编写了一个类,里面使用了log4j的Logger输出日志信息

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

public class Runner {

    private static Logger logger = LogManager.getLogger(Runner.class);

    public static void run(){
        logger.info("application is running...");
    }

}

log4j的配置文件如下

### set log levels ###
log4j.rootLogger=DEBUG

### direct log messages to stdout ###
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Target=System.out
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-2p %m%n

log4j.logger.com.sp=DEBUG,A1
  • 创建app项目,app项目依赖jcl这个包
    <dependency>
            <groupId>org.example</groupId>
            <artifactId>jcl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

在app包中编写一个测试类,调用Runner的run方法

import com.sp.Runner;

public class Main {

    public static void main(String[] args) {
        Runner.run();
    }

}

控制台输出如下

在这里插入图片描述
app包就好比我们自己写的应用程序,jcl就比如依赖的第三方框架,如spring,而jcl依赖的log4j就是第三方框架依赖的日志包。但是现在的这个依赖包依赖的是log4j,如何统一成slf4j?

  1. 首先排除这个依赖包中的日志包
    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>jcl</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

如果这样直接排除,则项目无法启动,因为第三方包中依赖的class文件找不到,此时抛出了一个经典异常。如果遇到 NoClassDefFoundError 这个异常可考虑依赖的相关jar包是否都已引入。

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/LogManager
	at com.sp.Runner.<clinit>(Runner.java:8)
	at com.sci.app.Main.main(Main.java:9)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.LogManager
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 2 more
  1. 此时就需要引入log4j的适配包,并且这些适配包的类名和包名需要和log4j保持一致,调用的方法以及形参也要保持一致,这样在程序运行时才不会报错。
    在这里插入图片描述
public class LogManager {
    
    public static Logger getLogger(Class clazz){
        return new Logger();
    }
    
}

public class Logger {

    public void info(Object message){
        System.out.println("slf4j  INFO ===> " + message);
    }

}
  1. 此时在app中引入 log4j-over-slf4j
    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>jcl</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

此时重新运行,amazing !

在这里插入图片描述

适配包的作用就是编写一个些与原日志依赖同包名,同类名,同方法名的类,然后在这些类中使用slf4j 作为门面来获取logger,至于这里的logger是slf4j的哪个实现,那就要看项目中引入了那个实现类了,slf4j的实现有logback,log4j-impl(兼容log4j的),jul-impl(兼容jul),log4j2。

  • 下面来看一下log4j-over-slf4j真正的庐山真面目
  1. 首先包名类名都是一致的

在这里插入图片描述

  1. LogManager.getLogger(Class clazz);
public class LogManager {
    public LogManager() {
    }

    public static Logger getRootLogger() {
        return Log4jLoggerFactory.getLogger("ROOT");
    }

    public static Logger getLogger(String name) {
        return Log4jLoggerFactory.getLogger(name);
    }

    public static Logger getLogger(Class clazz) {
        return Log4jLoggerFactory.getLogger(clazz.getName());
    }

    public static Logger getLogger(String name, LoggerFactory loggerFactory) {
        return loggerFactory.makeNewLoggerInstance(name);
    }

    public static Enumeration getCurrentLoggers() {
        return (new Vector()).elements();
    }

    public static void shutdown() {
    }

    public static void resetConfiguration() {
    }
}

获取Logger调用到了 Log4jLoggerFactory.getLogger(clazz.getName());
通过这个方法,获取到了Logger,对于那些第三方框架来说,获取的还是org.apache.log4j下的logger,但是看这个Logger的内部有一个slf4j.Logger的成员变量,在调用log4j的相关方法时,其实时调用了成员变量的logger方法

    public static Logger getLogger(String name) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            Logger newInstance = new Logger(name);
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }
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;
        }

    }
   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);
            }
        }

    }

而这个logger具体时什么级别,能打印哪些信息,这些东西在 this.slf4jLogger = LoggerFactory.getLogger(name); 时就可以获取到,logger内部维护了能打印哪些级别的信息等。比如传入的classname是 org.springboot.autoconfig.DataSourceAutoConfig,但是org.springboot.autoconfig包设置的打印级别是warn,那么DataSourceAutoConfig维护的logger成员变量就只能打印warn及以上级别的日志信息。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
log4j-over-slf4j是一个用于将log4j日志框架转换为SLF4J日志框架的桥接器。它的作用是将log4j接口输出的日志通过log4j-over-slf4j路由到SLF4J上,从而实现日志的集中输出。\[1\]在java的日志框架结构中,slf4j和jcl作为日志的门面存在于最上层,而log4j、jul、logback等则是这些门面的不同实现。为了使log4j也能符合slf4j规范,就出现了slf4j-log4j12这个桥接器,它的作用是将log4j适配slf4j的接口实现。\[2\]而log4j-over-slf4j的作用则是将引入的jar包中的log4j替换为log4j-over-slf4j,从而统一项目中的日志框架,避免冗余和复杂性。具体来说,log4j-over-slf4j会将使用log4j打印日志的地方转换为slf4j打印日志,而slf4j实际调用的是logback来具体实现日志的打印。\[2\]这样就可以实现项目中日志框架的统一。 #### 引用[.reference_title] - *1* [log4j-over-slf4j工作原理详解](https://blog.csdn.net/john1337/article/details/76152906)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [slf4j logback log4j slf4j-log412 log4j-over-slf4j 关系详解](https://blog.csdn.net/asdasd3418/article/details/82840607)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [log4j-over-slf4jjar包适配原理解析](https://blog.csdn.net/qq_43750656/article/details/125714607)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值