logback源碼分析-1.初始化

源碼基於logback 1.1.7java

首先看個demo:node

根據類名獲取Logger,這裏是用slf4j提供的工廠來獲取logger的:api

private static final Logger logger = LoggerFactory.getLogger("className");

進入getLogger方法:併發

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

首先獲取ILoggerFactory,而後根據name獲取Logger。app

getILoggerFactory方法:ui

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
      INITIALIZATION_STATE = ONGOING_INITIALIZATION;
      //執行初始化
      performInitialization();
    }
    switch (INITIALIZATION_STATE) {
      //初始化成功則調用logback的StaticLoggerBinder返回LoggerFactory
      case SUCCESSFUL_INITIALIZATION:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
      //沒有找到StaticLoggerBinder這個類  
      case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
      //版本不對,或者StaticLoggerBinder包路徑不對,或者其餘    
      case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
      //線程A已經快執行完performInitialization的時候,INITIALIZATION_STATE設置成SUCCESSFUL_INITIALIZATION,
      //線程B剛開始調用performInitialization,又將INITIALIZATION_STATE修改爲ONGOING_INITIALIZATION
      //線程A執行到switch代碼,就會跳轉到這裏,返回TEMP_FACTORY
      //TEMP_FACTORY如其名,調用它的getLogger方法獲取到SubstituteLogger。
      //使用SubstituteLogger的info、error等方法打印日誌的時候會判斷它的_delegate屬性是否爲空
      //一開始_delegate爲空,返回一個臨時的NOPLogger.NOP_LOGGER實例,該實例什麼也不作
      //後期performInitialization方法中會調用fixSubstitutedLoggers方法,從新設置_delegate爲正確的logger。
      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");
  }

下面看看執行初始化的方法 performInitialization:this

private final static void performInitialization() {
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
      //版本檢查
      versionSanityCheck();
    }
  }

private final static void bind() {
    try {
      //查找org/slf4j/impl/StaticLoggerBinder.class,可能存在多個
      Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
      reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
      // the next line does the binding
      //若是沒有StaticLoggerBinder class這裏調用便會異常
      StaticLoggerBinder.getSingleton();
      //設置狀態爲成功初始化
      INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
      //若是StaticLoggerBinder class個數大於1個,則進行上報實際綁定的類型
      reportActualBinding(staticLoggerBinderPathSet);
      //修復併發調用getILoggerFactory時出現狀態爲ONGOING_INITIALIZATION且獲取到的logger爲TEMP_FACTORY的問題
      //解決方法是設置每一個SubstituteLogger的_delegate爲正確的logger
      fixSubstitutedLoggers();
    } 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.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
        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);
    }
  }

執行完performInitialization方法後,狀態被設置成SUCCESSFUL_INITIALIZATION,而後執行StaticLoggerBinder.getSingleton().getLoggerFactory()。url

在這以前先看看StaticLoggerBinder的靜態代碼塊:spa

private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private static Object KEY = new Object();
static {
        //StaticLoggerBinder單例調用init方法
        SINGLETON.init();
    }
private boolean initialized = false;
//生成默認的LoggerContext
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
private StaticLoggerBinder() {
        defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
    }
public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }
void init() {
        try {
            try {
                //建立ContextInitializer,調用autoConfig解析配置文件
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                Util.report("Failed to auto configure default logger context", je);
            }
            // logback-292
            //打印logback內部error、warn信息
            //若是沒有配置StatusListener,則使用StatusPrinter的stdout方式打印
            //配置StatusListener的代碼實如今ContextInitializer#autoConfig()方法第一行
            //內部產生的error、warn等日誌在StaticLoggerBinder.defaultLoggerContext.sm中維護的。
            //好比以前autoConfig方法中解析xml配置時出現error或者warn日誌會在sm中存儲,而後在此處打印
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
            }
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            //initialized初始爲false,init成功設置成true
            initialized = true;
        } catch (Throwable t) {
            // we should never get here
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }

接下來分析ContextInitializer.autoConfig:線程

public void autoConfig() throws JoranException {
        //解析在環境變量中配置的StatusListener實現類,若是配置的話生成相應的實例,
        //調用其start方法,上報過去一段時間logback內部日誌
        StatusListenerConfigHelper.installIfAsked(loggerContext);
        //獲取logback配置文件URL
        URL url = findURLOfDefaultConfigurationFile(true);
        if (url != null) {
            //解析logback配置文件,核心
            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 {
                //設置默認的config,後期日誌直接輸出到控制檯
                BasicConfigurator basicConfigurator = new BasicConfigurator();
                basicConfigurator.setContext(loggerContext);
                basicConfigurator.configure(loggerContext);
            }
        }
    }

下面分析下ContextInitializer.configureByResource(url):

public void configureByResource(URL url) throws JoranException {
        if (url == null) {
            throw new IllegalArgumentException("URL argument cannot be null");
        }
        final String urlString = url.toString();
        //logback配置是groovy文件
        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")) {
            //logback配置文件是xml。咱們主要分析該狀況下的配置
            //首先建立一個配置器,設置好loggerContext後,根據url進行配置解析
            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");
        }
    }

