From 4f17a9cc9d2667accbd8ef65813a055d28525166 Mon Sep 17 00:00:00 2001 From: lionel benychou Date: Wed, 28 Dec 2022 18:42:31 +0100 Subject: [PATCH] Allow JoranConfigurators to be passed ahead of time in logback See gh-33643 --- .../logging/logback/LogbackLoggingSystem.java | 15 ++++-- .../logback/SpringBootJoranConfigurator.java | 11 +++++ .../logback/LogbackLoggingSystemTests.java | 49 ++++++++++++++++++- .../test/resources/logback-custom-rules.xml | 13 +++++ 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/resources/logback-custom-rules.xml diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index 2e0e243f6f..def808679d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -20,6 +20,8 @@ import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.logging.ConsoleHandler; @@ -83,6 +85,8 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF private static final LogLevels LEVELS = new LogLevels<>(); + private Collection configurators = Collections.emptyList(); + static { LEVELS.map(LogLevel.TRACE, Level.TRACE); LEVELS.map(LogLevel.TRACE, Level.ALL); @@ -184,7 +188,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF if (isAlreadyInitialized(loggerContext)) { return; } - if (!initializeFromAotGeneratedArtifactsIfPossible(initializationContext, logFile)) { + if (!initializeFromAotGeneratedArtifactsIfPossible(initializationContext, this.configurators, logFile)) { super.initialize(initializationContext, configLocation, logFile); } loggerContext.getTurboFilterList().remove(FILTER); @@ -196,7 +200,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF } private boolean initializeFromAotGeneratedArtifactsIfPossible(LoggingInitializationContext initializationContext, - LogFile logFile) { + Collection configurators, LogFile logFile) { if (!AotDetector.useGeneratedArtifacts()) { return false; } @@ -205,7 +209,8 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF } LoggerContext loggerContext = getLoggerContext(); stopAndReset(loggerContext); - SpringBootJoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext); + SpringBootJoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext, + configurators); configurator.setContext(loggerContext); return configurator.configureUsingAotGeneratedArtifacts(); } @@ -260,7 +265,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext, URL url) throws JoranException { if (url.toString().endsWith("xml")) { - JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext); + JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext, this.configurators); configurator.setContext(loggerContext); configurator.doConfigure(url); } @@ -415,6 +420,8 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF BeanFactoryInitializationAotContribution contribution = (BeanFactoryInitializationAotContribution) context .getObject(key); context.removeObject(key); + this.configurators = beanFactory.getBeansOfType(JoranConfigurator.class).values(); + this.configurators.forEach((configurator) -> configurator.setContext(context)); return contribution; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringBootJoranConfigurator.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringBootJoranConfigurator.java index 66f63250d7..de3e8610cb 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringBootJoranConfigurator.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringBootJoranConfigurator.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -78,8 +79,16 @@ class SpringBootJoranConfigurator extends JoranConfigurator { private final LoggingInitializationContext initializationContext; + private final Collection configurators; + SpringBootJoranConfigurator(LoggingInitializationContext initializationContext) { + this(initializationContext, Collections.emptyList()); + } + + SpringBootJoranConfigurator(LoggingInitializationContext initializationContext, + Collection configurators) { this.initializationContext = initializationContext; + this.configurators = configurators; } @Override @@ -105,6 +114,7 @@ class SpringBootJoranConfigurator extends JoranConfigurator { ruleStore.addRule(new ElementSelector("configuration/springProperty"), SpringPropertyAction::new); ruleStore.addRule(new ElementSelector("*/springProfile"), SpringProfileAction::new); ruleStore.addTransparentPathPart("springProfile"); + this.configurators.forEach((configurator) -> configurator.addElementSelectorAndActionAssociations(ruleStore)); } boolean configureUsingAotGeneratedArtifacts() { @@ -124,6 +134,7 @@ class SpringBootJoranConfigurator extends JoranConfigurator { getContext().putObject(BeanFactoryInitializationAotContribution.class.getName(), new LogbackConfigurationAotContribution(model, getModelInterpretationContext(), getContext())); } + this.configurators.forEach((configurator) -> configurator.processModel(model)); } private boolean isAotProcessingInProgress() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 61b98c18d3..de92ce7e98 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -33,9 +33,14 @@ import java.util.logging.LogManager; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.classic.spi.LoggerContextListener; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.encoder.LayoutWrappingEncoder; +import ch.qos.logback.core.joran.action.Action; +import ch.qos.logback.core.joran.spi.ElementSelector; +import ch.qos.logback.core.joran.spi.RuleStore; +import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; import ch.qos.logback.core.util.StatusPrinter; @@ -46,8 +51,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; +import org.xml.sax.Attributes; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.logging.AbstractLoggingSystemTests; import org.springframework.boot.logging.LogFile; @@ -651,7 +658,8 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { @Test void whenContextHasNoAotContributionThenProcessAheadOfTimeReturnsNull() { - BeanFactoryInitializationAotContribution contribution = this.loggingSystem.processAheadOfTime(null); + BeanFactoryInitializationAotContribution contribution = this.loggingSystem + .processAheadOfTime(new DefaultListableBeanFactory()); assertThat(contribution).isNull(); } @@ -660,11 +668,48 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { LoggerContext context = ((LoggerContext) LoggerFactory.getILoggerFactory()); context.putObject(BeanFactoryInitializationAotContribution.class.getName(), mock(BeanFactoryInitializationAotContribution.class)); - BeanFactoryInitializationAotContribution contribution = this.loggingSystem.processAheadOfTime(null); + BeanFactoryInitializationAotContribution contribution = this.loggingSystem + .processAheadOfTime(new DefaultListableBeanFactory()); assertThat(context.getObject(BeanFactoryInitializationAotContribution.class.getName())).isNull(); assertThat(contribution).isNotNull(); } + @Test + void whenContextHasConfiguratorsThenInitializeExecutesThem(CapturedOutput output) { + LoggerContext context = ((LoggerContext) LoggerFactory.getILoggerFactory()); + context.putObject(BeanFactoryInitializationAotContribution.class.getName(), + mock(BeanFactoryInitializationAotContribution.class)); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("joranConfigurator1", new JoranConfigurator() { + @Override + public void addElementSelectorAndActionAssociations(RuleStore ruleStore) { + ruleStore.addRule(new ElementSelector("*/rule1"), () -> new Action() { + @Override + public void begin(SaxEventInterpretationContext intercon, String name, Attributes attributes) { + } + + @Override + public void end(SaxEventInterpretationContext intercon, String name) { + } + }); + ruleStore.addRule(new ElementSelector("*/rule2"), () -> new Action() { + @Override + public void begin(SaxEventInterpretationContext intercon, String name, Attributes attributes) { + } + + @Override + public void end(SaxEventInterpretationContext intercon, String name) { + } + }); + } + }); + this.loggingSystem.processAheadOfTime(beanFactory); + this.loggingSystem.beforeInitialize(); + initialize(this.initializationContext, "classpath:logback-custom-rules.xml", null); + assertThat(output).doesNotContain("Ignoring unknown property [rule1] in [ch.qos.logback.classic.LoggerContext]") + .doesNotContain("Ignoring unknown property [rule2] in [ch.qos.logback.classic.LoggerContext]"); + } + @Test // gh-33610 void springProfileIfNestedWithinSecondPhaseElementSanityChecker(CapturedOutput output) { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot-project/spring-boot/src/test/resources/logback-custom-rules.xml b/spring-boot-project/spring-boot/src/test/resources/logback-custom-rules.xml new file mode 100644 index 0000000000..beddbe5da7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/logback-custom-rules.xml @@ -0,0 +1,13 @@ + + + + + %property{LOG_FILE} [%t] ${PID:-????} %c{1}: %m%n BOOTBOOT + + + + + + + +