【全面学习日志框架,实际案例分析, 完全掌握】

日志门面和日志实现

jcl和slf4j都是日志门面

jul,log4j1.x,log4j2,logback都是日志的实现

有说log4j既是门面也是实现, 待我进一步考证理解

jcl的日志使用

public class JclTest {

    @Test
    public void jclTest() {
        Log log = LogFactory.getLog("jcl");
        // 常用日志默认格式:
        // 信息: 这是一个jcl日志, 使用什么实现? 这是jul格式,默认红色
        log.info("这是一个jcl日志, 使用什么实现?");
    }
}

其实日志使用很简单,我们比较迷糊的还是日志的依赖组合

jcl+log4j1.x的依赖组合

完整依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>jcl</artifactId>
  <dependencies>
    <dependency>
      <groupId>len.hgy</groupId> <!--parent只会传递器依赖, 并不会传递器src的代码(也就是非jar的类容)-->
      <artifactId>logger-root</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <scope>compile</scope>
    </dependency>

<!--    <dependency> &lt;!&ndash;log4j 1.x&ndash;&gt;-->
<!--      <groupId>org.springframework.boot</groupId>-->
<!--      <artifactId>spring-boot-starter-log4j</artifactId>-->
<!--      <version>1.3.8.RELEASE</version>-->
<!--    </dependency>-->

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>

</project>

附一个简单的日志配置

log4j.properties

log4j.rootLogger=DEBUG, stdout, logfile

log4j.category.org.springframework=ERROR
log4j.category.org.apache=INFO

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=log4jV1 %d %p [%c] - %m%n

log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=${logger-root}/WEB-INF/log/jcl.log
log4j.appender.logfile.MaxFileSize=512KB
log4j.appender.logfile.MaxBackupIndex=5
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=log4jV1 %d %p [%c] - %m%n

打印日志格式:

log4jV1 2021-12-29 00:09:05,380 INFO [jcl] - 这是一个jcl日志, 使用什么实现?

说明: log4j1.x还是社区维护的开源项目, 到了log4j2已经是apache基金会的开源项目,所以依赖坐标变了

jcl+log4j2的依赖组合

完整依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>jcl</artifactId>
  <dependencies>
    <dependency>
      <groupId>len.hgy</groupId> <!--parent只会传递器依赖, 并不会传递器src的代码(也就是非jar的类容)-->
      <artifactId>logger-root</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <scope>compile</scope>
    </dependency>

    <!--    <dependency> &lt;!&ndash;log4j 1.x&ndash;&gt;-->
    <!--      <groupId>org.springframework.boot</groupId>-->
    <!--      <artifactId>spring-boot-starter-log4j</artifactId>-->
    <!--      <version>1.3.8.RELEASE</version>-->
    <!--    </dependency>-->

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

    <dependency> <!--log4j2 已经属于apache项目了-->
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>

</project>

配置: log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="log4j2 %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

打印日志格式:

log4j2 00:06:03.254 [main] INFO  jcl - 这是一个jcl日志, 使用什么实现?

原生的jcl+jul组合的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>jcl</artifactId>
  <dependencies>
    <dependency>
      <groupId>len.hgy</groupId> <!--parent只会传递器依赖, 并不会传递器src的代码(也就是非jar的类容)-->
      <artifactId>logger-root</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <scope>compile</scope>
    </dependency>

<!--    <dependency> &lt;!&ndash;log4j 1.x&ndash;&gt;-->
<!--      <groupId>org.springframework.boot</groupId>-->
<!--      <artifactId>spring-boot-starter-log4j</artifactId>-->
<!--      <version>1.3.8.RELEASE</version>-->
<!--    </dependency>-->

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-log4j12</artifactId>-->
<!--    </dependency>-->

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>

</project>

对,你没有看错,就是不需要引入日志的实现,因为jdk已经自带了, jul = java.util.logging

打印日志:

十二月 29, 2021 12:11:26 上午 jcl.JclTest jclTest
信息: 这是一个jcl日志, 使用什么实现?

​ 这个日志很特别它默认是红色的(我还不知道怎么修改颜色), 类基本信息和日志信息是换行打印的,这意味着它的行数更多, 它支持中文时间(没什么实际意义), jul的存在感几乎没有, 已经是被淘汰的日志系统了

