5.modelHandler处理model

文章目录

  • 一、前言
  • 二、常用modelHandler解析model
    • configuration标签解析
    • property标签解析
    • conversionRule标签解析
    • appender标签解析
    • root标签解析
    • logger标签解析
  • 三、总结

一、前言

前面的文章主要介绍了logback将logback.xml中的节点路径解析成一个个的model, 最后在DefaultProcessor中使用modelHandler对model进行处理, 本节将介绍几个常用标签节点的modelHandler解析的详细过程

二、常用modelHandler解析model

configuration标签解析

configuration节点对应的是ConfigurationModel, 使用ConfigurationModelHandlerFull解析, ConfigurationModelHandlerFull继承了ConfigurationModelHandler类, 我们先看一下ConfigurationModelHandler

public class ConfigurationModelHandler extends ModelHandlerBase {
    ConfigurationModel configurationModel = (ConfigurationModel) model;

        // See LOGBACK-527 (the system property is looked up first). Thus, it overrides
        // the equivalent property in the config file. This reversal of scope priority
        // is justified by the use case: the admin trying to chase rogue config file
        // 1.debug属性系统; 属性中获取 logback.debug
        String debugAttrib = OptionHelper.getSystemProperty(DEBUG_SYSTEM_PROPERTY_KEY, null);
        if (debugAttrib == null) {
            // configuration的debug属性
            debugAttrib = mic.subst(configurationModel.getDebugStr());
        }
        
        // 1.logback.debug不是空 2并且不是非false 3并且不是null字符串
        if (!(OptionHelper.isNullOrEmptyOrAllSpaces(debugAttrib) || debugAttrib.equalsIgnoreCase(FALSE.toString())
                || debugAttrib.equalsIgnoreCase(NULL_STR))) {
            // 添加OnConsoleStatusListener监听器, 并打印状态
            StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
        }
        // 查询scan属性, 非false的话打印日志
        processScanAttrib(mic, configurationModel);

        LoggerContext lc = (LoggerContext) context;
        // packagingData属性, 用于打印打包信息, 默认是false
        boolean packagingData = OptionHelper.toBoolean(mic.subst(configurationModel.getPackagingDataStr()),
                LoggerContext.DEFAULT_PACKAGING_DATA);
        lc.setPackagingDataEnabled(packagingData);

        ContextUtil contextUtil = new ContextUtil(context);
        contextUtil.addGroovyPackages(lc.getFrameworkPackages());
}

方法小结

  1. 这个方法解析了configuration标签的debug属性和scan属性和packagingData属性
  2. debug属性为true的话, 将会添加一个OnConsoleStatusListener监听器, 打印logback日志框架启动时的一些日志
  • 可以使用-Dlogback.debug设置系统级参数来控制, 它的优先级比configuration标签的debug属性还要高
  1. scan属性, 如果开启的话, 将会监听日志配置文件, 可以用来动态修改日志等级
  2. packagingData属性, 如果为true, 将会打印打包相关的日志

ConfigurationModelHandlerFull

public class ConfigurationModelHandlerFull extends ConfigurationModelHandler {
    
