logback源码学习一:配置加载过程

一:slf4j绑定日志框架(logback为例)

从 Logger logger = LoggerFactory.getLogger(Test.class);开始,LoggerFactory是slf4j的类

public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                                autoComputedCallingClass.getName()));
                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
            }
        }
        return logger;
    }

核心代码只有一行: Logger logger = getLogger(clazz.getName());该方法的具体实现如下:可以看到先获取一个日志工厂.然后从日志工厂中获得日志记录器

  1. ILoggerFactory 是slf4j的工厂类接口
  2. getILoggerFactory();必然得到一个工厂实现类,那么日志框架的初始化过程就在其中
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

第一行代码: ILoggerFactory iLoggerFactory = getILoggerFactory();主要做了2件事情

  1. slf4j绑定真正的日志框架(此处为logback),初始化具体日志框架
  2. 获取日志框架的LogContext
 public static ILoggerFactory getILoggerFactory() {
       
        //  slf4j绑定真正的日志框架(此处为logback)
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        
        // 获取日志框架的LogContext(工厂)
        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();如下

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

核心在于: bind();主要完成

  1. findPossibleStaticLoggerBinderPathSet();classpath下查找org/slf4j/impl/StaticLoggerBinder.class的URL
  2. StaticLoggerBinder.getSingleton();加载StaticLoggerBinder并初始化日志框架(LogContext)
  3. 修改状态:INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION
  4. 记录日志:具体使用了哪个日志实现:Actual binding is of type…
 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);
            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);
        }
    }

日志的绑定是重点:StaticLoggerBinder.getSingleton();
当执行这行代码的时候,类加载器会去类路径下加载StaticLoggerBinder,如果有多种slf4j的实现(包含多个StaticLoggerBinder),那么只会加载其中一个,具体加载哪个有一定的随机性.当加载了一个StaticLoggerBinder,类路径下的其它StaticLoggerBinder类不会再加载

StaticLoggerBinder的初始化过程会做哪些事情?
类的部分结构如下:
在这里插入图片描述
按照类的加载机制,类变量(static)会按顺序加载,所以当执行到
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();此时类初始化还没有完成.就开始执行实例初始化了,以下三个实例变量已经存在于SINGLETON中
private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
接着会执行静态代码块:

static {
        SINGLETON.init();
  }

new ContextInitializer(defaultLoggerContext).autoConfig();
1.defaultLoggerContext已经在加载StaticLoggerBinder的时候初始化了.内部有一个loggerCache是一个Map( Map<String, Logger>),内部有一个默认的名为Root的logger
2. 创建一个ContextInitializer辅助初始化日志框架,内部有LogContext

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

二:logback具体初始化细节

StaticLoggerBinder.getSingleton();这行代码开始初始化具体日志实现
StaticLoggerBinder类在slf4j的实现中:实现了slf4j中的LoggerFactoryBinder接口

public interface LoggerFactoryBinder {

    public ILoggerFactory getLoggerFactory();

    public String getLoggerFactoryClassStr();
}

查看StaticLoggerBinder的代码:可以看到构造器是private修饰的,无法使用new创建对象. private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();这是唯一的实例对象通过以下方法访问

public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

重写的两个方法:

 public ILoggerFactory getLoggerFactory() {
        if (!initialized) {
            return defaultLoggerContext;
        }

        if (contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
        }
        return contextSelectorBinder.getContextSelector().getLoggerContext();
    }

    public String getLoggerFactoryClassStr() {
        return contextSelectorBinder.getClass().getName();
    }

在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 (Throwable t) {
            // we should never get here
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }

其中defaultLoggerContext是LoggerContext实现了slf4j的ILoggerFactory接口,名字就叫做default,内部已经包含一个名为root的logger

autoConfig()详情如下:

  1. URL url = findURLOfDefaultConfigurationFile(true);搜索logback的配置文件:优先级 logback.configurationFile(系统配置) > logback.groovy > logback-test.xml > logback.xml (后面几个都是Classpath下的文件)
  2. 加载自定义配置或者默认配置
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);
            }
        }
    }

加载自定义配置:

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

只学习xml配置方式

  JoranConfigurator configurator = new JoranConfigurator();
  configurator.setContext(loggerContext);
  configurator.doConfigure(url);

