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