本文以 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 差异基本不大,这里不再赘述。
评论