三、日志初始化的过程

系列文章目录

一、日志系统是如何拖慢你的系统的

二、日志的发展史

三、日志初始化过程


日志初始化过程分析

 1、前言

       上一篇文章我们讲了日志的发展史,在日志组件发展的过程中,为了使得日志组件过多的耦合在业务中,所以,在组件迭代的过程中,通过面向门面编程的思想,逐渐形成了上层门面接口和下层业务实现的完全解耦,从而达到了在更换日志实现的情况下,业务系统无感知的效果。

2、日志组件初始化

      接下来,我们来看日志初始化过程主要源码的分析:

      源码层面主要分析业界常用的slf4j门面和logback实现。

      日志组件源码的分析无非两方面:1、LoggerFactory的初始化;2、logger的初始化。

      请参考如下时序图:          

这里写图片描述

public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory(); //采用单例模式初始化LoggerFactory
        return iLoggerFactory.getLogger(name);
    }

 2.1 LoggerFactory的初始化

public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    performInitialization();
                }
            }
        }

        //省略...
    }

这里通过单例模式初始化ILoggerFactory

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

    }

bind()方法是初始化的重点方法。 

private static final void bind() {
        String msg;
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }

            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = 3;
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError var2) {
            //  
        }

    }

bind方法内部有一个比较关键的子方法,那就是findPossibleStaticLoggerBinderPathSet

static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();

        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }

            while(paths.hasMoreElements()) {
                URL path = (URL)paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException var4) {
            Util.report("Error getting resources from path", var4);
        }

        return staticLoggerBinderPathSet;
    }

其中,STATIC_LOGGER_BINDER_PATH变量就是固定字符串"org/slf4j/impl/StaticLoggerBinder.class"。这段代码的意思就是从org/slf4j/impl/目录中找到并加载StaticLoggerBinder类。

        为什么说这里是关键呢?那是因为这里就是门面和实现解耦的关键,如果某个日志实现想要被slf4j门面绑定,那在自己的实现中就必须有org/slf4j/impl/StaticLoggerBinder.class这样一个类。必须,logback组件天然是可以和slf4j绑定的,那么logback组件里一定有org/slf4j/impl/StaticLoggerBinder.class这样一个类。

       如果在工程中引入了多个slf4j的日志实现,是不是就会扫描出多个StaticLoggerBinder类呢?答案是的。

        如果引入了多个组件,那么在系统启动的时候,会打印如上图所示的几条日志,含义就是找到了多个StaticLoggerBinder组件,并告知实际使用的是哪一个(有的说是实际使用的是扫描到的第一个,有的说是随机一个。笔者不知如何证明,有知道的同学可以留言哈)。

        下面重点看的就是StaticLoggerBinder的初始化过程:

在该类中有一个属性loggerContext,这个属性很重要,loggerContext的构造函数如下:

public LoggerContext() {
        super();
        this.loggerCache = new ConcurrentHashMap<String, Logger>();

        this.loggerContextRemoteView = new LoggerContextVO(this);
        this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
        this.root.setLevel(Level.DEBUG);
        loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
        initEvaluatorMap();
        size = 1;
        this.frameworkPackages = new ArrayList<String>();
    }

在LoggerContext中有root对象,就是在这里初始化的。root的作用,我们后续再讲。 

static {
        SINGLETON.init();
    }

         该类中有一个静态代码块,调用了init方法

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 (Exception t) { // see LOGBACK-1159
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }

       “new ContextInitializer(defaultLoggerContext).autoConfig();” 这里是做defaultLoggerContext的自动配置。完成后会调用contextSelectorBinder.init()做Context Selector的绑定,最后设置initialized = true。

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);
        URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
    }

从这段代码我们可以看到logback获取自动配置文件的顺序,依次是:

  1. 系统配置“logback.configurationFile”,这个参数通常是通过”java -Dlogback.configurationFile=*“ 这个方式设置的
  2. 资源文件logback.groovy
  3. 资源文件logback-test.xml,这个对开发时做测试非常有帮助
  4. 资源文件logback.xml
public void configureByResource(URL url) throws JoranException {
        if (url == null) {
            throw new IllegalArgumentException("URL argument cannot be null");
        }
        final String urlString = url.toString();
        if (urlString.endsWith("groovy")) {
            if (EnvUtil.isGroovyAvailable()) {
                // avoid directly referring to GafferConfigurator so as to avoid
                // loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
                GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
            } else {
                StatusManager sm = loggerContext.getStatusManager();
                sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", loggerContext));
            }
        } else if (urlString.endsWith("xml")) {
            //我们主要使用的就是xml形式的配置文件,所以这里是主要的代码
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(loggerContext);
            configurator.doConfigure(url);
        } else {
            throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml");
        }
    }

首先创建一个配置器,设置好loggerContext后,根据url进行配置解析。