jcl日志的原理

Apache Commons Loging (JCL)

Commons Loging 本身只提供日志接口,具体实现在运行时动态寻找对应组件?比如: log4j、jdk14looger 等。
在这里插入图片描述

JCL实现逻辑

​ 通过jcl 的源码可看出,jcl为每一种日志实现采用了一个适配器,具体采用哪个 是根据 动态的 根据指定顺序查找classPath 是否存在相应的实现。如果一个应用当中有多个classLoader 。 比如OSGI 规定了每个模块都有其独立的ClassLoader 。这种机制保证了插件互相独立, 同时也 限制了JCL在OSGi中的正常使用。这时出现了slf4j 基于静态绑定的方式解决了这个问题。

jcl源码

apache jcl源码

​ 学会了使用,我们就该了解其原理了

入口:

public void jclTest() {
    Log log = LogFactory.getLog("jcl"); // 这个是核心入口
    // 常用日志默认格式:
    // 信息: 这是一个jcl日志, 使用什么实现? 这是jul格式,默认红色
    log.info("这是一个jcl日志, 使用什么实现?");
}

// LogFactory#getLog(java.lang.String)
public static Log getLog(String name) throws LogConfigurationException {
    return getFactory().getInstance(name);
}

// LogFactoryImpl#getInstance(java.lang.String)
public Log getInstance(String name) throws LogConfigurationException {
    Log instance = (Log) instances.get(name); // name的日志是否已经创建了
    if (instance == null) {
        instance = newInstance(name); // 没有创建重新new一个实例
        instances.put(name, instance); // 放入Hashtable的缓存中
    }
    return instance; // 返回实例
}

protected Log newInstance(String name) throws LogConfigurationException {
    Log instance;
    try {
        if (logConstructor == null) { // log构造器为空就调用日志发现方法
            instance = discoverLogImplementation(name);
        }else {
            Object params[] = { name };
            instance = (Log) logConstructor.newInstance(params);
        }

        if (logMethod != null) {
            Object params[] = { this };
            logMethod.invoke(instance, params);
        }

        return instance;

    } 
}

上面有一个很重要的getFactory没有说明,避免代码太乱,单独说明

// LogFactory#getLog(java.lang.String)
public static Log getLog(String name) throws LogConfigurationException {
    return getFactory().getInstance(name);
}
// 这里我们进入getFactory, 因为不同的Factory将会使用不同的getInstance, 导致使用的日志实现不同
// public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
// 这是获取jcl的factory的片段
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
    if (isDiagnosticsEnabled()) {
        logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +
                      "' as specified by system property " + FACTORY_PROPERTY);
    }
    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else {
    if (isDiagnosticsEnabled()) {
        logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
    }
}

// 这是更加类加载器获取factory的代码片段
// This code is needed by EBCDIC and other strange systems.
// It's a fix for bugs reported in xerces
BufferedReader rd;
try {
    rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
} catch (java.io.UnsupportedEncodingException e) {
    rd = new BufferedReader(new InputStreamReader(is));
}

String factoryClassName = rd.readLine();
rd.close();

if (factoryClassName != null && ! "".equals(factoryClassName)) {
    if (isDiagnosticsEnabled()) {
        logDiagnostic("[LOOKUP]  Creating an instance of LogFactory class " +
                      factoryClassName +
                      " as specified by file '" + SERVICE_ID +
                      "' which was present in the path of the context classloader.");
    }
    factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
}

发现日志入口

LogFactoryImpl#discoverLogImplementation

从这方法名我们可以知道,这个意思是 发现日志实现,意思已经很明确了

方法有一个循环遍历

Log result = null;
...
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
    result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
// 循环中的属性的顺序就决定它的优先级
private static final String[] classesToDiscover = {
    LOGGING_IMPL_LOG4J_LOGGER, // org.apache.commons.logging.impl.Log4JLogger 高
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog" // 低
}; // 可见commons-loggin对这些类型的日志都做了一个适配的类

注意,这个退出的条件是&& result == null, 所以就算没有遍历完, 只要创建了日志类,就退出

所以我们非常有必要看看这个方法createLogFromClass