    protected void postProcessScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) {
        // scan属性
        String scanStr = mic.subst(configurationModel.getScanStr());
        // scanPeriod属性
        String scanPeriodStr = mic.subst(configurationModel.getScanPeriodStr());
        detachedPostProcess(scanStr, scanPeriodStr);
    }
    
    public void detachedPostProcess(String scanStr, String scanPeriodStr) {
        // scan 非false, 一般可以设置scan为true
        if (!OptionHelper.isNullOrEmptyOrAllSpaces(scanStr) && !"false".equalsIgnoreCase(scanStr)) {
            // 默认是ScheduledThreadPoolExecutor类, 核心线程是4个
            ScheduledExecutorService scheduledExecutorService = context.getScheduledExecutorService();
            // 1.校验热加载文件;动态监听的配置文件, 默认是监听logback.xml文件, 所以默认是true
            // 可以使用configuration/propertiesConfigurator节点来指定热加载的文件
            boolean watchPredicateFulfilled = ConfigurationWatchListUtil.watchPredicateFulfilled(context);
            if (!watchPredicateFulfilled) {
                addWarn(FAILED_WATCH_PREDICATE_MESSAGE_1);
                addWarn(FAILED_WATCH_PREDICATE_MESSAGE_2);
                return;
            }
            // 2.创建周期任务, 可以用来动态修改日志等级
            ReconfigureOnChangeTask rocTask = new ReconfigureOnChangeTask();
            rocTask.setContext(context);

            addInfo("Registering a new ReconfigureOnChangeTask " + rocTask);
            // 3.发布热加载对象注册事件
    context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectorRegisteredEvent(rocTask));

            // 扫描间隔 scanPeriod 默认是1min; 格式 '数字 单位'
            Duration duration = getDurationOfScanPeriodAttribute(scanPeriodStr, SCAN_PERIOD_DEFAULT);

            addInfo("Will scan for changes in [" + ConfigurationWatchListUtil.getConfigurationWatchList(context) + "] ");
            addInfo("Setting ReconfigureOnChangeTask scanning period to " + duration);

            // 4.执行周期任务
            ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(rocTask, duration.getMilliseconds(), duration.getMilliseconds(),
                            TimeUnit.MILLISECONDS);
            rocTask.setScheduredFuture(scheduledFuture);
            context.addScheduledFuture(scheduledFuture);
        }

    }
}

方法小结

  1. configuration标签的scan=true用来开启热加载扫描文件
  2. 默认热加载的文件是logback.xml, 可以通过configuration/propertiesConfigurator标签添加热加载的文件, 只支持添加properties后缀和网络地址的文件
  3. 使用4个核心的周期执行器执行扫描任务, 任务对象是ReconfigureOnChangeTask
  4. 默认是一分钟扫描一次, 可以通过configuration标签的scanPeriod属性置顶执行周期, 例如 scanPeriod="5 minute", 单位可选为milli(或millisecond)、second(或seconde)、minute、hour、day

ReconfigureOnChangeTask热加载配置文件

public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {
    public void run() {
        // 1.发布热加载运行事件
        context.fireConfigurationEvent(newConfigurationChangeDetectorRunningEvent(this));
        // 2.获取热加载需要扫描的文件们
        ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
        if (configurationWatchList == null) {
            addWarn("Empty ConfigurationWatchList in context");
            return;
        }
        if (configurationWatchList.emptyWatchLists()) {
            addInfo("Both watch lists are empty. Disabling ");
            return;
        }
        // 3.获取热加载的本地文件
        File changedFile = configurationWatchList.changeDetectedInFile();
        // 4.获取热加载的网络文件
        URL changedURL = configurationWatchList.changeDetectedInURL();

        if (changedFile == null && changedURL == null) {
            return;
        }
        // 5.发布文件变更事件
        context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this));
        addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);

        // 6.处理本地文件变更
        if (changedFile != null) {
            changeInFile(changedFile, configurationWatchList);
        }
        // 7.处理网络文件变更
        if (changedURL != null) {
            changeInURL(changedURL);
        }
    }
}

方法小结

  1. 发布热加载运行事件
  2. 获取热加载需要扫描的文件们
  3. 获取热加载的本地文件
  4. 获取热加载的网络文件
  5. 发布文件变更事件
  6. 处理本地文件变更
  7. 处理网络文件变更

这里只关注本地文件的变更

