使用桥接器在不改变源码的情况下实现从log4j日志框架转变为slf4j+logback日志框架

一、搭建log4j日志框架环境并输出日志格式

1.1导入log4j依赖

  导入apache下的log4j的jar包

 <dependency>
     <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
 </dependency>

导入测试包,用于测试日志输出

 <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
 </dependency>

1.2 配置log4j.properties文件

在实际生产开发中,由于硬编码方式需要修改源码,不便于维护,故统一使用配置文件的方式来管理日志。这里由于只做测试使用,故在配置文件中仅配置控制台打印输出即可

#配置根节点:第一个参数为info,即默认打印输出级别,第二个参数为console,即默认控制台打印输出
log4j.rootLogger=info,console
#配置appender输出方式:控制台方式输出
log4j.appender.console=org.apache.log4j.ConsoleAppender
#配置输出格式:默认系统输出方式,此处不适用默认格式,使用下述的自定义格式
#log4j.appender.console.layout=org.apache.log4j.SimpleLayout
#配置输出格式:设置为自定义输出方式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#自定义输出格式
log4j.appender.console.layout.conversionPattern= [%-6p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

自定义格式中个占位符的含义

%m 输出代码中指定的日志信息
%p 输出优先级,及 DEBUG、INFO 等
%n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss.SSS}
%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号
%% 输出一个 "%" 字符
[%p]%r %c%t%d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式
[%10p]:[]中必须有10个字符,由空格来进行补齐,信息右对齐
[%-10p]:[]中必须有10个字符,由空格来进行补齐,信息左对齐,应用较广泛

1.3测试(未写出完整方法)

//注意:此处的Logger类是org.apache(log4j框架)下的Logger,而非Java原生的日志Logger对象(JUL框架)	
Logger logger= LogManager.getLogger(SLF4JTest.class);
//默认info级别打印输出
logger.info("info信息输出");

1.4控制台结果打印

[INFO  ]0 com.zm.slf4j.SLF4JTestmain2022-01-14 11:49:01:633 info信息输出

从结果可看出,此日志打印的格式是根据配置文件中自定义日志格式进行输出

二、搭建slf4j+logback日志框架环境并输出日志格式

2.1导入slf4j和logback的依赖

注意:导入时需先将log4j的包注释掉(原因:既然要在不改变源码的情况下改变日志输出的门面,则说明不能使用原来的日志框架,故将其注释掉,以便能很好的显示改变之后的效果)

<!--slf4j 核心依赖-->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.25</version>
</dependency>
<!--logback适配器依赖导入-->
<dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.2.10</version>
</dependency>

2.2导入桥接器的依赖

桥接器解决的是项目中日志的重构问题,当前系统中存在之前的日志API,可以通过桥接转换到slf4j的实现

  <!--slf4j 桥接器-->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>log4j-over-slf4j</artifactId>
  <version>1.7.25</version>
</dependency>

2.3 测试1.3的源码并打印结果到控制台

11:52:46.958 [main] INFO com.zm.slf4j.SLF4JTest - info信息输出

可以看出,此时控制台打印的日志格式并不是上述配置文件中自定义的格式,而是logback日志框架默认的日志打印格式

2.4 补充

当去掉log4j的jar包依赖而又未导入桥接器、slf4j、logback的jar包之前,源码代码效果

  导入包的效果

注意:此时无需重新导入包,因为导入识别的肯定是非log4j jar包下的Logger对象,无需管理,只需将桥接器、slf4j、logback的jar包导入之后,报错即可消失,由此便实现了在不改变任何一处源码的情况下,改变了日志框架。

三、底层源码实现逻辑

那么,为什么slf4j下的桥接器能起到这种效果呢,还是看看源码来一探究竟吧!

1.首先,log4j下的日志打印时需要调用LogManager来实例化logger记录器对象

即调用LogManager的getLogger方法进行实例化

Logger logger= LogManager.getLogger(SLF4JTest.class);

2.接着进入getLogger方法

 可以看出该方法返回了Log4jLoggerFactory类下的getLogger方法

3.进入Log4jLoggerFactory对象

找到该类中的getLogger方法

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

由于此时找不到log4j的jar包,无法对其进行初始化,也就是instance==NULL,则源码走else

新建logger对象

4.进入Logger对象

找到该类中如下的构造方法, 因为无法调用本类中的构造器进行初始化,故只能调用父类的构造器

protected Logger(String name) {
    super(name);
}

5.进入super方法即Category类

Category(String name) {
    this.name = name;
    this.slf4jLogger = LoggerFactory.getLogger(name);
    if (this.slf4jLogger instanceof LocationAwareLogger) {
        this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
    }

找到父类的构造方法,可以看到如下的核心代码是slf4jLogger

this.slf4jLogger = LoggerFactory.getLogger(name);

即slf4j包下,故LoggerFactory是来自于org.slf4j,所以可以看出,实际上在实例话logger时,是调用的slf4j包下的日志工厂进行实例化,而并非log4j包下的日志工厂。

四、总结

在使用桥接器之后,原有的基于log4j日志框架的源码完全无需修改,只需要在依赖中进行简单的注释导入即可。

桥接器和适配器不能同时导入依赖

桥接器如果配置在适配器的上方,则运行报错,不同同时出现

桥接器如果配置在适配器的下方,则不会执行桥接器,没有任何的意义

注:

适配器作用:在slf4j下集成log4j框架时需要用到,与上述的logback集成依赖属于同一类包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值