快速理解JAVA日志框架slf4j其简单的实现原理和logback,log4j之间的关系(以及类似桥接包slf4j-log4j12的作用)

 

 

首先得搞清楚什么是日志框架

JUL(java.util.logging),JCL(Jakarta Commons Logging,spring框架内部使用),Log4j,Log4j2,Logback(具体框架,springboot使用)、SLF4j、jboss-logging等。

其中又可以分为

日志门面(提供日志接口)

日志实现(具体)

JCL

SLF4j

Jboss-logging

JUL

log4j

log4j2

logback

 

Log4j,Logback,SLF4j都是同一个作者,所以logback可以不需要适配包就可以直接配合SLF4j来使用,log4j出现比较早,所以需要适配包slf4j-log4j12来搭配使用。

SLF4j作为现在最好用的日志门面当然由它的过人之处,他可以完美地通过桥接包或者适配包,解决日志冲突和快速便捷地切换日志实现,

从这张图可以看出slf4j-simple、logback都是slf4j的具体实现,log4j和jcl并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12和slf4j-jdk14来实现slf4j,最终输出log4j和jcl的日志

 

但是从开始就可以看到一个项目中不可能只有一种日志,势必会引起各种日志冲突这时候slf4j也有相应的解决方法

从第一个例子可以看出如果最终直接使用slf4j和logback的组合(当然这是最好的组合),就可以加入jcl-over-slf4j.jar ,log4j-over-slf4j-jar,jul-to-slf4j.jar等包来替代原来的log4j和jcl,jul的日志jar包,这就相当于狸猫换太子(有了狸猫就不需要太子了,也就是原来的log4j或者其他日志的核心包就不用加入了)

例如log4j,,按照log4j库的目录放置几个相关类(比如Logger等),这样系统中使用log4j的代码编译就不会出错,但是这些代理logger内部实现时却将日志悄悄代理到了SLF4J相关接口,最终通过logback来实现

第二,三个例子无非就是依靠slf4j整合各种日志接口,通过适配包来使用log4j或者jcl来实现日志输出,就不细说了

接下来就是看slf4j的具体实现过程

           <dependency>
             <groupId>junit</groupId>
               <artifactId>junit</artifactId>
               <version>4.11</version>
               <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>1.7.25</version>
         </dependency>
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>slf4j的日志实现类
             <version>1.2.3</version>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>这个也是slf4j的日志实现log4j形式
             <version>1.7.21</version>
         </dependency>
            加两个实现类是为了下面的测试,不要学,一个日志实现就够了

 依赖引入如上

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test01 {
    @Test
    public static void test() {


        Logger log=  LoggerFactory.getLogger(Test01.class);
        log.debug("debug消息id={},{}",1,"2emmmm");//支持占位符
        log.info("info");
        log.warn("warn");
        log.error("error");
    }
}

一段java程序,这时候有小伙伴就会问了,slf4j不是日志门面么,不是抽象么.不是没有具体的实现么,为什么日志输出的时候不用引入其他包???,(这也是当时困扰了我好久,怀疑门面日志的定义),那就先来说说门面日志是什么意思

slf4j是门面模式的典型应用,因此在讲slf4j前,我们先简单回顾一下门面模式

门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。用一张图来表示门面模式的结构为:

门面模式的核心为Facade即门面对象,门面对象核心为几个点:

  • 知道所有子角色的功能和责任

  • 将客户端发来的请求委派到子系统中,没有实际业务逻辑

  • 不参与子系统内业务逻辑的实现

说人话就是slf4j只是一个日志标准,并不是日志系统的具体实现。

所以slf4j只做两件事情

  • 提供日志接口

  • 提供获取具体日志实现对象的方法

既然slf4j提供获取日志实现对象的方法,那我们就跟着运行一下吧,看看具体怎么获取

运行上面的测试后

第一步  public static Logger getLogger(Class clazz) {
        return getLogger(clazz.getName());
        }
        第二步
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
        }
        第三步
public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
        INITIALIZATION_STATE = 1;
        performInitialization();
        }
        //前面都是在初始化没什么价值bind才是关键
        第四步
private static final void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == 3) {
        versionSanityCheck();
        }

        }

 前面都是在初始化没什么价值bind才是关键

进入bind里面后

        第五步
private static final void bind() {
        String msg;
        try {
        Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
//首先运行findPossibleStaticLoggerBinderPathSet
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = 3;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstitutedLoggers();
        } catch (NoClassDefFoundError var2) {
        msg = var2.getMessage();
        if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
        failedBinding(var2);
        throw var2;
        }

        INITIALIZATION_STATE = 4;
        Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
        Util.report("Defaulting to no-operation (NOP) logger implementation");
        Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
        } catch (NoSuchMethodError var3) {
        msg = var3.getMessage();
        if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
        INITIALIZATION_STATE = 2;
        Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
        Util.report("Your binding is version 1.5.5 or earlier.");
        Util.report("Upgrade your binding to version 1.6.x.");
        }

        throw var3;
        } catch (Exception var4) {
        failedBinding(var4);
        throw new IllegalStateException("Unexpected initialization failure", var4);
        }

        }

