一.引言

  日志在排除线上问题、跟踪线上系统运行情况中发挥着重要的作用。在java应用的开发中,有许多的日志框架。

  这些日志框架大致可以分为两类,一类是日志门面(JCL、slf4j),定义日志的抽象接口。另一类是日志的实现(JUL、log4j、log4j2、logback),负责真正的处理日志。

  日志框架的发展的前因后果

  为什么会有这么多的日志框架,从Java日志框架的发展史里大概可以一探究竟。

log4j是Java社区最早的日志框架,推出后一度成为Java的事实日志标准,据说Apache曾建议Sun把log4j加入到Java标准库中,但是被Sun拒绝。
    在Java1.4中,Sun在标准库中推出了自己的日志框架java.util.logging,功能相对简陋
    虽然JUL相对简陋,但还是有类库采用了它,这就出现了同一个项目中同时使用log4j和JUL要维护两套配置的问题,Apache试图解决这个问题,推出了JCL日志门面(接口),定义了一套日志接口,底层实现支持log4j和JUL,但是并没有解决多套配置的问题
    log4j的主力开发Ceki Gülcü由于某些原因离开了Apache,创建了slf4j日志门面(接口),并实现了性能比log4j性能更好的logback(如果Ceki Gülcü没有离开Apache,这应该就是log4j2的codebase了)
    Apache不甘示弱,成立了不兼容log4j 1.x的log4j2项目,引入了logback的特性(还酸酸地说解决了logback架构上存在的问题),但目前采用率不是很高

日志框架的选择

  那么面对这些日志框架,该如何选择呢?如果你是在开发一个新的项目(类库)而不是维护一个上古的遗留代码,那么在打印日志时推荐使用日志门面,秉承面向接口编程的思想,与具体的日志实现框架解耦,这样日后可以很容易地切换到其他的日志实现框架。

  特别是当你的代码以SDK的方式提供给别人使用时,使用日志门面能避免使用方可能出现的日志框架冲突问题。如果你的SDK里使用了log4j,而使用方的应用里使用的logback,这时使用方就不得不分别针对log4j和logback维护两套日志配置文件,来确保所有日志正常的输出。

  在目前已有的两个日志门面框架中,slf4j规避了JCL在部分场景下因为ClassLoader导致绑定日志实现框架失败的问题;能支持以上提到的所有日志实现框架;且slf4j支持占位符功能,在需要拼接日志的情况在接口层面就比JCL有更好的性能,所以推荐使用slf4j,下面简单多介绍下slf4j。

// slf4j的占位符功能
LOGGER.info("hello {}", name);

二.slf4j-api

源码阅读-logback解析之对接日志门面slf4j插图

slf4j-api为各种日志框架提供了一个统一的接口,使用户可以用统一的接口来记录日志,但是可以动态的决定真正的实现框架。logback、log4j、common-logging等都实现了这个接口。所以阅读logback源码之前,首先看看logback是如何与slf4j对接的。

源码阅读-logback解析之对接日志门面slf4j插图1

slf4j-api最重要的是三个接口和一个入口类。

两个接口分别是org.slf4j.ILoggerFactory、org.slf4j.Logger和org.slf4j.spi.LoggerFactoryBinder。

入口类是org.slf4j.LoggerFactory。

1.org.slf4j.ILoggerFactory接口只提供了一个获取Logger的动作,由具体的日志框架实现者去实现该接口。

/**
 * ILoggerFactory logger工厂接口
 * 提供获取Logger动作
 * 由具体的日志框架实现者去实现该接口
 */
public interface ILoggerFactory {

    /**
     * 获取Logger
     *
     * @param name the name of the Logger to return
     * @return a Logger instance 
     */
    public Logger getLogger(String name);
}

2.org.slf4j.Logger接口提供了打印不同等级日志的动作和获取loggerName等动作。

源码阅读-logback解析之对接日志门面slf4j插图2

3.org.slf4j.spi.LoggerFactoryBinder接口提供了获取ILoggerFactory的实现类的动作。一个内部接口去帮助LoggerFactory绑定合适的ILoggerFactory的实例。

由指定路径的实现类org.slf4j.impl.StaticLoggerBinder 去实现该接口,每一个实现slf4j门面的日志框架都有一个实现该接口的org.slf4j.impl.StaticLoggerBinder。

public interface LoggerFactoryBinder {

    public ILoggerFactory getLoggerFactory();
 
    public String getLoggerFactoryClassStr();
}

 4..org.slf4j.LoggerFactory是一个入口类,通过调用ILoggerFactory的实现类获取Logger对象,稍后我们分析LoggerFactory的源码。

