From 0bfa9cd7049b70134f3e352db70a55af8f31458e Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 28 Sep 2022 21:14:08 +0100 Subject: [PATCH] Upgrade to Logback 1.4 and SLF4J 2.0 Closes gh-12649 --- ...terRegistryConfigurerIntegrationTests.java | 10 +- ...nEvaluationReportLoggingListenerTests.java | 6 +- .../spring-boot-dependencies/build.gradle | 6 +- .../spring-boot-starter-log4j2/build.gradle | 2 +- .../boot/loader/tools/LogbackInitializer.java | 6 +- .../logback/DefaultLogbackConfiguration.java | 8 +- .../logging/logback/LogbackLoggingSystem.java | 4 +- .../logback/SpringBootJoranConfigurator.java | 28 +++-- .../logging/logback/SpringProfileAction.java | 103 +++--------------- .../logging/logback/SpringProfileModel.java | 30 +++++ .../logback/SpringProfileModelHandler.java | 77 +++++++++++++ .../logging/logback/SpringPropertyAction.java | 55 +++------- .../logging/logback/SpringPropertyModel.java | 61 +++++++++++ .../logback/SpringPropertyModelHandler.java | 71 ++++++++++++ .../LoggingApplicationListenerTests.java | 4 +- .../logback/LogbackLoggingSystemTests.java | 18 +-- .../SpringBootJoranConfiguratorTests.java | 6 +- ...va => SpringProfileModelHandlerTests.java} | 44 ++++---- src/checkstyle/checkstyle-suppressions.xml | 6 + 19 files changed, 355 insertions(+), 190 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModel.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModelHandler.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModel.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModelHandler.java rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/{SpringProfileActionTests.java => SpringProfileModelHandlerTests.java} (69%) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryConfigurerIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryConfigurerIntegrationTests.java index 29e82b0792..35588249d8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryConfigurerIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryConfigurerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; import org.junit.jupiter.api.Test; -import org.slf4j.impl.StaticLoggerBinder; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.actuate.autoconfigure.metrics.export.atlas.AtlasMetricsExportAutoConfiguration; @@ -76,8 +76,7 @@ class MeterRegistryConfigurerIntegrationTests { void counterIsIncrementedOncePerEventWithoutCompositeMeterRegistry() { new ApplicationContextRunner().with(MetricsRun.limitedTo(JmxMetricsExportAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(LogbackMetricsAutoConfiguration.class)).run((context) -> { - Logger logger = ((LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory()) - .getLogger("test-logger"); + Logger logger = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger("test-logger"); logger.error("Error."); Map registriesByName = context.getBeansOfType(MeterRegistry.class); assertThat(registriesByName).hasSize(1); @@ -92,8 +91,7 @@ class MeterRegistryConfigurerIntegrationTests { .with(MetricsRun.limitedTo(JmxMetricsExportAutoConfiguration.class, PrometheusMetricsExportAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(LogbackMetricsAutoConfiguration.class)).run((context) -> { - Logger logger = ((LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory()) - .getLogger("test-logger"); + Logger logger = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger("test-logger"); logger.error("Error."); Map registriesByName = context.getBeansOfType(MeterRegistry.class); assertThat(registriesByName).hasSize(3); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java index 02a67adc3e..3f897e93ea 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportLoggingListenerTests.java @@ -23,7 +23,7 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.slf4j.impl.StaticLoggerBinder; +import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; @@ -147,8 +147,8 @@ class ConditionEvaluationReportLoggingListenerTests { } private void withDebugLogging(Runnable runnable) { - LoggerContext context = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory(); - Logger logger = context.getLogger(ConditionEvaluationReportLoggingListener.class); + Logger logger = ((LoggerContext) LoggerFactory.getILoggerFactory()) + .getLogger(ConditionEvaluationReportLoggingListener.class); Level currentLevel = logger.getLevel(); logger.setLevel(Level.DEBUG); try { diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index f47743fd72..48314579a6 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -863,7 +863,7 @@ bom { ] } } - library("Logback", "1.2.11") { + library("Logback", "1.4.1") { group("ch.qos.logback") { modules = [ "logback-access", @@ -1366,7 +1366,7 @@ bom { ] } } - library("SLF4J", "1.7.36") { + library("SLF4J", "2.0.2") { group("org.slf4j") { modules = [ "jcl-over-slf4j", @@ -1375,9 +1375,11 @@ bom { "slf4j-api", "slf4j-ext", "slf4j-jcl", + "slf4j-jdk-platform-logging", "slf4j-jdk14", "slf4j-log4j12", "slf4j-nop", + "slf4j-reload4j", "slf4j-simple" ] } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle index 4ae6a334a3..4bf5755222 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-log4j2/build.gradle @@ -5,7 +5,7 @@ plugins { description = "Starter for using Log4j2 for logging. An alternative to spring-boot-starter-logging" dependencies { - api("org.apache.logging.log4j:log4j-slf4j-impl") + api("org.apache.logging.log4j:log4j-slf4j2-impl") api("org.apache.logging.log4j:log4j-core") api("org.apache.logging.log4j:log4j-jul") } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LogbackInitializer.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LogbackInitializer.java index 048b3f1d2c..e3229edd61 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LogbackInitializer.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LogbackInitializer.java @@ -19,7 +19,7 @@ package org.springframework.boot.loader.tools; import ch.qos.logback.classic.Level; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; -import org.slf4j.impl.StaticLoggerBinder; +import org.slf4j.LoggerFactory; import org.springframework.util.ClassUtils; @@ -32,7 +32,7 @@ import org.springframework.util.ClassUtils; public abstract class LogbackInitializer { public static void initialize() { - if (ClassUtils.isPresent("org.slf4j.impl.StaticLoggerBinder", null) + if (ClassUtils.isPresent("org.slf4j.LoggerFactory", null) && ClassUtils.isPresent("ch.qos.logback.classic.Logger", null)) { new Initializer().setRootLogLevel(); } @@ -41,7 +41,7 @@ public abstract class LogbackInitializer { private static class Initializer { void setRootLogLevel() { - ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + ILoggerFactory factory = LoggerFactory.getILoggerFactory(); Logger logger = factory.getLogger(Logger.ROOT_LOGGER_NAME); ((ch.qos.logback.classic.Logger) logger).setLevel(Level.INFO); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java index 433a385f60..3ed66dd5a6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -25,6 +25,7 @@ import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; +import ch.qos.logback.core.spi.ScanException; import ch.qos.logback.core.util.FileSize; import ch.qos.logback.core.util.OptionHelper; @@ -146,7 +147,12 @@ class DefaultLogbackConfiguration { } private String resolve(LogbackConfigurator config, String val) { - return OptionHelper.substVars(val, config.getContext()); + try { + return OptionHelper.substVars(val, config.getContext()); + } + catch (ScanException ex) { + throw new RuntimeException(ex); + } } } 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 0fde1b977c..39df27a15f 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 @@ -39,9 +39,9 @@ import ch.qos.logback.core.status.Status; import ch.qos.logback.core.util.StatusListenerConfigHelper; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.Marker; import org.slf4j.bridge.SLF4JBridgeHandler; -import org.slf4j.impl.StaticLoggerBinder; import org.springframework.boot.logging.AbstractLoggingSystem; import org.springframework.boot.logging.LogFile; @@ -348,7 +348,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem { } private LoggerContext getLoggerContext() { - ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + ILoggerFactory factory = LoggerFactory.getILoggerFactory(); Assert.isInstanceOf(LoggerContext.class, factory, () -> String.format( "LoggerFactory is not a Logback LoggerContext but Logback is on " 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 b101191f04..61e19042a3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,18 @@ package org.springframework.boot.logging.logback; import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.core.joran.action.NOPAction; import ch.qos.logback.core.joran.spi.ElementSelector; import ch.qos.logback.core.joran.spi.RuleStore; +import ch.qos.logback.core.model.processor.DefaultProcessor; import org.springframework.boot.logging.LoggingInitializationContext; -import org.springframework.core.env.Environment; /** * Extended version of the Logback {@link JoranConfigurator} that adds additional Spring * Boot rules. * * @author Phillip Webb + * @author Andy Wilkinson */ class SpringBootJoranConfigurator extends JoranConfigurator { @@ -39,12 +39,22 @@ class SpringBootJoranConfigurator extends JoranConfigurator { } @Override - public void addInstanceRules(RuleStore rs) { - super.addInstanceRules(rs); - Environment environment = this.initializationContext.getEnvironment(); - rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment)); - rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment)); - rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction()); + protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) { + super.addModelHandlerAssociations(defaultProcessor); + defaultProcessor.addHandler(SpringPropertyModel.class, + (handlerContext, handlerMic) -> new SpringPropertyModelHandler(this.context, + this.initializationContext.getEnvironment())); + defaultProcessor.addHandler(SpringProfileModel.class, + (handlerContext, handlerMic) -> new SpringProfileModelHandler(this.context, + this.initializationContext.getEnvironment())); + } + + @Override + public void addElementSelectorAndActionAssociations(RuleStore ruleStore) { + super.addElementSelectorAndActionAssociations(ruleStore); + ruleStore.addRule(new ElementSelector("configuration/springProperty"), SpringPropertyAction::new); + ruleStore.addRule(new ElementSelector("*/springProfile"), SpringProfileAction::new); + ruleStore.addTransparentPathPart("springProfile"); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileAction.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileAction.java index a9a1e0a51b..091d23ef9a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileAction.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,102 +16,29 @@ package org.springframework.boot.logging.logback; -import java.util.ArrayList; -import java.util.List; - -import ch.qos.logback.core.joran.action.Action; -import ch.qos.logback.core.joran.event.InPlayListener; -import ch.qos.logback.core.joran.event.SaxEvent; -import ch.qos.logback.core.joran.spi.ActionException; -import ch.qos.logback.core.joran.spi.InterpretationContext; -import ch.qos.logback.core.joran.spi.Interpreter; -import ch.qos.logback.core.util.OptionHelper; +import ch.qos.logback.core.joran.action.BaseModelAction; +import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext; +import ch.qos.logback.core.model.Model; import org.xml.sax.Attributes; -import org.springframework.core.env.Environment; -import org.springframework.core.env.Profiles; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - /** - * Logback {@link Action} to support {@code } tags. Allows section of a - * logback configuration to only be enabled when a specific profile is active. + * Logback {@link BaseModelAction} for {@code } tags. Allows a section of + * a Logback configuration to only be enabled when a specific profile is active. * * @author Phillip Webb * @author Eddú Meléndez + * @author Andy Wilkinson + * @see SpringProfileModel + * @see SpringProfileModelHandler */ -class SpringProfileAction extends Action implements InPlayListener { - - private final Environment environment; - - private int depth = 0; - - private boolean acceptsProfile; - - private List events; - - SpringProfileAction(Environment environment) { - this.environment = environment; - } - - @Override - public void begin(InterpretationContext ic, String name, Attributes attributes) throws ActionException { - this.depth++; - if (this.depth != 1) { - return; - } - ic.pushObject(this); - this.acceptsProfile = acceptsProfiles(ic, attributes); - this.events = new ArrayList<>(); - ic.addInPlayListener(this); - } - - private boolean acceptsProfiles(InterpretationContext ic, Attributes attributes) { - if (this.environment == null) { - return false; - } - String[] profileNames = StringUtils - .trimArrayElements(StringUtils.commaDelimitedListToStringArray(attributes.getValue(NAME_ATTRIBUTE))); - if (profileNames.length == 0) { - return false; - } - for (int i = 0; i < profileNames.length; i++) { - profileNames[i] = OptionHelper.substVars(profileNames[i], ic, this.context); - } - return this.environment.acceptsProfiles(Profiles.of(profileNames)); - } - - @Override - public void end(InterpretationContext ic, String name) throws ActionException { - this.depth--; - if (this.depth != 0) { - return; - } - ic.removeInPlayListener(this); - verifyAndPop(ic); - if (this.acceptsProfile) { - addEventsToPlayer(ic); - } - } - - private void verifyAndPop(InterpretationContext ic) { - Object o = ic.peekObject(); - Assert.state(o != null, "Unexpected null object on stack"); - Assert.isInstanceOf(SpringProfileAction.class, o, "logback stack error"); - Assert.state(o == this, "ProfileAction different than current one on stack"); - ic.popObject(); - } - - private void addEventsToPlayer(InterpretationContext ic) { - Interpreter interpreter = ic.getJoranInterpreter(); - this.events.remove(0); - this.events.remove(this.events.size() - 1); - interpreter.getEventPlayer().addEventsDynamically(this.events, 1); - } +class SpringProfileAction extends BaseModelAction { @Override - public void inPlay(SaxEvent event) { - this.events.add(event); + protected Model buildCurrentModel(SaxEventInterpretationContext interpretationContext, String name, + Attributes attributes) { + SpringProfileModel model = new SpringProfileModel(); + model.setName(attributes.getValue(NAME_ATTRIBUTE)); + return model; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModel.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModel.java new file mode 100644 index 0000000000..eef6b7ee6d --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import ch.qos.logback.core.model.NamedModel; + +/** + * Logback {@link NamedModel model} to support {@code } tags. + * + * @author Andy Wilkinson + * @see SpringProfileAction + * @see SpringProfileModelHandler + */ +class SpringProfileModel extends NamedModel { + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModelHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModelHandler.java new file mode 100644 index 0000000000..2a1497d992 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringProfileModelHandler.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import ch.qos.logback.core.Context; +import ch.qos.logback.core.model.Model; +import ch.qos.logback.core.model.processor.ModelHandlerBase; +import ch.qos.logback.core.model.processor.ModelHandlerException; +import ch.qos.logback.core.model.processor.ModelInterpretationContext; +import ch.qos.logback.core.spi.ScanException; +import ch.qos.logback.core.util.OptionHelper; + +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.util.StringUtils; + +/** + * Logback {@link ModelHandlerBase model handler} to support {@code } tags. + * + * @author Phillip Webb + * @author Eddú Meléndez + * @author Andy Wilkinson + * @see SpringProfileModel + * @see SpringProfileAction + */ +class SpringProfileModelHandler extends ModelHandlerBase { + + private final Environment environment; + + SpringProfileModelHandler(Context context, Environment environment) { + super(context); + this.environment = environment; + } + + @Override + public void handle(ModelInterpretationContext intercon, Model model) throws ModelHandlerException { + SpringProfileModel profileModel = (SpringProfileModel) model; + if (!acceptsProfiles(intercon, profileModel)) { + model.markAsSkipped(); + } + } + + private boolean acceptsProfiles(ModelInterpretationContext ic, SpringProfileModel model) { + if (this.environment == null) { + return false; + } + String[] profileNames = StringUtils + .trimArrayElements(StringUtils.commaDelimitedListToStringArray(model.getName())); + if (profileNames.length == 0) { + return false; + } + for (int i = 0; i < profileNames.length; i++) { + try { + profileNames[i] = OptionHelper.substVars(profileNames[i], ic, this.context); + } + catch (ScanException ex) { + throw new RuntimeException(ex); + } + } + return this.environment.acceptsProfiles(Profiles.of(profileNames)); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyAction.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyAction.java index 5100922f1e..a55fd7492a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyAction.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,58 +16,37 @@ package org.springframework.boot.logging.logback; -import ch.qos.logback.core.joran.action.Action; -import ch.qos.logback.core.joran.action.ActionUtil; -import ch.qos.logback.core.joran.action.ActionUtil.Scope; -import ch.qos.logback.core.joran.spi.ActionException; -import ch.qos.logback.core.joran.spi.InterpretationContext; -import ch.qos.logback.core.util.OptionHelper; +import ch.qos.logback.core.joran.action.BaseModelAction; +import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext; +import ch.qos.logback.core.model.Model; import org.xml.sax.Attributes; -import org.springframework.core.env.Environment; - /** - * Logback {@link Action} to support {@code } tags. Allows logback + * Logback {@link BaseModelAction} for {@code } tags. Allows Logback * properties to be sourced from the Spring environment. * * @author Phillip Webb * @author Eddú Meléndez * @author Madhura Bhave + * @author Andy Wilkinson + * @see SpringPropertyModel + * @see SpringPropertyModelHandler */ -class SpringPropertyAction extends Action { +class SpringPropertyAction extends BaseModelAction { private static final String SOURCE_ATTRIBUTE = "source"; private static final String DEFAULT_VALUE_ATTRIBUTE = "defaultValue"; - private final Environment environment; - - SpringPropertyAction(Environment environment) { - this.environment = environment; - } - - @Override - public void begin(InterpretationContext context, String elementName, Attributes attributes) throws ActionException { - String name = attributes.getValue(NAME_ATTRIBUTE); - String source = attributes.getValue(SOURCE_ATTRIBUTE); - Scope scope = ActionUtil.stringToScope(attributes.getValue(SCOPE_ATTRIBUTE)); - String defaultValue = attributes.getValue(DEFAULT_VALUE_ATTRIBUTE); - if (OptionHelper.isEmpty(name) || OptionHelper.isEmpty(source)) { - addError("The \"name\" and \"source\" attributes of must be set"); - } - ActionUtil.setProperty(context, name, getValue(source, defaultValue), scope); - } - - private String getValue(String source, String defaultValue) { - if (this.environment == null) { - addWarn("No Spring Environment available to resolve " + source); - return defaultValue; - } - return this.environment.getProperty(source, defaultValue); - } - @Override - public void end(InterpretationContext context, String name) throws ActionException { + protected Model buildCurrentModel(SaxEventInterpretationContext interpretationContext, String name, + Attributes attributes) { + SpringPropertyModel model = new SpringPropertyModel(); + model.setName(attributes.getValue(NAME_ATTRIBUTE)); + model.setSource(attributes.getValue(SOURCE_ATTRIBUTE)); + model.setScope(attributes.getValue(SCOPE_ATTRIBUTE)); + model.setDefaultValue(attributes.getValue(DEFAULT_VALUE_ATTRIBUTE)); + return model; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModel.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModel.java new file mode 100644 index 0000000000..dafa86cb22 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModel.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import ch.qos.logback.core.model.NamedModel; + +/** + * Logback {@link NamedModel model} to support {@code } tags. Allows + * Logback properties to be sourced from the Spring environment. + * + * @author Andy Wilkinson + * @see SpringPropertyAction + * @see SpringPropertyModelHandler + */ +class SpringPropertyModel extends NamedModel { + + private String scope; + + private String defaultValue; + + private String source; + + String getScope() { + return this.scope; + } + + void setScope(String scope) { + this.scope = scope; + } + + String getDefaultValue() { + return this.defaultValue; + } + + void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + String getSource() { + return this.source; + } + + void setSource(String source) { + this.source = source; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModelHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModelHandler.java new file mode 100644 index 0000000000..3b10008df9 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringPropertyModelHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import ch.qos.logback.core.Context; +import ch.qos.logback.core.joran.action.ActionUtil; +import ch.qos.logback.core.joran.action.ActionUtil.Scope; +import ch.qos.logback.core.model.Model; +import ch.qos.logback.core.model.ModelUtil; +import ch.qos.logback.core.model.processor.ModelHandlerBase; +import ch.qos.logback.core.model.processor.ModelHandlerException; +import ch.qos.logback.core.model.processor.ModelInterpretationContext; +import ch.qos.logback.core.util.OptionHelper; + +import org.springframework.core.env.Environment; + +/** + * Logback {@link ModelHandlerBase model handler} to support {@code } + * tags. Allows Logback properties to be sourced from the Spring environment. + * + * @author Phillip Webb + * @author Eddú Meléndez + * @author Madhura Bhave + * @author Andy Wilkinson + * @see SpringPropertyAction + * @see SpringPropertyModel + */ +class SpringPropertyModelHandler extends ModelHandlerBase { + + private final Environment environment; + + SpringPropertyModelHandler(Context context, Environment environment) { + super(context); + this.environment = environment; + } + + @Override + public void handle(ModelInterpretationContext intercon, Model model) throws ModelHandlerException { + SpringPropertyModel propertyModel = (SpringPropertyModel) model; + Scope scope = ActionUtil.stringToScope(propertyModel.getScope()); + String defaultValue = propertyModel.getDefaultValue(); + String source = propertyModel.getSource(); + if (OptionHelper.isNullOrEmpty(propertyModel.getName()) || OptionHelper.isNullOrEmpty(source)) { + addError("The \"name\" and \"source\" attributes of must be set"); + } + ModelUtil.setProperty(intercon, propertyModel.getName(), getValue(source, defaultValue), scope); + } + + private String getValue(String source, String defaultValue) { + if (this.environment == null) { + addWarn("No Spring Environment available to resolve " + source); + return defaultValue; + } + return this.environment.getProperty(source, defaultValue); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java index 9ac81baa25..765c9b6e8e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java @@ -41,8 +41,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; -import org.slf4j.impl.StaticLoggerBinder; import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; @@ -101,7 +101,7 @@ class LoggingApplicationListenerTests { private final LoggingApplicationListener listener = new LoggingApplicationListener(); - private final LoggerContext loggerContext = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory(); + private final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); private final ch.qos.logback.classic.Logger logger = this.loggerContext.getLogger(getClass()); 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 3308792759..cf2b02d5db 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 @@ -43,8 +43,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; -import org.slf4j.impl.StaticLoggerBinder; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.logging.AbstractLoggingSystemTests; @@ -103,7 +103,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET); this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); this.loggingSystem.cleanUp(); - this.logger = ((LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory()).getLogger(getClass()); + this.logger = ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(getClass()); this.environment = new MockEnvironment(); ConversionService conversionService = ApplicationConversionService.getSharedInstance(); this.environment.setConversionService((ConfigurableConversionService) conversionService); @@ -114,7 +114,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { void cleanUp() { System.getProperties().keySet().retainAll(this.systemPropertyNames); this.loggingSystem.cleanUp(); - ((LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory()).stop(); + ((LoggerContext) LoggerFactory.getILoggerFactory()).stop(); } @Test @@ -243,7 +243,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { void getLoggingConfigurationForALL() { this.loggingSystem.beforeInitialize(); initialize(this.initializationContext, null, null); - Logger logger = (Logger) StaticLoggerBinder.getSingleton().getLoggerFactory().getLogger(getClass().getName()); + Logger logger = (Logger) LoggerFactory.getILoggerFactory().getLogger(getClass().getName()); logger.setLevel(Level.ALL); LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(getClass().getName()); assertThat(configuration) @@ -255,7 +255,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { this.loggingSystem.beforeInitialize(); initialize(this.initializationContext, null, null); this.loggingSystem.setLogLevel(getClass().getName(), LogLevel.TRACE); - Logger logger = (Logger) StaticLoggerBinder.getSingleton().getLoggerFactory().getLogger(getClass().getName()); + Logger logger = (Logger) LoggerFactory.getILoggerFactory().getLogger(getClass().getName()); assertThat(logger.getLevel()).isEqualTo(Level.TRACE); } @@ -516,9 +516,9 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { this.environment.setProperty("logging.logback.rollingpolicy.max-history", "20"); this.loggingSystem.beforeInitialize(); initialize(this.initializationContext, null, null); - LoggerContext loggerContext = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory(); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); Map properties = loggerContext.getCopyOfPropertyMap(); - Set expectedProperties = new HashSet(); + Set expectedProperties = new HashSet<>(); ReflectionUtils.doWithFields(LogbackLoggingSystemProperties.class, (field) -> expectedProperties.add((String) field.get(null)), this::isPublicStaticFinal); expectedProperties.removeAll(Arrays.asList("LOG_FILE", "LOG_PATH")); @@ -533,7 +533,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { @Test void initializationIsOnlyPerformedOnceUntilCleanedUp() { - LoggerContext loggerContext = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory(); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); LoggerContextListener listener = mock(LoggerContextListener.class); loggerContext.addListener(listener); this.loggingSystem.beforeInitialize(); @@ -635,7 +635,7 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { } private static Logger getRootLogger() { - ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + ILoggerFactory factory = LoggerFactory.getILoggerFactory(); LoggerContext context = (LoggerContext) factory; return context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringBootJoranConfiguratorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringBootJoranConfiguratorTests.java index e00058884e..64e7bf85ed 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringBootJoranConfiguratorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringBootJoranConfiguratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.impl.StaticLoggerBinder; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.logging.LoggingInitializationContext; @@ -65,8 +64,7 @@ class SpringBootJoranConfiguratorTests { this.environment = new MockEnvironment(); this.initializationContext = new LoggingInitializationContext(this.environment); this.configurator = new SpringBootJoranConfigurator(this.initializationContext); - StaticLoggerBinder binder = StaticLoggerBinder.getSingleton(); - this.context = (LoggerContext) binder.getLoggerFactory(); + this.context = (LoggerContext) LoggerFactory.getILoggerFactory(); this.logger = this.context.getLogger(getClass()); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringProfileActionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringProfileModelHandlerTests.java similarity index 69% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringProfileActionTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringProfileModelHandlerTests.java index acb6e9c41d..d76fec4e6e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringProfileActionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/SpringProfileModelHandlerTests.java @@ -21,38 +21,34 @@ import java.util.List; import ch.qos.logback.core.Context; import ch.qos.logback.core.ContextBase; -import ch.qos.logback.core.joran.action.Action; import ch.qos.logback.core.joran.spi.ActionException; -import ch.qos.logback.core.joran.spi.InterpretationContext; +import ch.qos.logback.core.model.processor.ModelHandlerException; +import ch.qos.logback.core.model.processor.ModelInterpretationContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.xml.sax.Attributes; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** - * Tests for {@link SpringProfileAction}. + * Tests for {@link SpringProfileModelHandler}. * * @author Andy Wilkinson */ -class SpringProfileActionTests { +class SpringProfileModelHandlerTests { private final Environment environment = mock(Environment.class); - private final SpringProfileAction action = new SpringProfileAction(this.environment); - private final Context context = new ContextBase(); - private final InterpretationContext interpretationContext = new InterpretationContext(this.context, null); + private final SpringProfileModelHandler action = new SpringProfileModelHandler(this.context, this.environment); - private final Attributes attributes = mock(Attributes.class); + private final ModelInterpretationContext interpretationContext = new ModelInterpretationContext(this.context); @BeforeEach void setUp() { @@ -60,9 +56,10 @@ class SpringProfileActionTests { } @Test - void environmentIsQueriedWithProfileFromNameAttribute() throws ActionException { - given(this.attributes.getValue(Action.NAME_ATTRIBUTE)).willReturn("dev"); - this.action.begin(this.interpretationContext, null, this.attributes); + void environmentIsQueriedWithProfileFromModelName() throws ActionException, ModelHandlerException { + SpringProfileModel model = new SpringProfileModel(); + model.setName("dev"); + this.action.handle(this.interpretationContext, model); ArgumentCaptor profiles = ArgumentCaptor.forClass(Profiles.class); then(this.environment).should().acceptsProfiles(profiles.capture()); List profileNames = new ArrayList<>(); @@ -74,9 +71,10 @@ class SpringProfileActionTests { } @Test - void environmentIsQueriedWithMultipleProfilesFromCommaSeparatedNameAttribute() throws ActionException { - given(this.attributes.getValue(Action.NAME_ATTRIBUTE)).willReturn("dev,qa"); - this.action.begin(this.interpretationContext, null, this.attributes); + void environmentIsQueriedWithMultipleProfilesFromCommaSeparatedModelName() throws ModelHandlerException { + SpringProfileModel model = new SpringProfileModel(); + model.setName("dev,qa"); + this.action.handle(this.interpretationContext, model); ArgumentCaptor profiles = ArgumentCaptor.forClass(Profiles.class); then(this.environment).should().acceptsProfiles(profiles.capture()); List profileNames = new ArrayList<>(); @@ -88,10 +86,11 @@ class SpringProfileActionTests { } @Test - void environmentIsQueriedWithResolvedValueWhenNameAttributeUsesAPlaceholder() throws ActionException { - given(this.attributes.getValue(Action.NAME_ATTRIBUTE)).willReturn("${profile}"); + void environmentIsQueriedWithResolvedValueWhenModelNameUsesAPlaceholder() throws ModelHandlerException { + SpringProfileModel model = new SpringProfileModel(); + model.setName("${profile}"); this.context.putProperty("profile", "dev"); - this.action.begin(this.interpretationContext, null, this.attributes); + this.action.handle(this.interpretationContext, model); ArgumentCaptor profiles = ArgumentCaptor.forClass(Profiles.class); then(this.environment).should().acceptsProfiles(profiles.capture()); List profileNames = new ArrayList<>(); @@ -104,11 +103,12 @@ class SpringProfileActionTests { @Test void environmentIsQueriedWithResolvedValuesFromCommaSeparatedNameNameAttributeWithPlaceholders() - throws ActionException { - given(this.attributes.getValue(Action.NAME_ATTRIBUTE)).willReturn("${profile1},${profile2}"); + throws ModelHandlerException { + SpringProfileModel model = new SpringProfileModel(); + model.setName("${profile1},${profile2}"); this.context.putProperty("profile1", "dev"); this.context.putProperty("profile2", "qa"); - this.action.begin(this.interpretationContext, null, this.attributes); + this.action.handle(this.interpretationContext, model); ArgumentCaptor profiles = ArgumentCaptor.forClass(Profiles.class); then(this.environment).should().acceptsProfiles(profiles.capture()); List profileNames = new ArrayList<>(); diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 9abafa91e7..8a2608f3b1 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -3,6 +3,12 @@ "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd"> + + + + + +