先运行 findPossibleStaticLoggerBinderPathSet();

   第六步
        findPossibleStaticLoggerBinderPathSet()方法其实就是通过classloader的getResources()方法找到所有的名为”org/slf4j/impl/StaticLoggerBinder.class”的resource。
private static Set findPossibleStaticLoggerBinderPathSet() {
        LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();

        try {
        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
        //获取类加载器
        Enumeration paths;
        if (loggerFactoryClassLoader == null) {
        paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);

       
     //     private static String STATIC_LOGGER_BINDER_PATH =
     //"org/slf4j/impl/StaticLoggerBinder.class";是当前类中已经定义的静态变量
     //


        } else {
        paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
        }

        while(paths.hasMoreElements()) {
        URL path = (URL)paths.nextElement();
        staticLoggerBinderPathSet.add(path);
        //这里就是往set集合添加地址
        }
        } catch (IOException var4) {
        Util.report("Error getting resources from path", var4);
        }

        return staticLoggerBinderPathSet;//最后返回set集合
        }

findPossibleStaticLoggerBinderPathSet()方法其实就是通过classloader的getResources()方法找到静态变量名为STATIC_LOGGER_BINDER_PATH 所代表的 org/slf4j/impl/StaticLoggerBinder.class默认的logger地址

这时候回来看看slf4j的实现类

以上就是logback和log4j形式的slf4j的实现包都有这个StaticLoggerBinder这个类也就是说所有的slf4j的实现包都有这个类

所以如果项目中有多个slf4j的实现类set的集合就不止一条数据

接下来退出 findPossibleStaticLoggerBinderPathSet();
      运行  reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);

//        第七步
private static boolean isAmbiguousStaticLoggerBinderPathSet(Set staticLoggerBinderPathSet) {
        return staticLoggerBinderPathSet.size() > 1;
        }

private static void reportMultipleBindingAmbiguity(Set staticLoggerBinderPathSet) {
        //如果set集合大于1个以上就会打印绑定前后的信息否则不答应直接跳出
        if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
        Util.report("Class path contains multiple SLF4J bindings.");
        Iterator iterator = staticLoggerBinderPathSet.iterator();

        while(iterator.hasNext()) {
        URL path = (URL)iterator.next();
        Util.report("Found binding in [" + path + "]");
        }

        Util.report("See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.");
        }

        }

 可以看出这个方法先是判断我们的set集合是否大于1如果大于就打印出这个警告信息

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/D:/mvnrepository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/mvnrepository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

也就是提示我们同时引入了多个slf4j的实现,然后选择其中的一个作为我们使用的日志系统(这里只是演示提示信息,最好只使用一个slf4j的日志实现,所以多个slf4j的实现类不会报错只是会提示警告而已)


private final static void bind() {
        ......
        StaticLoggerBinder.getSingleton();
        //也就是说最后执行绑定任务的是这行代码
        }

 但是这个StaticLoggerBinder类   slf4j-api本身是没有的,点进去就是logback包的org.slf4j.impl.StaticLoggerBinder也就是通过上面的步骤选择一个org/slf4j/impl/StaticLoggerBinder.class再实现具体的日志输出

参考博客:https://www.cnblogs.com/xrq730/p/8619156.html

如果错误务必留言

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SLF4J、log4j和logbackJava的三个不同的日志框架SLF4J是Java的一个日志门面,它提供了一些通用的API,可以与不同的具体日志框架集成使用。log4j是一个具体的日志框架,它提供了丰富的功能和配置选项。logback则是由log4j的作者设计完成的一个日志框架,它拥有更好的特性,并且是SLF4J的原生实现。 区别如下: 1. SLF4J是一个日志门面,它只提供了一些通用的API,而不是具体的实现。它的作用是为了让开发人员可以在不同的日志框架之间进行切换和集成,而不需要修改代码。 2. log4j是一个具体的日志框架,它提供了丰富的功能和配置选项。log4j可以与SLF4J结合使用,需要提供一些对应的jar。 3. logback是由log4j的作者设计完成的日志框架,它是SLF4J的原生实现logback拥有更好的特性,并且可以完整地实现SLF4J的API。logback括了三个模块:logback-core、logback-classic和logback-access,分别用于提供基础功能、改良版本以及与Servlet容器集成。 因此,SLF4J提供了通用的日志口,log4j是其中一个具体的实现,而logback则是log4j的改良版本,同时也是SLF4J的原生实现。根据具体需求和偏好,开发人员可以选择使用其中的任意一个日志框架。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Java日志框架SLF4J和log4j以及logback的联系和区别](https://blog.csdn.net/weixin_30241919/article/details/101487496)[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^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值