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

到此加載配置完成

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