Class logAdapterClass = null;
ClassLoader currentCL = getBaseClassLoader(); // 获取类加载器

URL url;
String resourceName = logAdapterClassName.replace('.', '/') + ".class";
if (currentCL != null) {
    url = currentCL.getResource(resourceName );
} else {
    url = ClassLoader.getSystemResource(resourceName + ".class");
} // 简单粗暴,直接根据类名去查找

更具返回的具体日志类型实现打印日志

在这里插入图片描述

这么多的实现类

spring-jcl源码

上面我们知道了apache jcl的工厂实现类是

org.apache.commons.logging.impl.LogFactoryImpl#getInstance(java.lang.String)

但是如果你使用的spring,可能会有这个类

org.apache.commons.logging.LogFactoryService

注释翻译:

标准Apache Commons Logging的LogFactory类的最小子类,覆盖抽象的getInstance查找方法。 这只适用于标准公共日志jar意外进入类路径的情况,而标准LogFactory类执行其META-INF服务发现。 这个实现简单地委托给Spring的通用Log工厂方法。

已弃用: 因为它只适用于上述的后备方案

一次这个也是不推荐的方式, 我们来看下代码

public Log getInstance(String name) {
    return LogAdapter.createLog(name);
}
static { // 初始化 logApi的类型
    if (isPresent(LOG4J_SPI)) {
        if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
            // log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
            // however, we still prefer Log4j over the plain SLF4J API since
            // the latter does not have location awareness support.
            logApi = LogApi.SLF4J_LAL;
        }
        else {
            // Use Log4j 2.x directly, including location awareness support
            logApi = LogApi.LOG4J;
        }
    }
    else if (isPresent(SLF4J_SPI)) {
        // Full SLF4J SPI including location awareness support
        logApi = LogApi.SLF4J_LAL;
    }
    else if (isPresent(SLF4J_API)) {
        // Minimal SLF4J API without location awareness support
        logApi = LogApi.SLF4J;
    }
    else {
        // java.util.logging as default
        logApi = LogApi.JUL;
    }
}

// Class.forName判断类有没有通过spi导入
private static boolean isPresent(String className) {
    try {
        Class.forName(className, false, LogAdapter.class.getClassLoader());
        return true;
    }
    catch (ClassNotFoundException ex) {
        return false;
    }
}

// 更具初始化的日志信息创建适配的Log对象
public static Log createLog(String name) {
    switch (logApi) {
        case LOG4J:
            return Log4jAdapter.createLog(name); // log4j的适配器
        case SLF4J_LAL:
            return Slf4jAdapter.createLocationAwareLog(name);
        case SLF4J:
            return Slf4jAdapter.createLog(name); // slf4j的适配器
        default:
            // Defensively use lazy-initializing adapter class here as well since the
            // java.logging module is not present by default on JDK 9. We are requiring
            // its presence if neither Log4j nor SLF4J is available; however, in the
            // case of Log4j or SLF4J, we are trying to prevent early initialization
            // of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
            // trying to parse the bytecode for all the cases of this switch clause.
            // 没有找到就使用jul, 所以jdk自带一个日志还是很有必要的, 对于不会使用日志的小白开发省去了麻烦
            return JavaUtilAdapter.createLog(name);
    }
}

​ 这个使用的方式就不是通过哦ClassLoader进行动态绑定的了,这个就是静态绑定, 因为项目的jar包他国Class.forName加载就已经确定了是否可以使用

为什么现在推荐使用slf4j不使用jcl

commons-logging在2015之后就没有更新了

Class logAdapterClass = null;
ClassLoader currentCL = getBaseClassLoader(); // 获取类加载器

​ jcl是通过调用日志的ClassLoader去当前的classpath中找日志实现类, 属于一种动态绑定方式,对于不同的类加载器,加载出来的日志,可能会部分特性不能使用

​ 怎么理解,画个图理解

在这里插入图片描述

spring-jcl使用的是静态绑定,但是spring-jcl注释也说明弃用了, 在LogFactoryImpl没有获取到时候做了一个兜底操作,不推荐再使用了

所以我们应该使用slf4j

slf4j使用(适配方案)

SLF4j 组成方案