private void changeInFile(File changedFile, ConfigurationWatchList configurationWatchList) {
    // properties结尾的文件
    if (changedFile.getName().endsWith(PROPERTIES_FILE_EXTENSION)) {
        runPropertiesConfigurator(changedFile);
        return;
    }

    // 下面的配置一般就是logback.xml修改了
    // ======== fuller processing below
    addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
    // 取消正在进行的任务
    cancelFutureInvocationsOfThisTaskInstance();
    // 获取主配置文件, 一般是logback.xml
    URL mainConfigurationURL = configurationWatchList.getMainURL();

    LoggerContext lc = (LoggerContext) context;
    // 主配置文件是xml文件才处理
    if (mainConfigurationURL.toString().endsWith("xml")) {
        performXMLConfiguration(lc, mainConfigurationURL);
    } else if (mainConfigurationURL.toString().endsWith("groovy")) {
        addError("Groovy configuration disabled due to Java 9 compilation issues.");
    }
}
// properties结尾的文件
private void runPropertiesConfigurator(Object changedObject) {
    addInfo("Will run PropertyConfigurator on "+changedObject);
    PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator();
    propertiesConfigurator.setContext(context);
    try {
        // 磁盘文件
        if (changedObject instanceof File) {
            File changedFile = (File) changedObject;
            propertiesConfigurator.doConfigure(changedFile);
        }
        // 网络文件
        else if(changedObject instanceof URL) {
            URL changedURL = (URL) changedObject;
            propertiesConfigurator.doConfigure(changedURL);
        }
        // 发布修改成功事件
        context.fireConfigurationEvent(newPartialConfigurationEndedSuccessfullyEvent(this));
    } catch (JoranException e) {
        addError("Failed to reload "+ changedObject);
    }
}

这里将本地文件分为.properties和.xml为后缀两种; .properties的文件使用PropertiesConfigurator来处理, 只支持修改日志级别, .xml文件就支持整个日志文件的变更了

这里关注下.properties文件的变更, 下面是PropertiesConfigurator中的核心代码

PropertiesConfigurator

void doConfigure(Properties properties) {
    // 非logback开头的属性们
    Map<String, String> variablesMap = extractVariablesMap(properties);
    // logback开头的属性们
    Map<String, String> instructionMap = extractLogbackInstructionMap(properties);

    this.variableSubstitutionsHelper = new VariableSubstitutionsHelper(context, variablesMap);
    // 设置日志等级
    configureLoggers(instructionMap);
    // root日志等级
    configureRootLogger(instructionMap);
}

void configureLoggers(Map<String, String> instructionMap) {
    for (String key : instructionMap.keySet()) {
        // logback.logger. 格式开头的属性
        if (key.startsWith(LOGBACK_LOGGER_PREFIX)) {
            // 取logback.logger.后面的部分
            String loggerName = key.substring(LOGBACK_LOGGER_PREFIX_LENGTH);
            // 取值
            String value = subst(instructionMap.get(key));
            // 设置日志等级
            setLevel(loggerName, value);
        }
    }
}

void configureRootLogger(Map<String, String> instructionMap) {
    // logback.root开头的属性
    String val = subst(instructionMap.get(LOGBACK_ROOT_LOGGER_PREFIX));
    if (val != null) {
        // root的日志级别
        setLevel(org.slf4j.Logger.ROOT_LOGGER_NAME, val);
    }
}

private void setLevel(String loggerName, String levelStr) {
    Logger logger = getLoggerContext().getLogger(loggerName);
    // 等级levelStr为INHERITED 或者 null字符串
    if (JoranConstants.INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
        // root
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(loggerName)) {
            // 报错
            addError(ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
        } else {
            addInfo("Setting level of logger [" + loggerName + "] to null, i.e. INHERITED");
            logger.setLevel(null);
        }
    } else {
        // 修改日志的级别; 默认是debug; 可选有ALL,TRACE,DEBUG,INFO,WARN,ERROR,OFF
        Level level = Level.toLevel(levelStr);
        logger.setLevel(level);
    }
}