JoranConfigurator.doConfigure(url)代碼以下:

public final void doConfigure(URL url) throws JoranException {
        InputStream in = null;
        try {
            //將該url添加到watch list中
            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被鎖住。若是應用須要從新加載的話會出現問題。
            //因此設置成false。
            urlConnection.setUseCaches(false);

            in = urlConnection.getInputStream();
            //將logback配置轉換成流進行解析
            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);
                }
            }
        }
    }
    

//將InputStream轉換成sax的InputSource對象,而後調用doConfigure進行解析
public final void doConfigure(InputStream inputStream) throws JoranException {
        doConfigure(new InputSource(inputStream));
    }



// this is the most inner form of doConfigure whereto other doConfigure
// methods ultimately delegate
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);
        //根據xml每一個element對應的event來進行解析
        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()) {
            //解析eventList
            interpreter.getEventPlayer().play(eventList);
        }
    }

以下是一個簡單的logback.xml demo:

<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://www.padual.com/java/logback.xsd">

    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/Users/apple/Documents/UNKONESERVER/warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滾 daily -->
            <fileNamePattern>/Users/apple/Documents/UNKONESERVER/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日誌最大的保存天數 -->
            <maxHistory>2</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.logback.test" level="debug"
            additivity="false">
        <appender-ref ref="WARN" />
    </logger>

    <root level="debug">
    </root>

</configuration>

轉換成SaxEvent list即是這個樣子:

eventList = {ArrayList@982}  size = 26
 0 = {StartEvent@999} "StartEvent(configuration)  [2,63]"
 1 = {StartEvent@1000} "StartEvent(appendername="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender")  [4,83]"
 2 = {StartEvent@1001} "StartEvent(file)  [5,15]"
 3 = {BodyEvent@1002} "BodyEvent(/Users/apple/Documents/UNKONESERVER/warn.log)5,61"
 4 = {EndEvent@1003} "  EndEvent(file)  [5,66]"
 5 = {StartEvent@1004} "StartEvent(rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy")  [6,83]"
 6 = {StartEvent@1005} "StartEvent(fileNamePattern)  [8,30]"
 7 = {BodyEvent@1006} "BodyEvent(/Users/apple/Documents/UNKONESERVER/warn.%d{yyyy-MM-dd}.log)8,91"
 8 = {EndEvent@1007} "  EndEvent(fileNamePattern)  [8,107]"
 9 = {StartEvent@1008} "StartEvent(maxHistory)  [10,25]"
 10 = {BodyEvent@1009} "BodyEvent(2)10,28"
 11 = {EndEvent@1010} "  EndEvent(maxHistory)  [10,39]"
 12 = {EndEvent@1011} "  EndEvent(rollingPolicy)  [11,25]"
 13 = {StartEvent@1012} "StartEvent(encoder)  [12,18]"
 14 = {StartEvent@1013} "StartEvent(pattern)  [13,22]"
 15 = {BodyEvent@1014} "BodyEvent(%msg%n)13,30"
 16 = {EndEvent@1015} "  EndEvent(pattern)  [13,38]"
 17 = {EndEvent@1016} "  EndEvent(encoder)  [14,19]"
 18 = {EndEvent@1017} "  EndEvent(appender)  [15,16]"
 19 = {StartEvent@1018} "StartEvent(loggername="com.logback.test" level="debug" additivity="false")  [18,32]"
 20 = {StartEvent@1019} "StartEvent(appender-refref="WARN")  [19,36]"
 21 = {EndEvent@1020} "  EndEvent(appender-ref)  [19,36]"
 22 = {EndEvent@1021} "  EndEvent(logger)  [20,14]"
 23 = {StartEvent@1022} "StartEvent(rootlevel="debug")  [23,25]"
 24 = {EndEvent@1023} "  EndEvent(root)  [25,12]"
 25 = {EndEvent@1024} "  EndEvent(configuration)  [27,17]"

對應26個event。 下面分析EventPlayer中play方法如何對event list進行遍歷處理。 很清晰的看到event分紅三種:StartEvent、BodyEvent、EndEvent。這個三種最終會調用對應action的begin、body、end方法:

public void play(List<SaxEvent> aSaxEventList) {
        eventList = aSaxEventList;
        SaxEvent se;
        for (currentIndex = 0; currentIndex < eventList.size(); currentIndex++) {
            se = eventList.get(currentIndex);

            if (se instanceof StartEvent) {
                interpreter.startElement((StartEvent) se);
                // invoke fireInPlay after startElement processing
                interpreter.getInterpretationContext().fireInPlay(se);
            }
            if (se instanceof BodyEvent) {
                // invoke fireInPlay before characters processing
                interpreter.getInterpretationContext().fireInPlay(se);
                interpreter.characters((BodyEvent) se);
            }
            if (se instanceof EndEvent) {
                // invoke fireInPlay before endElement processing
                interpreter.getInterpretationContext().fireInPlay(se);
                interpreter.endElement((EndEvent) se);
            }

        }
    }
  • satrtEvent
