写点什么

从源码角度分析 Spring Boot 中日志系统的配置

作者:喝水不抬头
  • 2025-06-02
    上海
  • 本文字数:4144 字

    阅读完需:约 14 分钟

本文以 logback 为例,介绍 Spring Boot 中如何进行日志系统的配置,假定读者已经有了基本的知识储备,如:

  • slf4j、log4j 和 logback 的区别及联系,以及如何在 pom 中正确添加依赖

  • Spring Boot 的自动配置原理

总的配置流程包含以下两个阶段:

  • 初始化阶段

当 Spring Boot 启动时,日志系统的初始化是优先级最高的任务之一(早于 ApplicationContext 的创建),日志系统会调用 ch.qos.logback.classic.spi.Configurator.doConfigure 方法进行配置。

  • Spring Boot 干预阶段

Spring Boot 的 LoggingApplicationListener 这个监听器负责在应用启动的早期阶段初始化日志系统,具体到 Logback,则调用 org.springframework.boot.logging.logback.LogbackLoggingSystem#initialize 进行配置。

Spring Boot 的不同版本处理方式略微不同,下面主要阐述一下两个版本:

  • logback 1.1.7 + Spring Boot  1.5.11.RELEASE

  • logback 1.4.14 + Spring Boot 3.2.1

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,配置文件的优先级如下:

  1. logback.configurationFile 指定的文件

  2. logback.groovy

  3. logback-test.xml

  4. 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);
}
复制代码

变化点有:

  • 首先通过 SPI 机制获取 ch.qos.logback.classic.spi.Configurator 的所有实现类并进行排序

List<Configurator> configuratorList = ClassicEnvUtil.loadFromServiceLoader(Configurator.class, classLoader);configuratorList.sort(this.rankComparator);
复制代码

支持 Configurator 的实现类使用 @ConfiguratorRank 注解,如:

@ConfiguratorRank(-10)public class BasicConfigurator extends ContextAwareBase implements Configurator {}
复制代码
  • Configurator 的 configure 方法的返回类型由 void 改成了 ExecutionStatus,ExecutionStatus 的三个值决定了 configure() 方法执行后是否继续调用后续配置器:

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() { } }}
复制代码
  • 若没有自定义 Configurator,或者所有的 Configurator 都没有返回 DO_NOT_INVOKE_NEXT_IF_ANY,默认的三个 Configurator 会被触发。

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

发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2018-05-15 加入

还未添加个人简介

评论

发布
暂无评论
从源码角度分析Spring Boot中日志系统的配置_Spring Boot_喝水不抬头_InfoQ写作社区