方法小结

  1. properties文件中只可以配置日志的级别, 包括root日志和普通日志, 其中root日志的配置以logback.root.开头, 其它类的日志以logback.logger.开头
  2. 可以修改的等级有ALL,TRACE,DEBUG,INFO,WARN,ERROR,OFF
  3. 如果是配置的变量, 例如{logger.level}这种格式, 可以直接从系统属性中获取哦, 然后maven的pom.xml文件可以配置系统属性, 可以配合使用, 或者设置到全局变量中

关于configuration/propertiesConfigurator标签的解析, 我们简单看一下

PropertiesConfiguratorModelHandler

public void detachedHandle(ContextAwarePropertyContainer capc, Model model) throws ModelHandlerException {

        PropertiesConfiguratorModel propertyConfiguratorModel = (PropertiesConfiguratorModel) model;

        this.optional = OptionHelper.toBoolean(propertyConfiguratorModel.getOptional(), false);
        // resource、url、file三选一
        if (!checkAttributes(propertyConfiguratorModel)) {
            inError = true;
            return;
        }

        InputStream in = getInputStream(capc, propertyConfiguratorModel);
        if (in == null) {
            inError = true;
            return;
        }

        addInfo("Reading configuration from [" + getAttribureInUse() + "]");

        PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator();
        propertiesConfigurator.setContext(capc.getContext());
        try {
            // 根据配置的文件修改日志级别
            propertiesConfigurator.doConfigure(in);
        } catch (JoranException e) {
            addError("Could not configure from " + getAttribureInUse());
            throw new ModelHandlerException(e);
        }

    }

    protected InputStream getInputStream(ContextAwarePropertyContainer capc, ResourceModel resourceModel) {
        URL inputURL = getInputURL(capc, resourceModel);
        if (inputURL == null)
            return null;

        // 添加到动态监听列表
        ConfigurationWatchListUtil.addToWatchList(context, inputURL, CREATE_CWL_IF_NOT_ALREADY_CREATED);
        return openURL(inputURL);
    }

方法小结

  1. 可以使用propertiesConfigurator标签的resource、url、file三选一指定热加载文件
  • 例如, 使用classpath下的application-log.properties来动态设置日志级别
  1. 将配置的文件直接添加都动态监听列表
  2. 立刻调用上面提到的PropertiesConfigurator动态更新一次

一般的关于热加载的可以配置如下

<configuration scan="true" scanPeriod="5 minute">
    <propertiesConfigurator resource="reConfiglog.properties"/>
configuration>

// reConfigLog的内容可以如下
# 指定per.qiao.App这个类的日志级别(也可以per.qiao指定到包)
logback.logger.per.qiao.App=DEBUG
# 指定root日志的日志级别
logback.root=DEBUG

property标签解析

property标签对应的是PropertyAction->PropertyModel->PropertyModelHandler

variable标签和property标签是等同的

PropertyModelHandler类解析

public class PropertyModelHandler extends ModelHandlerBase {
    @Override
    public void handle(ModelInterpretationContext mic, Model model) {

        PropertyModel propertyModel = (PropertyModel) model;
        PropertyModelHandlerHelper propertyModelHandlerHelper = new PropertyModelHandlerHelper(this);
        propertyModelHandlerHelper.setContext(context);
        // 核心方法
        propertyModelHandlerHelper.handlePropertyModel(mic, propertyModel);
    }
}

