SLF4J--日志门面担当

Any problem in computer science can be solved by anther layer of indirection.

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决

日志门面存在的必要性

在java生态中,开始是没有日志的官方标准的,而是社区先出现了各种优秀的日志框架(各种日志框架的出现顺序如下图),而且彼此之间还不兼容;这样的话就会当在项目中引入的其他jar使用的日志框架和自己使用的日志框架不一致的时候,就会出现混乱,很难维护。所以,我们需要一个中间的抽象层,来隐藏各个日志框架的差异性,让对外的使用者对真正的底层日志实现无感,这样日志门面就出现了。

java日志历史.png

日志门面担当--SLF4J

1、初识SLF4J

SLF4J的意思是酸辣粉?呸,当然不是,你个吃货;它的英文全称是:Simple Logging Facade for Java ,简单的日志门面。我们先来看看SLF4J官网的一张经典的图:

slf4j经典图.png

这张图可以说是十分的详细了,不仅很好的说明了SLF4J是一个抽象层,而且还告诉了我们SLF4J如何和市面上各种各样的具体日志实现搭配使用:我们可以看到slf4j处于第二层,是直接面向我们应用的,也就是直接面向我们API调用工程师的;但是,我们发现有的日志框架和slf4j搭配使用只需要三层(比如logback,slf4j-simple等),而有的却需要四层(比如reload4,jul等),这又是为什么呢?

其实,这不就是我们前面说的原因,开始日志是没有规范的,各个社区自己玩自己的,日志门面SLF4J是后面出现的,有的日志框架在你SLF4J出现之前就有的,怎么遵从你slf4j的规范呢?是的,采用适配器模式,添加一个适配层帮助是配到SLF4J规范;而那些开始就遵守SLF4J规范的框架就不需要适配层的存在了,所以他们只需要单层就可以了。

2、SLF4J的规范

好了,说明了日志门面的作用后,我们来具体看看SLF4J指定的规范是怎么样的,了解了规范后,我们就可以在这个规范下实现自己的日志框架了,想想就很屌的样子,一起来看看吧:)

想想我们一般获取到logger实例是通过什么样的方式呢?我猜大家肯定会说:使用注解@SLF4J,确实,Lombook提供的这个编译期注解帮我们省去了自己编写如下代码来获取logger实例了:

    Logger log = LoggerFactory.getLogger(Class<?> clazz);

LoggerFactory是SLF4J提供的类,它可以帮助我们获取到真正的日志打印对象Logger,那它到底提供了怎样的规范,可以获取到真正的Logger呢?我们一起来看看代码吧

    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

LoggerFactory.getLogger的内部调用了上面静态的getLogger方法,看下这个方法的实现,这不就是一个抽象个工厂的模式吗?工厂的规范由SLF4J提供,角色的规范也由SLF4J提供,那么我们自己的日志框架只要依据工厂规范来实现自己的具体工厂,并且依据角色的规范来实现自己的具体日志实现类就可以了,那么还剩下一个问题就是:因为对于应用来说直接面向的仅仅是SLF4J的API,那么SLF4J如何发现第三方实现的具体工厂和日志实现类呢?其实,对于这种可插拔的功能,我们很容易想到使用SPI机制来实现,但是SLF4J究竟是如何来实现的呢,我们继续往下追踪代码:

来看看getILoggerFactory是如何拿到具体工厂的:

    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");
    }

这段代码的前半部分是进行初始化,去寻找具体的日志工厂,而后面的switch...case...是来根据初始化后的不同状态来进行返回一些默认的日志工厂或者真正的日志工厂;先来看最重要的初始化方法,初始化方法里面主要的就是bind方法来进行绑定具体的日志工厂实现类:

    private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
        } 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);
        } finally {
            postBindCleanUp();
        }
    }

SLF4J去探查具体的日志实现框架的逻辑就发生在findPossibleStaticLoggerBinderPathSet方法中,在方法中SLF4J会去加载org.slf4j.impl.StaticLoggerBinder.java这个类,并且会调用StaticLoggerBinder的静态方法getSingleton来进行绑定,这就是SLF4J提供的绑定规范了。

所以当我们自己的日志框架选择SLF4J作为日志门面,那么我们就必须要在org/slf4j/impl路径下提供一个的StaticLoggerBinder类(建议实现SLF4J提供的LoggerFactoryBinder接口)并要包含静态的getSingleton方法来实现绑定(其实就是返回单例的StaticLoggerBinder对象)。

3.特殊情况

当然,在这期间也会出现一些问题:比如如果我引入了多个第三方日志框架,探查到了多个org.slf4j.impl.StaticLoggerBinder.java类该怎么办呢? 还有就是:如果我们一个日志实现框架也没有引入,那么就不会探查到任何org.slf4j.impl.StaticLoggerBinder.java类,又会发生什么情况呢? 这些问题SLF4J都有考虑到,也都帮我们提供了解决方案如下:

如果我们在项目中引入了多个第三方的日志具体实现,那么SLF4J就会探查到多个org/slf4j/impl/StaticLoggerBinder.class,这时reportMultipleBindingAmbiguity方法会打印出日志提示,而且reportActualBinding会打印出日志告知最终采用的的StaticLoggerBinder。
而当我们没有引入任何的具体日志实现类的时候,也就是SLF4J没有探查到org/slf4j/impl/StaticLoggerBinder.class时,初始化的状态会被赋值为NOP_FALLBACK_INITIALIZATION(INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION),这样就会返回SLF4J默认实现的NOPLoggerFactory日志工厂,但是这个日志工厂提供的日志实现类打印日志时,不做任何操作,没有任何输出的。

写在最后

以上只是简单的介绍了下SLF4J作为日志门面的原理,其中SLF4J中还有一些有趣的点,比如说MDC(Mapped Diagnostic Context),还有为web应用提供的MDCInsertingServletFilter等等。所以还是希望大家有时间多看看SLF4J官网的内容。

另外,我们知道logback是SLF4J的直接实现,而且作者都是同一个人,在log4j2出来之前,SLF4J+logback可谓是日志的最佳配方,毕竟Springboot默认提供的就是它两。那么如何在SLF4J的规范下编写具体的日志实现框架,logback是我们不得不去学习的一个日志实现,考虑篇幅和时间为问题,关于logback的研究我们后续再一起研究,后会有期。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容