public final void doConfigure(URL url) throws JoranException {
        InputStream in = null;
        try {
            informContextOfURLUsedForConfiguration(getContext(), url);
            URLConnection urlConnection = url.openConnection();
            // per http://jira.qos.ch/browse/LBCORE-105
            // per http://jira.qos.ch/browse/LBCORE-127
//如果设置成true的话,当这个配置文件在一个jar中的时候,jar被锁住。如果应用需要重新加载的话会出现问题。
//不是太明白
            urlConnection.setUseCaches(false);

            in = urlConnection.getInputStream();
//将logback配置转换成流进行解析
            doConfigure(in, url.toExternalForm());
        } catch (IOException ioe) {
            
        }
    }

 

public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
        InputSource inputSource = new InputSource(inputStream);
        inputSource.setSystemId(systemId);
        doConfigure(inputSource);
    }
public final void doConfigure(final InputSource inputSource) throws JoranException {

        long threshold = System.currentTimeMillis();
        // if (!ConfigurationWatchListUtil.wasConfigurationWatchListReset(context)) {
        // informContextOfURLUsedForConfiguration(getContext(), null);
        // }
        SaxEventRecorder recorder = new SaxEventRecorder(context);
//将xml的<tag>bodyStr</tag>解析成StartEvent,BodyEvent,EndEvent,保存到recorder.saxEventList中
        //如果解析期间遇到warn、error、fatalError情况则会生成信息存入StaticLoggerBinder.defaultLoggerContext.sm中
        //后期将会将这些日志输出
        recorder.recordEvents(inputSource);
        doConfigure(recorder.saxEventList);
        // no exceptions a this level
        StatusUtil statusUtil = new StatusUtil(context);
        if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
            addInfo("Registering current configuration as safe fallback point");
            registerSafeConfiguration(recorder.saxEventList);
        }
    }
public void doConfigure(final List<SaxEvent> eventList) throws JoranException {
//将大部分的element path包装成ElementSelector并映射一个Action,比如xml中configuration/logger element path映射成LoggerAction
        //当解析到<configuration><logger>时会触发LoggerAction的begin方法
        //当解析到<configuration><logger>bodyStr 时会触发LoggerAction的body方法,当然logger element是没有body的
        //当解析到<configuration><logger>bodyStr</logger> 时会触发LoggerAction的end方法
        //action的映射关系存在JoranConfigurator#interpreter中        
        buildInterpreter();
        // disallow simultaneous configurations of the same context
        synchronized (context.getConfigurationLock()) {
            interpreter.getEventPlayer().play(eventList);
        }
    }

LoggerFactory的初始化过程主要就是对配置文件的解析,在分析过程发现,主要是由一个loggerContext对象进行维护信息的,下面是各个对象的属性引用关系:

可以看到,loggerContext对象的地位。 

2.2 logger的初始化

       LoggerFactory初始化成功之后,就可以通过LoggerFactory对logger进行初始化和维护了。

   

public final Logger getLogger(final String name) {

        if (name == null) {
            throw new IllegalArgumentException("name argument cannot be null");
        }

        // if we are asking for the root logger, then let us return it without
        // wasting time
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
            return root;
        }

        int i = 0;
        Logger logger = root;

        // check if the desired logger exists, if it does, return it
        // without further ado.
//从缓存中获取
        Logger childLogger = (Logger) loggerCache.get(name);
        // if we have the child, then let us return it without wasting time
        if (childLogger != null) {
            return childLogger;
        }
//如果缓存中获取不到,就初始化
        // if the desired logger does not exist, them create all the loggers
        // in between as well (if they don't already exist)
        String childName;
        while (true) {
            int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
            if (h == -1) {
                childName = name;
            } else {
                childName = name.substring(0, h);
            }
            // move i left of the last point
            i = h + 1;
            synchronized (logger) {
                childLogger = logger.getChildByName(childName);
                if (childLogger == null) {
                    childLogger = logger.createChildByName(childName);
                    loggerCache.put(childName, childLogger);
                    incSize();
                }
            }
            logger = childLogger;
            if (h == -1) {
                return childLogger;
            }
        }
    }

这里的含义是,在创建某个class的logger的时候,会对该class的全路径进行分析(通过“.”分割),下面举一个例子:

        假如现在有我们想获取xxService.class的logger,该类的路径为com.test.xxService。那么在getLogger的逻辑中,会同时创建com、com.test、com.test.xxService的logger对象,并且上一级作为下一级的parent,构建完之后,它们的对应关系如下:

        我们可以看到,最外层的包----com对应的logger,其parent是root。这样设计的目的:每一个logger都会继承其parent的level等信息。这样,我们只需要配置root的level为info或debug,那么在整个工程中,所有的logger在不设置自己的level的情况下,都会使用root的level。这样就可以做到全局管控。

 

参考:

https://blog.csdn.net/MrZhangXL/article/details/68489565

https://blog.csdn.net/chenping0574/article/details/100790859

https://blog.csdn.net/Z875983491/article/details/104042357

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值