Java日誌框架之logback詳解

一:logback介紹和基本使用

1.框架組成

logback-core.jar (是其它兩個模塊的基礎模塊)
logback-classic.jar (是log4j的一個 改良版本。此外logback-classic完整實現SLF4J)
logback-access.jar (與Servlet容器集成提供通過Http來訪問日誌的功能)

2. 配置文件和日誌組件說明

1. 配置文件

<!-- 級別從高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日誌輸出規則 根據當前ROOT 級別,日誌輸出時,級別高於root默認的級別時 會輸出 -->
<!-- 以下 每個配置的 filter 是過濾掉輸出文件裏面,會出現高級別文件,依然出現低級別的日誌信息,通過filter 過濾只記錄本級別的日誌 -->
<!-- scan 當此屬性設置爲true時,配置文件如果發生改變,將會被重新加載,默認值爲true。 -->
<!-- scanPeriod 設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒。當scan爲true時,此屬性生效。默認的時間間隔爲1分鐘。 -->
<!-- debug 當此屬性設置爲true時,將打印出logback內部日誌信息,實時查看logback運行狀態。默認值爲false。 -->

<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 動態日誌級別 -->
    <jmxConfigurator />
    <!-- 定義日誌文件 輸出位置 -->
    <!-- <property name="log_dir" value="C:/test" />-->
    <property name="log_dir" value="/home/hadmin/data/logs/src" />
    <!-- 日誌最大的歷史 30天 -->
    <property name="maxHistory" value="30" />

    <!-- ConsoleAppender 控制檯輸出日誌 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                <!-- 設置日誌輸出格式 -->
                %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n
            </pattern>
        </encoder>
    </appender>

    <!-- ERROR級別日誌 -->
    <!-- 滾動記錄文件,先將日誌記錄到指定文件,當符合某個條件時,將日誌記錄到其他文件 RollingFileAppender -->
    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 過濾器,只記錄WARN級別的日誌 -->
        <!-- 果日誌級別等於配置級別,過濾器會根據onMath 和 onMismatch接收或拒絕日誌。 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 設置過濾級別 -->
            <level>ERROR</level>
            <!-- 用於配置符合過濾條件的操作 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 用於配置不符合過濾條件的操作 -->
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 最常用的滾動策略,它根據時間來制定滾動策略.既負責滾動也負責出發滾動 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日誌輸出位置 可相對、和絕對路徑 -->
            <fileNamePattern>
                ${log_dir}/error/%d{yyyy-MM-dd}/error-log.log
            </fileNamePattern>
            <!-- 可選節點,控制保留的歸檔文件的最大數量,超出數量就刪除舊文件假設設置每個月滾動,且<maxHistory>是6, 則只保存最近6個月的文件,刪除之前的舊文件。注意,刪除舊文件是,那些爲了歸檔而創建的目錄也會被刪除 -->
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>
                <!-- 設置日誌輸出格式 -->
                %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n
            </pattern>
        </encoder>
    </appender>

    <!-- WARN級別日誌 appender -->
    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 過濾器,只記錄WARN級別的日誌 -->
        <!-- 果日誌級別等於配置級別,過濾器會根據onMath 和 onMismatch接收或拒絕日誌。 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 設置過濾級別 -->
            <level>WARN</level>
            <!-- 用於配置符合過濾條件的操作 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 用於配置不符合過濾條件的操作 -->
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日誌輸出位置 可相對、和絕對路徑 -->
            <fileNamePattern>${log_dir}/warn/%d{yyyy-MM-dd}/warn-log.log</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- INFO級別日誌 appender -->
    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/info/%d{yyyy-MM-dd}/info-log.log</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- DEBUG級別日誌 appender -->
    <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/debug/%d{yyyy-MM-dd}/debug-log.log</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- TRACE級別日誌 appender -->
    <appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>TRACE</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/trace/%d{yyyy-MM-dd}/trace-log.log</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- root級別 DEBUG -->
    <root>
        <!-- 打印debug級別日誌及以上級別日誌 -->
        <level value="debug" />
        <!-- 控制檯輸出 -->
        <appender-ref ref="console" />
        <!-- 文件輸出 -->
        <appender-ref ref="ERROR" />
        <appender-ref ref="INFO" />
        <appender-ref ref="WARN" />
        <appender-ref ref="DEBUG" />
        <appender-ref ref="TRACE" />
    </root>
