本文以 logback 为例,介绍 Spring Boot 中如何进行日志系统的配置,假定读者已经有了基本的知识储备,如:
总的配置流程包含以下两个阶段:
当 Spring Boot 启动时,日志系统的初始化是优先级最高的任务之一(早于 ApplicationContext 的创建),日志系统会调用 ch.qos.logback.classic.spi.Configurator.doConfigure 方法进行配置。
Spring Boot 的 LoggingApplicationListener 这个监听器负责在应用启动的早期阶段初始化日志系统,具体到 Logback,则调用 org.springframework.boot.logging.logback.LogbackLoggingSystem#initialize 进行配置。
Spring Boot 的不同版本处理方式略微不同,下面主要阐述一下两个版本:
Spring Boot 1.5.11.RELEASE
初始化阶段
核心的实现类及方法是 ch.qos.logback.classic.util.ContextInitializer#autoConfig
public void autoConfig() throws JoranException { StatusListenerConfigHelper.installIfAsked(this.loggerContext); URL url = this.findURLOfDefaultConfigurationFile(true); if (url != null) { this.configureByResource(url); } else { Configurator c = (Configurator)EnvUtil.loadFromServiceLoader(Configurator.class); if (c != null) { try { c.setContext(this.loggerContext); c.configure(this.loggerContext); } catch (Exception var4) { Exception e = var4; 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(this.loggerContext); basicConfigurator.configure(this.loggerContext); } } }
复制代码
如果用户定义配置文件,会走到 this.configureByResource(url)的分支,最终会调用 JoranConfigurator.doConfigure,配置文件的优先级如下:
logback.configurationFile 指定的文件
logback.groovy
logback-test.xml
logback.xml
如果用户没有定义配置文件,则根据 SPI 机制获取 ch.qos.logback.classic.spi.Configurator 的所有实现类,并调用第一个实现类的 doConfigure 方法
如果没有任何 Configurator 的实现类,则调用 BasicConfigurator 的 doConfigure 方法
Spring Boot 干预阶段
如果项目中配置 logging.config,则使用 org.springframework.boot.logging.AbstractLoggingSystem#initializeWithSpecificConfig
如果项目中没有配置 logging.config,则使用 org.springframework.boot.logging.AbstractLoggingSystem#initializeWithConventions,逻辑如下:
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) { String config = this.getSelfInitializationConfig(); if (config != null && logFile == null) { this.reinitialize(initializationContext); } else { if (config == null) { config = this.getSpringInitializationConfig(); } if (config != null) { this.loadConfiguration(initializationContext, config, logFile); } else { this.loadDefaults(initializationContext, logFile); } } }
复制代码
如果配置文件存在,则使用配置文件进行初始化,初始化过程中会调用 ch.qos.logback.classic.LoggerContext#reset 重置所有 logger 的 level 和 appender
首先获取标准的配置文件,获取路径如下,该方法可以被重载
org.springframework.boot.logging.logback.LogbackLoggingSystem#getStandardConfigLocations
protected String[] getStandardConfigLocations() { return new String[]{"logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml"};}
复制代码
如果标准的配置文件存在,会走到 reinitialize 逻辑,清除之前的 LoggerContext,并根据配置文件的内容重新进行配置。
如果标准的配置文件不存在,则查找类路径下是否存在 Spring 的配置文件,默认是把标准的配置文件名结尾加上-spring,如:
"logback-test.groovy" → "logback-test-spring.groovy"
"logback-test.xml" → "logback-test-spring.xm"
"logback.groovy" → "logback-spring.groovy"
"logback.xml" → "logback-spring.xml"
如果以上两种配置文件都不存在,则使用默认的配置方式,见 org.springframework.boot.logging.logback.DefaultLogbackConfiguration#apply
public void apply(LogbackConfigurator config) { synchronized(config.getConfigurationLock()) { this.base(config); Appender<ILoggingEvent> consoleAppender = this.consoleAppender(config); if (this.logFile != null) { Appender<ILoggingEvent> fileAppender = this.fileAppender(config, this.logFile.toString()); config.root(Level.INFO, new Appender[]{consoleAppender, fileAppender}); } else { config.root(Level.INFO, new Appender[]{consoleAppender}); } } }
复制代码
Spring Boot 3.2.1
初始化阶段
Spring Boot 3.2.1 改写了 ch.qos.logback.classic.util.ContextInitializer#autoConfig
public void autoConfig(ClassLoader classLoader) throws JoranException { classLoader = Loader.systemClassloaderIfNull(classLoader); String versionStr = EnvUtil.logbackVersion(); if (versionStr == null) { versionStr = "?"; }
this.loggerContext.getStatusManager().add(new InfoStatus("This is logback-classic version " + versionStr, this.loggerContext)); StatusListenerConfigHelper.installIfAsked(this.loggerContext); List<Configurator> configuratorList = ClassicEnvUtil.loadFromServiceLoader(Configurator.class, classLoader); configuratorList.sort(this.rankComparator); if (configuratorList.isEmpty()) { this.contextAware.addInfo("No custom configurators were discovered as a service."); } else { this.printConfiguratorOrder(configuratorList); }
Iterator var4 = configuratorList.iterator();
Configurator c; do { if (!var4.hasNext()) { String[] var9 = this.INTERNAL_CONFIGURATOR_CLASSNAME_LIST; int var10 = var9.length;
for(int var6 = 0; var6 < var10; ++var6) { String configuratorClassName = var9[var6]; this.contextAware.addInfo("Trying to configure with " + configuratorClassName); Configurator c = this.instantiateConfiguratorByClassName(configuratorClassName, classLoader); if (c != null && this.invokeConfigure(c) == ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY) { return; } }
return; }
c = (Configurator)var4.next(); } while(this.invokeConfigure(c) != ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY);
}
复制代码
变化点有:
List<Configurator> configuratorList = ClassicEnvUtil.loadFromServiceLoader(Configurator.class, classLoader);configuratorList.sort(this.rankComparator);
复制代码
支持 Configurator 的实现类使用 @ConfiguratorRank 注解,如:
@ConfiguratorRank(-10)public class BasicConfigurator extends ContextAwareBase implements Configurator {}
复制代码
NEUTRAL
中性状态,不改变默认行为。是否调用下一个配置器取决于框架的默认逻辑(可能继续调用或停止)。
INVOKE_NEXT_IF_ANY
明确要求调用下一个配置器(如果存在)。即使当前配置器已执行某些操作,仍继续链式调用。
DO_NOT_INVOKE_NEXT_IF_ANY
明确要求停止链式调用。即使存在后续配置器,也不再执行。
public interface Configurator extends ContextAware { ExecutionStatus configure(LoggerContext var1);
public static enum ExecutionStatus { NEUTRAL, INVOKE_NEXT_IF_ANY, DO_NOT_INVOKE_NEXT_IF_ANY;
private ExecutionStatus() { } }}
复制代码
String[] INTERNAL_CONFIGURATOR_CLASSNAME_LIST = new String[]{"ch.qos.logback.classic.joran.SerializedModelConfigurator", "ch.qos.logback.classic.util.DefaultJoranConfigurator", "ch.qos.logback.classic.BasicConfigurator"};
复制代码
Spring Boot 干预阶段
这个阶段同 1.5.11.RELEASE 差异基本不大,这里不再赘述。
评论