市面上的日志框架

JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j…
日志门面 (日志的抽象层)日志实现
JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-loggingLog4j JUL(java.util.logging) Log4j2 Logback

日志桥接架构图:
http://www.slf4j.org/legacy.html#jcl-over-slf4j

开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;

给系统里面导入slf4j的jar和 logback的实现jar

SLF4j使用

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

图片.png

每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;

遗留问题

a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx

统一日志记录,即使别的框架和我一起统一使用slf4j进行输出?

图片.png

如何让系统中所有的日志都统一到slf4j;

1、将系统中其他日志框架先排除出去;

2、用中间包来替换原有的日志框架;

3、我们导入slf4j其他的实现

SpringBoot日志关系

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

SpringBoot使用它来做日志功能;

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

底层依赖关系
图片.png

Slf4j框架绑定原理

说到Logger日志的动态绑定,主要归功与Slf4j,在之前的文章也说过,Slf4j是类似于Apache Common-Logging,英文为Simple Logging Facade,是一个简单的日志门面适配器,所有的日志代码都可以用slf4j方式,它会根据项目具体依赖的日志实现包进行日志操作,只需修改pom.xml文件中的日志实现依赖,对于Log4j、Log4j2和Logback等都有相应的桥接包,对相应的slf4j-api接口实现。

   那么slf4j是如何自动绑定上的呢?

   多个框架是如何选择的呢?

Slf4j框架架构原理

日志的绑定原理主要是依赖Slf4j的静态加载,从相应的实现框架中获取Logger。需要自己动手一步步跟踪下去才会感触良多。

Slf4j的实现流程:

图片.png

Logger日志打印调用流程:

图片.png

在slf4j中主要是实现 LoggerFactoryBinder 的 StaticLoggerBinder 采用单例模式,编译时期静态加载方式,得到不同的ILoggerFactory工厂的实现类,最终拿到相应框架所匹配的Logger。

对于Slf4j的源码 分析我这边就不再过多的赘余,关键得自己去理解的看看,推荐一个: Java日志体系(slf4j)

  介绍一下slf4j中主要的几个类的作用:

  • Logger:slf4j日志接口类,提供了trace < debug < info < warn < error这5个级别对应的方法,主要提供了占位符{}的日志打印方式;

  • Log4jLoggerAdapter:Logger适配器,主要对org.apache.log4j.Logger对象的封装,占位符{}日志打印的方式在此类中实现;

  • LoggerFactory:日志工厂类,获取实际的日志工厂类,获取相应的日志实现对象;

  • lLoggerFactory:底层日志框架中日志工厂的中介,再其实现类中,通过底层日志框架中的日志工厂获取对应的日志对象;

  • StaticLoggerBinder:静态日志对象绑定,在编译期确定底层日志框架,获取实际的日志工厂,也就是lLoggerFactory的实现类;

  项目中有多种日志框架时会存在多个StaticLoggerBinder时候获取第一个,主要是采用类的顺序加载机制,与Pom文件中依赖的填写顺序有关(个人实践得到)。

图片.png

这里需要讲一下Maven中对于jar包的处理方式

Maven的Jar包默认处理策略

最短路径优先

  Maven 对于两个依赖有冲突时,谁的路径深度短谁优先:如对 D1 和 D2 时,会默认选择最短路径的那个 jar 包,即 D2。E->F->D2 比 A->B->C->D1 路径较短 。

最先声明优先

  对与相同路径长度的,选择最先声明的依赖,如 A->B->C1, E->F->C2 ,两个依赖路径长度相同,选择最先声明。


框架桥接过程

首先先看Slf4j-api包结构:主要定义了一些接口做相应的适配,其包结构主要是 org.slf4j 其他实现框架都是根据其进行相应的桥接

图片.png

图片.png

Log4j的桥接包形式:相同包名,StaticLoggerBinder采用构造器形式获取 Log4jLoggerFactory 工厂

图片.png

Logback的桥接包形式:相同包名,在StaticLoggerBinder调用时就已经静态初始化LoggerContext 工厂
   Logback的详细配置:https://cloud.tencent.com/developer/article/1445599

图片.png

Log4j2的桥接包形式:相同包名,与Log4j类似采用构造器形式获取 Log4jLoggerFactory 工厂

图片.png

以下是个人收集一些比较简单好理解的绑定关系图,体现了StaticLoggerBinder绑定的重要性

图片.png

图片.png


绑定过程中所遇到的问题

  1. Log4j和Log4j2需要配置好log4j.xml和log4j2.xml文件,不然无法正常使用,Logback可以正常使用

  2. 与SpringBoot的spring-boot-starter中的依赖spring-boot-starter-logging内部的依赖Logback产生冲突(可以自己点击查看一番)

Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.Log4jLoggerFactory loaded from file:/Users/zhouguanglin/.m2/repository/org/slf4j/slf4j-log4j12/1.7.29/slf4j-log4j12-1.7.29.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.impl.Log4jLoggerFactory
    at org.springframework.util.Assert.instanceCheckFailed(Assert.java:699)
    at org.springframework.util.Assert.isInstanceOf(Assert.java:599)
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:284)
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:104)
    at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:232)
    at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:213)

主要原因是:SpringBoot所使用的搭配日志框架是Logback

    若是项目使用SpringBoot但搭配的是其他日志框架A,由StaticLoggerBinder.getSingleton().getLoggerFactory()获取的是搭配框架A的实现,导致与SpringBoot内部自身搭配的Logback产生冲突,可以进入报错地方查看。

图片.png

从代码逻辑中可以发现,Assert.isInstanceOf 判断获取得到的ILoggerFactory的实现类必须是LoggerContext 才OK,硬性规定不然就会报错。

  解决方案:只要在SpringBoot中排除Logback的依赖就OK,如下:

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>logback-classic</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
            </exclusions>
        </dependency>

Q.E.D.


爱调味品的大哥