</configuration>

2.組件

1 .logger組件
Logger作爲日誌的記錄器,把它關聯到應用的對應的logContext上後,主要用於存放日誌信息,也可以定義日誌類型、級別
2. appender
指定日誌輸出的目的地:目的地可以是控制檯、文件、遠程套接字服務器、 數據庫、 JMS和遠程UNIX Syslog守護進程等。
3. Layout
負責把事件轉換成字符串,格式化的日誌信息的輸出
4. loggerContext(上下文)
實現了slf4j的ILoggerFactory,負責製造並存儲logger(以樹結構排列各logger).getLogger方法以 logger名稱爲參數。用同一名字調用LoggerFactory.getLogger 方法所得到的永遠都是同一個logger對象的引用。
5.有效級別及級別的繼承
Logger 可以被分配級別。級別包括:TRACE、DEBUG、INFO、WARN 和 ERROR,定義於ch.qos.logback.classic.Level類。如果 logger沒有被分配級別,那麼它將從有被分配級別的最近的祖先那裏繼承級別。root logger 默認級別是 DEBUG
打印方法的級別大於日誌的有效級別纔會記錄日誌

3.使用演示

1. 引用logback的pom座標:

 <!--日誌門面-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.25</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.創建配置文件

配置文件只支持以下幾種方式:優先級依次減小

方式一:在jvm系統變量中指定路徑(logback.configurationFile)
System.getProperty(“logback.configurationFile”)獲取路徑
方式二:classpath下配置:logback.groovy
方式三:classpath下配置logback-test.xml
方式四:classpath下配置logback.xml ss
默認路徑是classpath根路徑

配置文件內容:
點擊參考樣例

3.使用

Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("test log");

4.工程結構

在這裏插入圖片描述

二:logback原理

從日誌框架的入口開始

Logger logger = LoggerFactory.getLogger(Test.class);

跟進:LoggerFactory的getlogger方法

  public static Logger getLogger(Class<?> clazz) {
        // 內部會找到logback的實現
        // 初始化logback
        // 從logcontext獲取logger
        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;
    }

繼續跟進 getLogger(clazz.getName());
第一行: 找到logback並初始化logback,創建logcontext(實現了ILoggerFactory)
第二行:獲取loggger

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

繼續跟進getILoggerFactory();
2個作用:
初始化logback並更改狀態
根據初始化完成的狀態,返回對應的ILoggerFactory

public static ILoggerFactory getILoggerFactory() {
      
        //  加鎖初始化
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
      
        // 根據初始化狀態,返回對應的ILoggerFactory 
        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();  // 重點
      
        // 檢查jdk版本
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }

跟進: bind(),這個是真正執行初始化的地方
1;slf4j找到logback:關鍵在於 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
2:logback初始化:關鍵在於
StaticLoggerBinder.getSingleton();

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

1. slf4j找到logback的實現

跟進:staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
在這裏插入圖片描述
很明顯:通過classloader來搜索指定類(org/slf4j/impl/StaticLoggerBinder.class)的url,如果有多種實現,也會一起讀取到,所有返回Set,如果有多個實現,會打錯誤日誌(Class path contains multiple SLF4J bindings.)但是不影響初始化和後續使用
當執行到 StaticLoggerBinder.getSingleton();會加載StaticLoggerBinder,如果有多個,具體加載哪個看jvm的加載策略,每一個都有可能,一旦加載了一個,其它的就不會加載了

2. logback初始化

跟進 StaticLoggerBinder.getSingleton();看logback初始化

現來看看StaticLoggerBinder的結構:
在這裏插入圖片描述

當執行StaticLoggerBinder.getSingleton();的時候會先加載StaticLoggerBinder,執行類初始化,加載static成員,以上標識紅色的部分