​ 全称 Simple Logging Facade for Java(简单日志门面),与jcl 类似本身不替供日志具体实 现,只对外提供接口或门面。与commons loging 不同的是其采用在classPath 加入适配器ajar 包来表示具体采用哪种实现 :

  • slf4j-log4j12.jar (表示适配 log4j) (不需要实现)

  • slf4j-jdk14.jar(表示适配 jdk Logging) (不需要实现包)

    这个就是 JUL, 这个14可不是指的jdk14,而是jdk1.4,因为不能使用点,所以省略了,log4j12同理

  • slf4j-jcl.jar(表示适配 jcl) (不需要实现包)

  • log4j-slf4j-impl(表示适配 log4j2) (不需要实现包)

  • logback-classic(表示适配 logback) (不需要实现包)

所以说slf4j是可以不引入api的依赖包的, 因为这些适配包里面都有slf4j的接口类的依赖(适配一般已经集成了API的jar), 保证我们代码各个场景都可以编译通过

在这里插入图片描述

在这里插入图片描述

基本测试类

public class SLF4JTest {

    @Test
    public void testSjf4j() {
        Logger log = LoggerFactory.getLogger(SLF4JTest.class);
        log.info("这是一个slf4j的日志");
    }
}

slf4j+log4j1.x依赖组合

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>slf4j</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>
  <dependencies>
<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-api</artifactId>-->
<!--      <version>1.7.30</version>-->
<!--    </dependency>-->

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.32</version>
    </dependency>

    <!--    <dependency>-->
    <!--      <groupId>org.apache.logging.log4j</groupId>-->
    <!--      <artifactId>log4j-slf4j-impl</artifactId>-->
    <!--      <version>2.17.0</version>-->
    <!--    </dependency>-->

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


    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

</project>

日志配置文件同上,不再赘述

日志格式:

log4jV1 2021-12-29 02:19:39,727 INFO [slf4j.SLF4JTest] - 这是一个slf4j的日志

slf4j+log4j2.x依赖组合

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>slf4j</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>

  <dependencies>
<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-api</artifactId>-->
<!--      <version>1.7.30</version>-->
<!--    </dependency>-->

<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-log4j12</artifactId>-->
<!--      <version>1.7.32</version>-->
<!--    </dependency>-->

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.17.0</version>
    </dependency>

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


    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

</project>

由于log4j-slf4j-impl既是适配包也是实现包,所以log4j2只需要引入一个依赖即可
在这里插入图片描述

日志配置文件同上,不再赘述

日志格式

log4j2 02:22:54.853 [main] INFO  slf4j.SLF4JTest - 这是一个slf4j的日志

slf4j+jdk14依赖组合

依赖: 同样是适配包,所以不需要单独引入实现

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>slf4j</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>

  <dependencies>
<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-api</artifactId>-->
<!--      <version>1.7.30</version>-->
<!--    </dependency>-->

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.7.32</version>
    </dependency>

<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-log4j12</artifactId>-->
<!--      <version>1.7.32</version>-->
<!--    </dependency>-->

<!--    <dependency>-->
<!--      <groupId>org.apache.logging.log4j</groupId>-->
<!--      <artifactId>log4j-slf4j-impl</artifactId>-->
<!--      <version>2.17.0</version>-->
<!--    </dependency>-->

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


    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

</project>

日志格式: 红色

十二月 29, 2021 2:44:43 上午 slf4j.SLF4JTest testSjf4j
信息: 这是一个slf4j的日志

slf4j+jcl依赖组合

有问题的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>slf4j</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>

  <dependencies>
    <!--    <dependency>-->
    <!--      <groupId>org.slf4j</groupId>-->
    <!--      <artifactId>slf4j-api</artifactId>-->
    <!--      <version>1.7.30</version>-->
    <!--    </dependency>-->

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jcl</artifactId>
      <version>1.7.32</version>
    </dependency>

    <!--    <dependency>-->
    <!--      <groupId>org.slf4j</groupId>-->
    <!--      <artifactId>slf4j-jdk14</artifactId>-->
    <!--      <version>1.7.32</version>-->
    <!--    </dependency>-->

    <!--    <dependency>-->
    <!--      <groupId>org.slf4j</groupId>-->
    <!--      <artifactId>slf4j-log4j12</artifactId>-->
    <!--      <version>1.7.32</version>-->
    <!--    </dependency>-->

    <!--    <dependency>-->
    <!--      <groupId>org.apache.logging.log4j</groupId>-->
    <!--      <artifactId>log4j-slf4j-impl</artifactId>-->
    <!--      <version>2.17.0</version>-->
    <!--    </dependency>-->

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


    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