// PropertyModelHandlerHelper#handlePropertyModel
public void handlePropertyModel(ContextAwarePropertyContainer capc, PropertyModel propertyModel) {
    // scope属性, 可选项: LOCAL, CONTEXT, SYSTEM, 默认是LOCAL
    ActionUtil.Scope scope = ActionUtil.stringToScope(propertyModel.getScopeStr());

    // 仅file不为空
    if (checkFileAttributeSanity(propertyModel)) {
        String file = propertyModel.getFile();
        file = capc.subst(file);
        try (FileInputStream istream = new FileInputStream(file)) {
            // 从properties中获取数据
            PropertyModelHandlerHelper.loadAndSetProperties(capc, istream, scope);
        } catch (FileNotFoundException e) {
            // ...
        }
    }
    // 仅resource不为空
    else if (checkResourceAttributeSanity(propertyModel)) {
        String resource = propertyModel.getResource();
        resource = capc.subst(resource);
        URL resourceURL = Loader.getResourceBySelfClassLoader(resource);
        if (resourceURL == null) {
            addError("Could not find resource [" + resource + "].");
        } else {
            try (InputStream istream = resourceURL.openStream();) {
                PropertyModelHandlerHelper.loadAndSetProperties(capc, istream, scope);
            } catch (IOException e) {
                addError("Could not read resource file [" + resource + "].", e);
            }
        }
    }
    // name和value不为空, 并且file和resource为空
    else if (checkValueNameAttributesSanity(propertyModel)) {
        String value = propertyModel.getValue();
        value = value.trim();
        value = capc.subst(value);
        ActionUtil.setProperty(capc, propertyModel.getName(), value, scope);
    } else {
        addError(ModelConstants.INVALID_ATTRIBUTES);
    }
}

方法小结

  1. properties标签支持指定磁盘上properties后缀的文件、classpath下properties后缀的文件、以及指定name,value的形式(三选一)
  2. 可以使用scope指定属性存放在哪; 可选值有
  • LOCAL, model解析上下文,仅model解析作用范围内有效, 默认是这个
  • CONTEXT, 整个日志范围内有效
  • SYSTEM, 直接使用System.setProperty 添加到环境上下文

conversionRule标签解析

听说你想给我点颜色看看, show me some color see see!!!

好的, logback也支持在控制台打印带点颜色的日志

ConversionRuleAction->ConversionRuleModel->ConversionRuleModelHandler

public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {

    ConversionRuleModel conversionRuleModel = (ConversionRuleModel) model;
    String converterClass = conversionRuleModel.getClassName();

    if (OptionHelper.isNullOrEmptyOrAllSpaces(converterClass)) {
        addWarn("Missing className. This should have been caught earlier.");
        inError = true;
        return;
    } else {
        // 支持从import中获取
        converterClass = mic.getImport(converterClass);
    }
    // 转换的那个词, 比如%d{yyyy-MM-dd HH:mm:ss.SSS}中的d, 请设置唯一哦, 否则就是覆盖了
    String conversionWord = conversionRuleModel.getConversionWord();
    try {
        // 解析pattern 规则的Converter们
        Map<String, Supplier<DynamicConverter>> ruleRegistry = (Map<String, Supplier<DynamicConverter>>) context
                .getObject(CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS);
        if (ruleRegistry == null) {
            ruleRegistry = new HashMap<>();
            // 放到LoggerContext的objectMap中存起来
            context.putObject(CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS, ruleRegistry);
        }
        addInfo("registering conversion word " + conversionWord + " with class [" + converterClass + "]");
        // 绑定conversionWord和converterClass
        ConverterSupplierByClassName converterSupplierByClassName = new ConverterSupplierByClassName(conversionWord, converterClass);
        converterSupplierByClassName.setContext(getContext());
        // 放到map中
        ruleRegistry.put(conversionWord, converterSupplierByClassName);
    } catch (Exception oops) {
        inError = true;
        String errorMsg = "Could not add conversion rule to PatternLayout.";
        addError(errorMsg);
    }
}

举个栗子:

<conversionRule conversionWord="clr" converterClass="ch.qos.logback.core.pattern.color.RedCompositeConverter"/>

// 然后可以在<pattern>标签中这么用了  %clr(%d{yyyy-MM-dd HH:mm:ss.SSS})

方法小结

​ 将conversionRule标签中的conversionWord和converterClass组成map中的key和value添加到LoggerContext上下文中; 其中map的key为CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS

appender标签解析

AppenderAction->AppenderModel->AppenderModelHandler

AppenderModelHandler