先執行: private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();實例初始化
初始化實例成員變量:

 private boolean initialized = false;
 private LoggerContext defaultLoggerContext = new LoggerContext();
 private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();

defaultLoggerContext 創建完成

之後執行構造器:給defaultLoggerContext設置默認的名子default

private StaticLoggerBinder() {
        defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
    }

再執行:(重點)

 static {
        SINGLETON.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);
        }
    }

logcontext初始化工具類
new ContextInitializer(defaultLoggerContext),具體實現在autoConfig();

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

首先 從 URL url = findURLOfDefaultConfigurationFile(true);中讀取配置文件

如果有配置文件:解析流程如下

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

// 解析配置

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

以下三行代碼解析xml

  SaxEventRecorder recorder = new SaxEventRecorder(context);
  recorder.recordEvents(inputSource);
  doConfigure(recorder.saxEventList);

SaxEventRecorder繼承DefaultHandler,重寫了startDocument,endDocument,startElement,endElement,characters幾個重要方法

在 recorder.recordEvents(inputSource);中把xml配置文件解析成對應的SaxEvent
在解析過程中,通過SAX的方式解析,每一個元素解析過程中會調用SaxEventRecorder的startelement,characters,endElement方法,可以看到都是在封裝SaxEvent

public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {

        String tagName = getTagName(localName, qName);
        globalElementPath.push(tagName);
        ElementPath current = globalElementPath.duplicate();
        saxEventList.add(new StartEvent(current, namespaceURI, localName, qName, atts, getLocator()));
    }

public void endElement(String namespaceURI, String localName, String qName) {
        saxEventList.add(new EndEvent(namespaceURI, localName, qName, getLocator()));
        globalElementPath.pop();
    }

 public void characters(char[] ch, int start, int length) {
        String bodyStr = new String(ch, start, length);
        SaxEvent lastEvent = getLastEvent();
        if (lastEvent instanceof BodyEvent) {
            BodyEvent be = (BodyEvent) lastEvent;
            be.append(bodyStr);
        } else {
            // ignore space only text if the previous event is not a BodyEvent
            if (!isSpaceOnly(bodyStr)) {
                saxEventList.add(new BodyEvent(bodyStr, getLocator()));
            }
        }
    }

跟進 doConfigure(recorder.saxEventList);

public void doConfigure(final List<SaxEvent> eventList) throws JoranException {
        buildInterpreter();
        // disallow simultaneous configurations of the same context
        synchronized (context.getConfigurationLock()) {
            interpreter.getEventPlayer().play(eventList);
        }
    }

buildInterpreter();創建一個解析器解析SaxEvent

protected void buildInterpreter() {
        // 首先構造規則集合: 元素和Action的對應關係
       RuleStore rs = new SimpleRuleStore(context);
       addInstanceRules(rs);
       
       this.interpreter = new Interpreter(context, rs, initialElementPath());
       InterpretationContext interpretationContext = interpreter.getInterpretationContext();
       interpretationContext.setContext(context);
       addImplicitRules(interpreter);
       addDefaultNestedComponentRegistryRules(interpretationContext.getDefaultNestedComponentRegistry());
   }

構造RuleStore
在這裏插入圖片描述

// 解析SaxEvent
interpreter.getEventPlayer().play(eventList);
顯然在遍歷SaxEventlist,分爲三種StartEvent,BodyEvent,EndEvent
一旦匹配成功就從event中取出元素信息,然後從RuleStore中尋找對應的action,執行對應的action的satrt,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);
            }

        }
    }

例如:
以解析logger元素爲例,看如何從SaxEvent找到LoggerAction執行,創建logger,存到logggerContext下

開始解析到logger的StarEvent事件

private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
       // 獲取元素名稱
        String tagName = getTagName(localName, qName);
        // 元素名稱入棧,後面解析EndEvfent時出棧
        elementPath.push(tagName);

        if (skip != null) {
            // every startElement pushes an action list
            pushEmptyActionList();
            return;
        }

        List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
        if (applicableActionList != null) {
            actionListStack.add(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);
        }
    }

