slf4j加载过程(基于logback实现)

主流的Web框架spring boot默认使用slf4j + logback,slf4j 为日志处理提供了统一的接口,比较代表的实现org.apache.log4j、ch.qos.logback等。
主要内容 logback的一般加载过程:

  • slf4j查找具体实现类的原理;
  • 加载配置文件过程(logback是logback.xml);


    logback load.jpg

1. slf4j + logback maven集成

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
</dependency>

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

2. 基本使用

package com.bclz;

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

/**
 * @Program: logback-test
 * @Description: logback测试
 * @Author: c
 * @Date: 2021-08-17 18:36
 */
public class Test {

    private static final Logger LOGGER= LoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {

        LOGGER.info("测试普通日志...");
        LOGGER.error("测试错误日志...");
    }

}


3. 源码分析

关键方法分析

  1. getILoggerFactory()

    public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
      INITIALIZATION_STATE = ONGOING_INITIALIZATION;
      performInitialization();
    }
    switch (INITIALIZATION_STATE) {
      case SUCCESSFUL_INITIALIZATION:
        //如果初始化成功,则调用StaticLoggerBinder单例的getLoggerFactory()方法获得LoggerFactory工厂对象
        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://bugzilla.slf4j.org/show_bug.cgi?id=106
        return TEMP_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
    }
    
  2. performInitialization()初始化

    private final static void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
        versionSanityCheck();
        }
    }
    
  • bind()

    try {
        //从classpath获取所有slf4j的实现,并将它们的资源路径存放到staticLoggerBinderPathSet中
        Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
        //若从classpath中找到了多个slf4j的实现,则打印警告
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        // the next line does the binding 将具体的实现绑定到slf4j
        StaticLoggerBinder.getSingleton();
        //修改初始化状态为初始化成功
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstitutedLoggers();
    } catch (NoClassDefFoundError ncde) {
        //若有多个实现,则会抛此异常
        //jvm在编译时能找到合适的类,而在运行时不能找到合适的类导致的错误(在这里是找不到具体的StaticLoggerBinder)
        ......   
    }
    
    1. findPossibleStaticLoggerBinderPathSet()

      注意在方法中有个经典的资源路径使用:

    • ClassLoader.getSystemResources(...)

    • LoggerFactory.class.getClassLoader().getResources(...)

      这两者由于类加载器的不同,可能会有不同的结果:

      getSystemResources(...)是用AppClassLoader加载的;

      而后者可能是自定义的类加载器,这两个方法可能会有不同的结果(加载jar资源一般推荐后者)。

    ClassLoader loggerFactoryClassLoader = LoggerFactory.class
        .getClassLoader();
    Enumeration<URL> paths;
    if (loggerFactoryClassLoader == null) {
        //STATIC_LOGGER_BINDER_PATH="org/slf4j/impl/StaticLoggerBinder.class"
        paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
    } else {
        paths = loggerFactoryClassLoader
            .getResources(STATIC_LOGGER_BINDER_PATH);
    }
    

至此,完成"org/slf4j/impl/StaticLoggerBinder.class"路径的查找,该实现类在logback-classic包中,slf4j的具体实现就是通过这种方式查找绑定的。

  1. StaticLoggerBinder.getSingleton()

该方法返回SINGLETON。
具体在类加载的静态代码块中初始化:

        static {
            SINGLETON.init();
        }
            /**
             * Package access for testing purposes.
             */
            void init() {
                try {
                    try {
                        new ContextInitializer(defaultLoggerContext).autoConfig();
                    } catch (JoranException je) {
                        Util.report("Failed to auto configure default logger context", je);
                    }
                    // logback-292
                    if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                        StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
                    }
                    contextSelectorBinder.init(defaultLoggerContext, KEY);
                    initialized = true;
                } catch (Throwable t) {
                    // we should never get here
                    Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
                }
            }

关键方法上下文初始化器的 <font color='red'>new ContextInitializer(defaultLoggerContext).autoConfig();</font>

            public void autoConfig() throws JoranException {
                StatusListenerConfigHelper.installIfAsked(loggerContext);
                //查找默认配置文件  
                URL url = findURLOfDefaultConfigurationFile(true);
                if (url != null) {
                    configureByResource(url);
                } else {
                    Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
                    if (c != null) {
                        try {
                            c.setContext(loggerContext);
                            c.configure(loggerContext);
                        } catch (Exception e) {
                            throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
                                            .getCanonicalName() : "null"), e);
                        }
                    } else {
                        //没有配置文件,则使用打印控制台的默认配置
                        BasicConfigurator basicConfigurator = new BasicConfigurator();
                        basicConfigurator.setContext(loggerContext);
                        basicConfigurator.configure(loggerContext);
                    }
                }
            }

再看看findURLOfDefaultConfigurationFile方法,就是查找具体路径的实现了

       public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
               // 获取当前实例的类加载器
               ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
               //logback.configurationFile
               URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
               if (url != null) {
                   return url;
               }
               
               //再找logback.groovy
               url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
               if (url != null) {
                   return url;
               }
               
               //再找logback-test.xml
               url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
               if (url != null) {
                   return url;
               }
               //最后找logback.xml
               return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
           }

至此xml配置文件加载完毕

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

推荐阅读更多精彩内容