具体细节在configurator.doConfigure(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
            urlConnection.setUseCaches(false);

            in = urlConnection.getInputStream();
            doConfigure(in);
        } catch (IOException ioe) {
            String errMsg = "Could not open URL [" + url + "].";
            addError(errMsg, ioe);
            throw new JoranException(errMsg, ioe);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioe) {
                    String errMsg = "Could not close input stream";
                    addError(errMsg, ioe);
                    throw new JoranException(errMsg, ioe);
                }
            }
        }
    }

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

Interpreter在解析过程中起到关键作用: SAX解析xml时将xml各节点解析成对应的Event时通过xml元素对应的Action来做具体解析

  1. ruleStore各个xml节点对应的Action
  2. elementPath元素入栈出栈的临时空间 在startElement中进栈表示开始解析,endElement出栈表示解析完成
  3. actionListStack 各个Action的临时栈 解析开始中在ruleStore中找到元素对应的Action放入actionListStack,调用action的begin方法,解析完成时,出栈,调用end方法

三.常见的Action解析

1:ConfigurationAction

begin方法:
1:获取系统属性logback.debug或者configuration标签上的debug属性,当为true,开启打印logback内部日志
2:获取标签的scan属性,如果为true,定时扫描配置,跟新配置

public void begin(InterpretationContext ic, String name, Attributes attributes) {
        threshold = System.currentTimeMillis();

        String debugAttrib = getSystemProperty(DEBUG_SYSTEM_PROPERTY_KEY);
        if (debugAttrib == null) {
            debugAttrib = ic.subst(attributes.getValue(INTERNAL_DEBUG_ATTR));
        }

        if (OptionHelper.isEmpty(debugAttrib) || debugAttrib.equalsIgnoreCase("false") || debugAttrib.equalsIgnoreCase("null")) {
            addInfo(INTERNAL_DEBUG_ATTR + " attribute not set");
        } else {
            StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
        }

        processScanAttrib(ic, attributes);

        ContextUtil contextUtil = new ContextUtil(context);
        contextUtil.addHostNameAsProperty();

        LoggerContext lc = (LoggerContext) context;
        boolean packagingData = OptionHelper.toBoolean(ic.subst(attributes.getValue(PACKAGING_DATA_ATTR)), LoggerContext.DEFAULT_PACKAGING_DATA);
        lc.setPackagingDataEnabled(packagingData);

        if (EnvUtil.isGroovyAvailable()) {
            contextUtil.addGroovyPackages(lc.getFrameworkPackages());
        }
        ic.pushObject(getContext());
    }

2:ContextNameAction

给默认的logcontext设置名字

 public void body(InterpretationContext ec, String body) {

        String finalBody = ec.subst(body);
        addInfo("Setting logger context name as [" + finalBody + "]");
        try {
            context.setName(finalBody);
        } catch (IllegalStateException e) {
            addError("Failed to rename context [" + context.getName() + "] as [" + finalBody + "]", e);
        }
    }

3:PropertyAction

三种来源 : file resource ,name和value的形式
三种使用范围local :加载配置时可用 ,context:上下文可用,SYSTEM系统变量

public void begin(InterpretationContext ec, String localName, Attributes attributes) {

        if ("substitutionProperty".equals(localName)) {
            addWarn("[substitutionProperty] element has been deprecated. Please use the [property] element instead.");
        }

        String name = attributes.getValue(NAME_ATTRIBUTE);
        String value = attributes.getValue(VALUE_ATTRIBUTE);
        String scopeStr = attributes.getValue(SCOPE_ATTRIBUTE);

        Scope scope = ActionUtil.stringToScope(scopeStr);

        if (checkFileAttributeSanity(attributes)) {
            String file = attributes.getValue(FILE_ATTRIBUTE);
            file = ec.subst(file);
            try {
                FileInputStream istream = new FileInputStream(file);
                loadAndSetProperties(ec, istream, scope);
            } catch (FileNotFoundException e) {
                addError("Could not find properties file [" + file + "].");
            } catch (IOException e1) {
                addError("Could not read properties file [" + file + "].", e1);
            }
        } else if (checkResourceAttributeSanity(attributes)) {
            String resource = attributes.getValue(RESOURCE_ATTRIBUTE);
            resource = ec.subst(resource);
            URL resourceURL = Loader.getResourceBySelfClassLoader(resource);
            if (resourceURL == null) {
                addError("Could not find resource [" + resource + "].");
            } else {
                try {
                    InputStream istream = resourceURL.openStream();
                    loadAndSetProperties(ec, istream, scope);
                } catch (IOException e) {
                    addError("Could not read resource file [" + resource + "].", e);
                }
            }
        } else if (checkValueNameAttributesSanity(attributes)) {
            value = RegularEscapeUtil.basicEscape(value);
            // now remove both leading and trailing spaces
            value = value.trim();
            value = ec.subst(value);
            ActionUtil.setProperty(ec, name, value, scope);

        } else {
            addError(INVALID_ATTRIBUTES);
        }
    }