public void startElement(StartEvent se) {
        //設置event在xml中的locator信息,好比在多少行多少列
        setDocumentLocator(se.getLocator());
        //處理start event
        startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
    }
    
private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
        //首先選擇localName爲tagName,若是localName爲空則選擇qName
        String tagName = getTagName(localName, qName);
        //將這個startEvent的tagName推入elementPath棧中,後期處理對應的endEvent時,觸發endElement方法彈出該tagName
        elementPath.push(tagName);

        if (skip != null) {
            // every startElement pushes an action list
            pushEmptyActionList();
            return;
        }
        //根據elementPath查找相應的action list,
        //好比elementPath推入了兩個數據[configuration][appender],則會找到AppenderAction。
        //Interpreter中ruleStore並不維護全部elementPath對應的action,
        //好比[configuration][appender][file],在ruleStore中找不到對應的action。
        //logback提供了兩種隱式action:NestedComplexPropertyIA、NestedBasicPropertyIA,
        //1.[configuration][appender][file]對應的action是NestedBasicPropertyIA,
        //由於它屬於簡單的內嵌式action,file屬於appender的一個屬性,而file element沒有內嵌element,只有一個body。
        //2.[configuration][appender][rollingPolicy]對應的action是NestedComplexPropertyIA,
        //由於它屬於複雜的內嵌式action,rollingPolicy屬於appender的一個屬性,可是,rollingPolicy element還擁有本身的內嵌element
        List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
        if (applicableActionList != null) {
            //將action list推入actionlistStacl棧中,後續會用到
            actionListStack.add(applicableActionList);
            //遍歷applicableActionList,觸發每一個action的begin方法,正常applicableActionList中只有一個元素
            callBeginAction(applicableActionList, tagName, atts);
        } else {
            // every startElement pushes an action list
            pushEmptyActionList();
            String errMsg = "no applicable action for [" + tagName + "], current ElementPath  is [" + elementPath + "]";
            cai.addError(errMsg);
        }
    }
void callBeginAction(List<Action> applicableActionList, String tagName, Attributes atts) {
        if (applicableActionList == null) {
            return;
        }

        Iterator<Action> i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = (Action) i.next();
            // now let us invoke the action. We catch and report any eventual
            // exceptions
            try {
                //真正的處理start event的邏輯,即該element path對應action的begin方法
                action.begin(interpretationContext, tagName, atts);
            } catch (ActionException e) {
                skip = elementPath.duplicate();
                cai.addError("ActionException in Action for tag [" + tagName + "]", e);
            } catch (RuntimeException e) {
                skip = elementPath.duplicate();
                cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
            }
        }
    }
  • bodyEvent
public void characters(BodyEvent be) {
         
        setDocumentLocator(be.locator);
        //獲取body文本
        String body = be.getText();
        List<Action> applicableActionList = actionListStack.peek();

        if (body != null) {
            body = body.trim();
            if (body.length() > 0) {
                // System.out.println("calling body method with ["+body+ "]");
                callBodyAction(applicableActionList, body);
            }
        }
    }

private void callBodyAction(List<Action> applicableActionList, String body) {
        if (applicableActionList == null) {
            return;
        }
        Iterator<Action> i = applicableActionList.iterator();

        while (i.hasNext()) {
            Action action = i.next();
            try {
                //觸發該element path對應action的body方法
                action.body(interpretationContext, body);
            } catch (ActionException ae) {
                cai.addError("Exception in end() methd for action [" + action + "]", ae);
            }
        }
    }
  • endEvent
public void endElement(EndEvent endEvent) {
        setDocumentLocator(endEvent.locator);
        endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
    }

private void endElement(String namespaceURI, String localName, String qName) {
        // given that an action list is always pushed for every startElement, we
        // need
        // to always pop for every endElement
        List<Action> applicableActionList = (List<Action>) actionListStack.pop();

        if (skip != null) {
            if (skip.equals(elementPath)) {
                skip = null;
            }
        } else if (applicableActionList != EMPTY_LIST) {
            callEndAction(applicableActionList, getTagName(localName, qName));
        }

        // given that we always push, we must also pop the pattern
        //把在startElement方法中推入的元素彈出來
        elementPath.pop();
    }

private void callEndAction(List<Action> applicableActionList, String tagName) {
        if (applicableActionList == null) {
            return;
        }

        // logger.debug("About to call end actions on node: [" + localName + "]");
        Iterator<Action> i = applicableActionList.iterator();

        while (i.hasNext()) {
            Action action = i.next();
            // now let us invoke the end method of the action. We catch and report
            // any eventual exceptions
            try {
                //觸發當前element path對應action的end方法
                action.end(interpretationContext, tagName);
            } catch (ActionException ae) {
                // at this point endAction, there is no point in skipping children as
                // they have been already processed
                cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
            } catch (RuntimeException e) {
                // no point in setting skip
                cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
            }
        }
    }
相關文章
相關標籤/搜索