使用桥接器在不改变源码的情况下实现从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集成依赖属于同一类包

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SLF4J 是一个简单的日志门面,它提供了一组日志 API,可以与许多流行的日志框架(例如 logbacklog4j、java.util.logging)集成。而 logback 则是一个功能强大的日志框架,它是 log4j 框架的继承者,提供了更好的性能和更丰富的特性。 要使用 SLF4J + logback 实现日志输出和记录,需要按照以下步骤进行: 1. 引入依赖:在项目的 pom.xml 文件中加入以下依赖: ``` <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> ``` 2. 配置 logback.xml 文件:在项目的 src/main/resources 目录下创建 logback.xml 文件,并配置日志输出的格式、级别、输出目标等信息。以下是一个简单的 logback.xml 配置示例: ``` <configuration> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="console" /> </root> </configuration> ``` 该配置将日志输出到控制台中,并显示日志的时间、线程、级别、类名和消息内容。 3. 在代码中使用 SLF4J API:在需要记录日志的代码中,使用 SLF4J API 进行日志记录。以下是一个示例: ``` import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyClass { private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class); public void doSomething() { LOGGER.info("This is a log message."); } } ``` 在这个示例中,我们使用 LoggerFactory.getLogger() 方法获取了一个 Logger 对象,并使用该对象进行日志记录。在记录日志时,我们使用LOGGER.info() 方法,指定日志的级别为 INFO。 以上就是使用 SLF4J + logback 实现日志输出和记录的基本步骤。通过 SLF4J,我们可以方便地切换不同的日志框架,而 logback 则提供了强大的日志功能,例如异步日志记录、定时滚动日志文件等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值