在 List applicableActionList = getApplicableActionList(elementPath, atts);中獲取event對應的Action
在這裏插入圖片描述
actionListStack.add(applicableActionList);將Action入棧後面會用到(解析endevent的時候會出棧)

// 調用action的方法
callBeginAction(applicableActionList, tagName, atts);

最終執行
action.begin(interpretationContext, tagName, atts);

即:
LoggerAction的begin方法

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;

        // 獲取logger元素的name屬性
        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);
            }
        }
       
       // 設置additivity屬性
        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);
    }

additivity作用在於 children-logger是否使用 rootLogger配置的appender進行輸出。
false:表示只用當前logger的appender-ref。
true:表示當前logger的appender-ref和rootLogger的appender-ref都有效

logger = loggerContext.getLogger(loggerName);這一行獲取logger
如果已經創建同名的直接返回,否則創建對象,創建的時候可能創建多個
例如com.test.TestLog爲名的logger元素會創建三個logger
com和com.test和com.test.TestLog爲名形成一個鏈狀結構(父子結構,root是所有logger的父logger)

ec.pushObject(logger);把創建的logger放到InterpretationContext中後面會用到

接着會解析到appender-ref元素的StartEvent
在這裏插入圖片描述
AppenderRefAction對應的begin方法

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

       // logger.debug("begin called");
       
       // 在解析logger的startevent時,在LoggerAction最後 ec.pushObject(logger);所以現在取出的就是logger
       Object o = ec.peekObject();

       // logger的確是AppenderAttachable的實現類,所以該分支代碼不執行
       if (!(o instanceof AppenderAttachable)) {
           String errMsg = "Could not find an AppenderAttachable at the top of execution stack. Near [" + tagName + "] line " + getLineNumber(ec);
           inError = true;
           addError(errMsg);
           return;
       }

       AppenderAttachable<E> appenderAttachable = (AppenderAttachable<E>) o;
      
      // 獲取當前appender-ref元素的ref屬性引用的appder名稱
       String appenderName = ec.subst(attributes.getValue(ActionConst.REF_ATTRIBUTE));

       if (OptionHelper.isEmpty(appenderName)) {
           // print a meaningful error message and return
           String errMsg = "Missing appender ref attribute in <appender-ref> tag.";
           inError = true;
           addError(errMsg);

           return;
       }

      // 從InterpretationContext中獲取key爲APPENDER_BAG的一個Map()
       HashMap<String, Appender<E>> appenderBag = (HashMap<String, Appender<E>>) ec.getObjectMap().get(ActionConst.APPENDER_BAG);
                    
       // 從APPENDER_BAG中獲取前面ref指定的apper,這些apppder是在解析appender的startevent時加進去的(AppenderAction的begin方法中)
       Appender<E> appender = (Appender<E>) appenderBag.get(appenderName);

       if (appender == null) {
           String msg = "Could not find an appender named [" + appenderName + "]. Did you define it below instead of above in the configuration file?";
           inError = true;
           addError(msg);
           addError("See " + CoreConstants.CODES_URL + "#appender_order for more details.");
           return;
       }
      
      // 將apper加入到logger中
       addInfo("Attaching appender named [" + appenderName + "] to " + appenderAttachable);
       appenderAttachable.addAppender(appender);
   }

從以下可以看出Logger是AppenderAttachable的實現類
在這裏插入圖片描述

接着解析到apper-ref的結束事件,endevent,對應了AppenderRefAction的end方法,什麼也沒做,接着解析到logger的endwvent,對應LoggerAction的end方法,從InterpretationContext中出棧並且刪除
在這裏插入圖片描述

到此一個logger元素解析完成

3. 獲取logger

在logback中就是從LoggerContext的loggerCache中取出指定名稱的logger

三:參考

  1. https://blog.csdn.net/qq_29580525/article/details/79422696
  2. https://www.cnblogs.com/xing901022/p/4149524.html
  3. https://my.oschina.net/u/2526336/blog/2885330?nocache=1542792093251
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章