</project>

报错如下:

java.lang.StackOverflowError
	at java.lang.reflect.Method.copy(Method.java:153)
	at java.lang.reflect.ReflectAccess.copyMethod(ReflectAccess.java:140)
	at sun.reflect.ReflectionFactory.copyMethod(ReflectionFactory.java:316)
	at java.lang.Class.searchMethods(Class.java:3013)
	at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
	at java.lang.Class.getMethod0(Class.java:3018)
	at java.lang.Class.getMethod(Class.java:1784)
	at org.apache.commons.logging.LogFactory.directGetContextClassLoader(LogFactory.java:896)

调用栈溢出

slf4j+logback依赖组合

这个依赖可以使用

<dependency>
	<groupId>com.networknt</groupId>
	<artifactId>slf4j-logback</artifactId>
	<version>2.0.32</version>
</dependency>

在这里插入图片描述

包含了logback-classic,但是明显这不是一个正式包

实际使用包

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.9</version>
</dependency>

日志格式

02:53:47.207 [main] INFO slf4j.SLF4JTest - 这是一个slf4j的日志

slf4j常见的报错

SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

因为静态日志绑定器加载失败, 缺少包

比如 log4j1.x的适配包

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.32</version>
</dependency>

log4j2.x的适配包(log4j2属于apache项目了)

<dependency>
   <groupId>org.apache.logging.log4j</groupId> 
   <artifactId>log4j-slf4j-impl</artifactId> (可能是为了和log4j12区分开, 将slf4j-log4j改成了log4j-slf4j并加了impl)
   <version>2.17.0</version>
</dependency>

slf4j桥接方案

	既然有了适配器方案,为什么还要使用桥接?

如果我们新代码想使用适配方案使用log4j2,但是老代码使用的是log4j,此时我们如果直接使用,代码将会有两个日志系统在打印日志,是非常不好的

​ 于是就有了桥接方案, 我们可以为旧代码搭建一桥梁,让它的日志也通过slf4j打印不就好了吗?

slf4j桥接方案原理图

​ 只要classPath 当中指定 sfl4j 适配器 包即可无缝将 原日志输出转移动slf4j上来。

  • jcl-over-slf4j :转移jcl 日志至slf4j

  • log4j-over-slf4j : 转移log4j 日志至 slf4j

  • jul-over-slf4j : 转移jul 日志至 slf4j

在这里插入图片描述

为什么没有log4j2和logback的桥接方案, 因为这两个日志框架足够优秀, 没有需要被桥接的需求

log4j直接打印 + log4j2适配案例桥接整改

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>logger-root</artifactId>
    <groupId>len.hgy</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>slf4j-bridging</artifactId>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>


  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.17.0</version>
    </dependency>

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

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
    </dependency>
  </dependencies>

</project>

日志配置同上

​ log4j.properties
​ log4j2.xml

测试代码

public class SLF4jBridgingTest {

    @Test
    public void testSlf4jBridging() {
        Logger log = LoggerFactory.getLogger("slf4j");
        log.info("这是一个slf4j的日志");

        org.apache.log4j.Logger log4j = org.apache.log4j.Logger.getLogger("log4j");

        log4j.info("这是一个log4j日志");
    }
}

打印结果

log4j2 03:32:57.038 [main] INFO slf4j - 这是一个slf4j的日志
log4jV1 2021-12-29 03:32:57,086 INFO [log4j] - 这是一个log4j日志

很明显出现了两个日志系统同时打印

通过log4j-over-slf4j 桥接整改

在加一个桥接依赖包,同时需要去掉log4j的依赖包,具体改动如下

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>
<!--    <dependency>-->
<!--      <groupId>log4j</groupId>-->
<!--      <artifactId>log4j</artifactId>-->
<!--      <version>1.2.17</version>-->
<!--    </dependency>-->