首先看一个简单的例子:

public class HelloWorld1 {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
        logger.debug("Hello world.");
    }
}

不管实现框架是什么,要获取Logger对象,都是通过这个LoggerFactory的getLogger()方法,所以这个类也非常重要。具体实现框架和slf4j的对接,就是通过这个类LoggerFactory,让我们来看看这个类是如何对接slf4j的。

 getLogger(String name)

1.获取ILoggerFactory的实现类,并调用getLogger(name)方法返回Logger对象。

public static Logger getLogger(String name) {
        //获得ILoggerFactory的实现类
        //把获取实际Logger的工作,委托给具体的日志框架上,比如log4j、logback、common-logging等
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

getILoggerFactory()

 1.首先用一个静态变量INITIALIZATION_STATE标识初始化状态,初始值=0,用双重检查锁判断这个状态,如果没有初始化,则将状态置为1,并且调用performInitialization()方法去初始化。

2.当初始化状态=1,则调用StaticLoggerBinder.getSingleton().getLoggerFactory()获取ILoggerFactory接口的实现类。

 当初始化状态=4,则意味着没有可绑定的实现框架,返回一个空的ILoggerFactory接口实现类NOPLoggerFactory。内部getLogger(name)方法返回一个NOPLogger对象,其中打印方法中什么也没做。

 当初始化状态=2,则初始化过程中发生异常,直接抛出IllegalStateException()异常。

 当初始化状态=3,则正在进行初始化,返回一个替代工厂SubstituteLoggerFactory。

static final int UNINITIALIZED = 0;   //未初始化
    static final int ONGOING_INITIALIZATION = 1;  //正在初始化
    static final int FAILED_INITIALIZATION = 2;  //失败初始化  
    static final int SUCCESSFUL_INITIALIZATION = 3;  //成功初始化
    static final int NOP_FALLBACK_INITIALIZATION = 4;  //空初始化

    static volatile int INITIALIZATION_STATE = UNINITIALIZED;  //初始化状态
 static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory(); //替代工厂
    static final NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory(); //空工厂
/**
     * Return the {@link ILoggerFactory} instance in use.
     * <p/>
     * <p/>
     * ILoggerFactory instance is bound with this class at compile time.
     * ILoggerFactory 实例在编译时期和该类绑定
     * @return the ILoggerFactory instance in use
     */
    public static ILoggerFactory getILoggerFactory() {
        //判断是否初始化
        //双重检查锁 并发情况下重复执行初始化
        if (INITIALIZATION_STATE == UNINITIALIZED) {//若已经初始化则无需竞争锁 提高性能
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }

 performInitialization()

1.调用bind()执行绑定。

2.如果初始化成功,则进行版本检查。

private final static void performInitialization() {
        //执行绑定
        bind();
        //成功获取binder实现
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            //这里会获取binder的静态变量REQUESTED_API_VERSION(要求API的版本号)和slf4j做比较
            versionSanityCheck();
        }
    }

 bind()

1.首先是检查classpath里是否存在多个日志框架,如果有就会抛出警告信息,提示用户只保留一个。

2.这段代码提到了一个最关键的StaticLoggerBinder类,这个类实现LoggerFactoryBinder接口,提供获取ILoggerFactory实现类的方法,检查是否有这个类存在,以及这个类有没有getSingleton()方法,如果有,就视为绑定成功。其实这个类还必须有getLoggerFactory()方法,否则虽然绑定成功,但是到了运行期,一样会抛出NoSuchMethodException。我认为这里是slf4j设计不好的地方,应该在bind()方法里,就检查一下StaticLoggerBinder有没有实现getLoggerFactory()方法。

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            //判断系统变量java.vendor.url是否包含android 默认是不会包含的
            if (!isAndroid()) {
                //在classpath下查找org/slf4j/impl/StaticLoggerBinder.class
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                //检查是否找到多个binder
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            //饿汉单例 首先初始化binder实例
            StaticLoggerBinder.getSingleton();
            //初始化状态置为成功
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            //报告实际绑定路径
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                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 nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

总结一下大致流程:

  1.LoggerFactory通过StaticLoggerBinder获取ILoggerFactory的实现类。

  2.通过ILoggerFactory的实现类获取Logger的实现类返回。

作者: 张天赐的博客

出处:https://www.cnblogs.com/tc971121/p/12508180.html

版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。



源码阅读-logback解析之对接日志门面slf4j插图3

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:https://choupangxia.com/2020/11/18/logback-slf4j/