public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
    this.appender = null;
    this.inError = false;

    AppenderModel appenderModel = (AppenderModel) model;

    String appenderName = mic.subst(appenderModel.getName());
    // 判断当前appender是否被appender-ref引用, 没有被引用直接打个日志然后退出
    if (!mic.hasDependers(appenderName)) {
        addWarn("Appender named [" + appenderName + "] not referenced. Skipping further processing.");
        skipped = true;
        appenderModel.markAsSkipped();
        return;
    }

    addInfo("Processing appender named [" + appenderName + "]");

    String originalClassName = appenderModel.getClassName();
    // 支持import标签引入
    String className = mic.getImport(originalClassName);

    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);
        appender.setName(appenderName);
        // 压入栈
        mic.pushObject(appender);
    } catch (Exception oops) {
        inError = true;
        addError("Could not create an Appender of type [" + className + "].", oops);
        throw new ModelHandlerException(oops);
    }
}

public void postHandle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
    if (inError || skipped) {
        return;
    }
    // 标记为运行
    if (appender instanceof LifeCycle) {
        ((LifeCycle) appender).start();
    }
    // 标记当前appender被ref引用了
    mic.markStartOfNamedDependee(appender.getName());
	// 查询栈顶数据
    Object o = mic.peekObject();

    // 添加到model解析上下文中, key为JoranConstants.APPENDER_BAG
    @SuppressWarnings("unchecked")
    Map<String, Appender<E>> appenderBag = (Map<String, Appender<E>>) mic.getObjectMap()
            .get(JoranConstants.APPENDER_BAG);
    appenderBag.put(appender.getName(), appender);

    if (o != appender) {
        addWarn("The object at the of the stack is not the appender named [" + appender.getName()
                + "] pushed earlier.");
    } else {
        // 弹出栈顶
        mic.popObject();
    }

}

方法小结

  1. appender标签的对象必须被appender-ref引用才会被正常解析, 否则只会提示 Appender named [xxx] not referenced. Skipping further processing.
  2. 将appender对象放到model解析上下文的JoranConstants.APPENDER_BAG对应的map中

root标签解析

RootLoggerAction->RootLoggerModel->RootLoggerModelHandler

LoggerModelHandler

public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
        inError = false;
    RootLoggerModel rootLoggerModel = (RootLoggerModel) model;

    LoggerContext loggerContext = (LoggerContext) this.context;
    // root节点日志
    root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
    // 等级, 可以是占位符
    String levelStr = mic.subst(rootLoggerModel.getLevel());
    if (!OptionHelper.isNullOrEmptyOrAllSpaces(levelStr)) {
        Level level = Level.toLevel(levelStr);
        addInfo("Setting level of ROOT logger to " + level);
        root.setLevel(level);
    }
    mic.pushObject(root);
}

这个方法没什么, 就是设置了一下root日志的级别

  1. 说明一下, root日志的创建不是通过loggerContext.getLogger创建的, 在logback框架被spi引入时会创建日志上下文LoggerContext, 在LoggerContext的构造器中创建了root的Logger对象

  2. root日志的默认级别是debug, 并且不支持设置为NULL和INHERITED

  3. root日志是所有其它包或者类日志的顶层, 当我们没有额外设置某个类或者包的logger对象时, 默认都使用的root日志

logger标签解析

LoggerAction->LoggerModel->LoggerModelHandler

LoggerModelHandler