4:AppenderAction

1:读取class属性值,加载appper的类,读取name,并设置
2:从InterpretationContext中获取objectMap,从中获取APPENDER_BAG为key的值(appernder的map),将apppender放入

public void begin(InterpretationContext ec, String localName, Attributes attributes) throws ActionException {
        // We are just beginning, reset variables
        appender = null;
        inError = false;

        String className = attributes.getValue(CLASS_ATTRIBUTE);
        if (OptionHelper.isEmpty(className)) {
            addError("Missing class name for appender. Near [" + localName + "] line " + getLineNumber(ec));
            inError = true;
            return;
        }

        try {
            addInfo("About to instantiate appender of type [" + className + "]");

            appender = (Appender<E>) OptionHelper.instantiateByClassName(className, ch.qos.logback.core.Appender.class, context);

            appender.setContext(context);

            String appenderName = ec.subst(attributes.getValue(NAME_ATTRIBUTE));

            if (OptionHelper.isEmpty(appenderName)) {
                addWarn("No appender name given for appender of type " + className + "].");
            } else {
                appender.setName(appenderName);
                addInfo("Naming appender as [" + appenderName + "]");
            }

            // The execution context contains a bag which contains the appenders
            // created thus far.
            HashMap<String, Appender<E>> appenderBag = (HashMap<String, Appender<E>>) ec.getObjectMap().get(ActionConst.APPENDER_BAG);

            // add the appender just created to the appender bag.
            appenderBag.put(appenderName, appender);

            ec.pushObject(appender);
        } catch (Exception oops) {
            inError = true;
            addError("Could not create an Appender of type [" + className + "].", oops);
            throw new ActionException(oops);
        }
    }

5:AppenderRefAction

把Appender和logger整合

6:LoggerAction

在logcontext中loggerCache中存储了loggger对象,每个logger维护自己的childrenList,形成树形结构

public void begin(InterpretationContext ec, String name, Attributes attributes) {
        // Let us forget about previous errors (in this object)
        inError = false;
        logger = null;

        LoggerContext loggerContext = (LoggerContext) this.context;

        String loggerName = ec.subst(attributes.getValue(NAME_ATTRIBUTE));

        if (OptionHelper.isEmpty(loggerName)) {
            inError = true;
            String aroundLine = getLineColStr(ec);
            String errorMsg = "No 'name' attribute in element " + name + ", around " + aroundLine;
            addError(errorMsg);
            return;
        }

        logger = loggerContext.getLogger(loggerName);

        String levelStr = ec.subst(attributes.getValue(LEVEL_ATTRIBUTE));

        if (!OptionHelper.isEmpty(levelStr)) {
            if (ActionConst.INHERITED.equalsIgnoreCase(levelStr) || ActionConst.NULL.equalsIgnoreCase(levelStr)) {
                addInfo("Setting level of logger [" + loggerName + "] to null, i.e. INHERITED");
                logger.setLevel(null);
            } else {
                Level level = Level.toLevel(levelStr);
                addInfo("Setting level of logger [" + loggerName + "] to " + level);
                logger.setLevel(level);
            }
        }

        String additivityStr = ec.subst(attributes.getValue(ActionConst.ADDITIVITY_ATTRIBUTE));
        if (!OptionHelper.isEmpty(additivityStr)) {
            boolean additive = OptionHelper.toBoolean(additivityStr, true);
            addInfo("Setting additivity of logger [" + loggerName + "] to " + additive);
            logger.setAdditive(additive);
        }
        ec.pushObject(logger);
    }

到此加载配置完成

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章