再次执行测试代码,结果如下

log4j2 03:39:15.451 [main] INFO  slf4j - 这是一个slf4j的日志
log4j2 03:39:15.460 [main] INFO  log4j - 这是一个log4j日志

现在两个打印都是使用log4j2了

其他日志的桥接方案类似处理

解决slf4j+jcl适配器方案报的StackOverflowError

报错

java.lang.StackOverflowError
	at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
	at java.lang.Class.getMethod0(Class.java:3018)
	at java.lang.Class.getMethod(Class.java:1784)
	at org.apache.commons.logging.LogFactory.directGetContextClassLoader(LogFactory.java:896)
	at org.apache.commons.logging.LogFactory$1.run(LogFactory.java:862)
	at java.security.AccessController.doPrivileged(Native Method)
	at org.apache.commons.logging.LogFactory.getContextClassLoaderInternal(LogFactory.java:859)
	at org.apache.commons.logging.LogFactory.getFactory(LogFactory.java:423)
	at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:704)
	at org.slf4j.impl.JCLLoggerFactory.getLogger(JCLLoggerFactory.java:77)  ## JCL
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)
	at org.apache.commons.logging.LogAdapter$Slf4jAdapter.createLocationAwareLog(LogAdapter.java:130)
	at org.apache.commons.logging.LogAdapter.createLog(LogAdapter.java:91)
	at org.apache.commons.logging.LogFactoryService.getInstance(LogFactoryService.java:46)
	at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:704)
	at org.slf4j.impl.JCLLoggerFactory.getLogger(JCLLoggerFactory.java:77)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)
	at org.apache.commons.logging.LogAdapter$Slf4jAdapter.createLocationAwareLog(LogAdapter.java:130)
	at org.apache.commons.logging.LogAdapter.createLog(LogAdapter.java:91)
	at org.apache.commons.logging.LogFactoryService.getInstance(LogFactoryService.java:46)
	at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:704)
	at org.slf4j.impl.JCLLoggerFactory.getLogger(JCLLoggerFactory.java:77)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)

很显然, jcl是一个门面没有日志实现, 也不匹配默认的jul包,导致,jcl还是去找jcl的门面,形成死循环, 最后栈溢出

尝试解决这个问题, 我有两个思路, 一个是使用 jcl-over-slf4j 桥接到slf4j,另一个是添加日志实现,打破死循环

但是细想, 其实这两个是同一个问题, 就算桥接到slf4j, 还是没有实现包

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jcl</artifactId>
    <version>1.7.32</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-1.2-api</artifactId>
    <version>2.17.0</version>
</dependency>

报错

SLF4J: Detected both jcl-over-slf4j.jar AND bound slf4j-jcl.jar on the class path, preempting StackOverflowError. 
SLF4J: See also http://www.slf4j.org/codes.html#jclDelegationLoop for more details.

java.lang.ExceptionInInitializerError
	at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:71)
	at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:42)
	at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
	at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
	at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
	at slf4j.SLF4JTest.testSjf4j(SLF4JTest.java:11)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.IllegalStateException: Detected both jcl-over-slf4j.jar AND bound slf4j-jcl.jar on the class path, preempting StackOverflowError. See also http://www.slf4j.org/codes.html#jclDelegationLoop for more details.
	at org.slf4j.impl.JCLLoggerFactory.<clinit>(JCLLoggerFactory.java:54)
	... 75 more

很明显jcl-over-slf4j.jar AND bound slf4j-jcl.jar不能同时使用

所以我尝试使用第二个方案

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jcl</artifactId>
    <version>1.7.32</version>
</dependency>

<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>jcl-over-slf4j</artifactId>-->
<!--      <version>1.7.32</version>-->
<!--    </dependency>-->

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.0</version>
</dependency>

通过添加实现的方式确实解决了, 不过这不就是jcl+log4j2的方式的一部分吗?我们的是slf4j+jcl+log4j2

所以我们为什么不直接使用 slf4j+log4j2呢? 解决这个问题只是为了自己更深入理解日志框架, 实际生成中不会这么用

slf4j源码

待完成, 太晚了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岁月玲珑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值