public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
    inError = false;

    LoggerModel loggerModel = (LoggerModel) model;
	// 解析名称
    String finalLoggerName = mic.subst(loggerModel.getName());

    LoggerContext loggerContext = (LoggerContext) this.context;
	// 获取到对应的日志对象
    logger = loggerContext.getLogger(finalLoggerName);
	// 解析日志等级
    String levelStr = mic.subst(loggerModel.getLevel());
    // 不为空
    if (!OptionHelper.isNullOrEmptyOrAllSpaces(levelStr)) {
        // INHERITED或者null字符串
        if (JoranConstants.INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
            // root等级不支持INHERITED和null字符串
            if(Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(finalLoggerName)) {
                addError(ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
            } else {
                // 其它类的日志直接置为空
                addInfo("Setting level of logger [" + finalLoggerName + "] to null, i.e. INHERITED");
                logger.setLevel(null);
            }
        } else {
            // 设置日志具体的等级
            Level level = Level.toLevel(levelStr);
            addInfo("Setting level of logger [" + finalLoggerName + "] to " + level);
            logger.setLevel(level);
        }
    }
	// additivity属性
    String additivityStr = mic.subst(loggerModel.getAdditivity());
    // 不为空
    if (!OptionHelper.isNullOrEmptyOrAllSpaces(additivityStr)) {
        // 转boolean, 默认是true
        boolean additive = OptionHelper.toBoolean(additivityStr, true);
        addInfo("Setting additivity of logger [" + finalLoggerName + "] to " + additive);
        // 设置additive, 如果
        logger.setAdditive(additive);
    }

    mic.pushObject(logger);
}

方法小结

  1. 根据日志名称获取对应的Logger对象, 这里会将类或者包的每一个路径节点都创建对应的Logger对象, 什么意思呢?? 比如下面
<logger name="per.qiao.App" level="debug" additivity="ture">...logger>

这种不仅App这个类的日志级别是debug, per,per.qiao这两个级别的包的日志级别都是debug, 好奇的同学可能就问了, 那么我还有个per.qiao.App2也设置了level=“info”, 那么per和per.qiao这两个包的日志级别是啥呢?? 它们还是debug, 只会设置一次

  1. 关于additive属性, 默认是true, 也就是如果当前logger标签没有配置appender的话会继承使用root节点的appender(root节点是所有logger节点的父类)

三、总结

本节就介绍了几个核心日志节点的modelHandler, 下一节讲重点介绍下appender和logger(root)相关的标签

  1. configuration标签是logback.xml配置文件的顶层标签, 支持四个属性, 如下
  • debug:true 添加OnConsoleStatusListener用来监听日志框架本身的运行状态, 可以使用-Dlogback.debug设置系统级参数来控制, 它的优先级比configuration标签的更高
  • scan:true 配置文件修改后自动生效, 会生成定时任务;默认监听的是logback.xml文件, 可以修改所有配置, 也可以使用configuration/propertiesConfigurator标签指定热更新的properties文件
  • scanPeriod: 刷新频率, 默认是1min一次 格式: 数字 单位 这里单位可选为milli(或millisecond)、second(或seconde)、minute、hour、day
  • packagingData:true, 用于打印打包信息
  1. properties标签支持指定磁盘上properties后缀的文件、classpath下properties后缀的文件、以及指定name,value的形式(三选一), 存放的作用域有三个, 分别如下
  • LOCAL: model解析上下文, 它仅在model相关的处理下有效
  • CONTEXT: 整个日志框架中有效, 它放在LoggerContext中
  • SYSTEM: 直接使用System.setProperty 添加到环境上下文
  1. conversionRule标签用于注入一个DynamicConverter到LoggerContext中, 它可以解析pattern中的关键字, 可以给打印的控制台日志加点颜色
  2. appender标签比较重要, 它将指定的appender类放到了LoggerContext的 objectMap中, 其中key为JoranConstants.APPENDER_BAG
  • 有一点需要注意, 如果某个appender标签没有被其它appender-ref引用, 那么它不会被解析
  1. root标签只有一个level属性, 它是所有日志的顶层父节点, 默认的level是debug, 不支持null和INHERITED, 它与LoggerContxt同生命周期
  2. logger标签用来隔离不同包或者类的日志级别, appender等
  • 所有的logger共享一个父节点root
  • 一个类指定了logger标签, 那么它本类和它所在的父级包都会共享这一个日志级别
  • 如果additivity为true, 那么如果logger节点没有配置appender, 那么它会共享root节点的appender

你可能感兴趣的:(slf4j,slf4j,logback)