From 5a7964af2b6d82058853cfbbd5f2798c6838de9a Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 12 Oct 2022 21:29:16 -0700 Subject: [PATCH] Polish 'Support profile specific Log4j2 configuration' See gh-32734 --- .../logging/log4j2/SpringProfileArbiter.java | 80 +++------ .../log4j2/Log4J2LoggingSystemTests.java | 25 --- .../log4j2/SpringProfileArbiterTests.java | 162 ++++++++++++++++++ .../log4j2/TestLog4J2LoggingSystem.java | 47 +++++ .../logging/log4j2/multi-profile-names.xml | 8 + .../logging/log4j2/production-profile.xml | 8 + .../logging/log4j2/profile-expression.xml | 8 + 7 files changed, 258 insertions(+), 80 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/SpringProfileArbiterTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java create mode 100644 spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/multi-profile-names.xml create mode 100644 spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/production-profile.xml create mode 100644 spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/profile-expression.xml diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringProfileArbiter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringProfileArbiter.java index bf5e7d18864..f002813dce5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringProfileArbiter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringProfileArbiter.java @@ -37,51 +37,38 @@ import org.springframework.util.StringUtils; * included. * * @author Ralph Goers - * @since 3.0.0 */ @Plugin(name = "SpringProfile", category = Node.CATEGORY, elementType = Arbiter.ELEMENT_TYPE, deferChildren = true, printObject = true) -public final class SpringProfileArbiter implements Arbiter { - - private final String[] profileNames; +final class SpringProfileArbiter implements Arbiter { private final Environment environment; - private SpringProfileArbiter(final String[] profiles, Environment environment) { - this.profileNames = profiles; + private final Profiles profiles; + + private SpringProfileArbiter(Environment environment, String[] profiles) { this.environment = environment; + this.profiles = Profiles.of(profiles); } @Override public boolean isCondition() { - if (this.environment == null) { - return false; - } - - if (this.profileNames.length == 0) { - return false; - } - return this.environment.acceptsProfiles(Profiles.of(this.profileNames)); + return (this.environment != null) ? this.environment.acceptsProfiles(this.profiles) : false; } @PluginBuilderFactory - public static Builder newBuilder() { + static Builder newBuilder() { return new Builder(); } /** * Standard Builder to create the Arbiter. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static final class Builder implements org.apache.logging.log4j.core.util.Builder { - private static final Logger LOGGER = StatusLogger.getLogger(); + private static final Logger statusLogger = StatusLogger.getLogger(); - /** - * Attribute name identifier. - */ - public static final String ATTR_NAME = "name"; - - @PluginBuilderAttribute(ATTR_NAME) + @PluginBuilderAttribute private String name; @PluginConfiguration @@ -90,47 +77,30 @@ public final class SpringProfileArbiter implements Arbiter { @PluginLoggerContext private LoggerContext loggerContext; + private Builder() { + } + /** - * Sets the Profile Name or Names. - * @param name the profile name(s). + * Sets the profile name or expression. + * @param name the profile name or expression * @return this + * @see Profiles#of(String...) */ - public Builder setName(final String name) { + public Builder setName(String name) { this.name = name; - return asBuilder(); - } - - public Builder setConfiguration(final Configuration configuration) { - this.configuration = configuration; - return asBuilder(); - } - - public Builder setLoggerContext(final LoggerContext loggerContext) { - this.loggerContext = loggerContext; - return asBuilder(); - } - - private SpringProfileArbiter.Builder asBuilder() { return this; } + @Override public SpringProfileArbiter build() { - String[] profileNames = StringUtils.trimArrayElements(StringUtils - .commaDelimitedListToStringArray(this.configuration.getStrSubstitutor().replace(this.name))); - Environment environment = null; - if (this.loggerContext != null) { - environment = (Environment) this.loggerContext.getObject(Log4J2LoggingSystem.ENVIRONMENT_KEY); - if (environment == null) { - LOGGER.warn("Cannot create Arbiter, no Spring Environment provided"); - return null; - } - - return new SpringProfileArbiter(profileNames, environment); + Environment environment = Log4J2LoggingSystem.getEnvironment(this.loggerContext); + if (environment == null) { + statusLogger.warn("Cannot create Arbiter, no Spring Environment available"); + return null; } - else { - LOGGER.warn("Cannot create Arbiter, LoggerContext is not available"); - } - return null; + String name = this.configuration.getStrSubstitutor().replace(this.name); + String[] profiles = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); + return new SpringProfileArbiter(environment, profiles); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index ec12461e83e..8d0955e54f9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -19,8 +19,6 @@ package org.springframework.boot.logging.log4j2; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; -import java.util.ArrayList; -import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; @@ -480,29 +478,6 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { return defaultPath; } - static class TestLog4J2LoggingSystem extends Log4J2LoggingSystem { - - private List availableClasses = new ArrayList<>(); - - TestLog4J2LoggingSystem() { - super(TestLog4J2LoggingSystem.class.getClassLoader()); - } - - Configuration getConfiguration() { - return ((org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false)).getConfiguration(); - } - - @Override - protected boolean isClassAvailable(String className) { - return this.availableClasses.contains(className); - } - - private void availableClasses(String... classNames) { - Collections.addAll(this.availableClasses, classNames); - } - - } - /** * Used for testing that loggers in nested classes are returned by * {@link Log4J2LoggingSystem#getLoggerConfigurations()} . diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/SpringProfileArbiterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/SpringProfileArbiterTests.java new file mode 100644 index 00000000000..ab1f6e746a9 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/SpringProfileArbiterTests.java @@ -0,0 +1,162 @@ +/* + * 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.log4j2; + +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertySource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.logging.LoggingInitializationContext; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.boot.testsupport.logging.ConfigureClasspathToPreferLog4j2; +import org.springframework.boot.testsupport.system.CapturedOutput; +import org.springframework.boot.testsupport.system.OutputCaptureExtension; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringProfileArbiter}. + * + * @author Phillip Webb + */ +@ExtendWith(OutputCaptureExtension.class) +@ClassPathExclusions("logback-*.jar") +@ConfigureClasspathToPreferLog4j2 +class SpringProfileArbiterTests { + + private CapturedOutput output; + + private final TestLog4J2LoggingSystem loggingSystem = new TestLog4J2LoggingSystem(); + + private final MockEnvironment environment = new MockEnvironment(); + + private final LoggingInitializationContext initializationContext = new LoggingInitializationContext( + this.environment); + + private Logger logger; + + private Configuration configuration; + + @BeforeEach + void setup(CapturedOutput output) { + this.output = output; + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + this.configuration = loggerContext.getConfiguration(); + this.loggingSystem.cleanUp(); + this.logger = LogManager.getLogger(getClass()); + cleanUpPropertySources(); + } + + @AfterEach + void cleanUp() { + this.loggingSystem.cleanUp(); + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + loggerContext.stop(); + loggerContext.start(((Reconfigurable) this.configuration).reconfigure()); + cleanUpPropertySources(); + } + + @SuppressWarnings("unchecked") + private void cleanUpPropertySources() { // https://issues.apache.org/jira/browse/LOG4J2-3618 + PropertiesUtil properties = PropertiesUtil.getProperties(); + Object environment = ReflectionTestUtils.getField(properties, "environment"); + Set sources = (Set) ReflectionTestUtils.getField(environment, "sources"); + sources.removeIf((candidate) -> candidate instanceof SpringEnvironmentPropertySource + || candidate instanceof SpringBootPropertySource); + } + + @Test + void profileActive() { + this.environment.setActiveProfiles("production"); + initialize("production-profile.xml"); + this.logger.trace("Hello"); + assertThat(this.output).contains("Hello"); + } + + @Test + void multipleNamesFirstProfileActive() { + this.environment.setActiveProfiles("production"); + initialize("multi-profile-names.xml"); + this.logger.trace("Hello"); + assertThat(this.output).contains("Hello"); + } + + @Test + void multipleNamesSecondProfileActive() { + this.environment.setActiveProfiles("test"); + initialize("multi-profile-names.xml"); + this.logger.trace("Hello"); + assertThat(this.output).contains("Hello"); + } + + @Test + void profileNotActive() { + initialize("production-profile.xml"); + this.logger.trace("Hello"); + assertThat(this.output).doesNotContain("Hello"); + } + + @Test + void profileExpressionMatchFirst() { + this.environment.setActiveProfiles("production"); + initialize("profile-expression.xml"); + this.logger.trace("Hello"); + assertThat(this.output).contains("Hello"); + } + + @Test + void profileExpressionMatchSecond() { + this.environment.setActiveProfiles("test"); + initialize("profile-expression.xml"); + this.logger.trace("Hello"); + assertThat(this.output).contains("Hello"); + } + + @Test + void profileExpressionNoMatch() { + this.environment.setActiveProfiles("development"); + initialize("profile-expression.xml"); + this.logger.trace("Hello"); + assertThat(this.output).doesNotContain("Hello"); + } + + private void initialize(String config) { + this.environment.setProperty("logging.log4j2.config.override", getPackageResource(config)); + this.loggingSystem.initialize(this.initializationContext, null, null); + } + + private String getPackageResource(String fileName) { + String path = ClassUtils.getPackageName(getClass()); + path = path.replace('.', '/'); + path = path + "/" + fileName; + return "src/test/resources/" + path; + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java new file mode 100644 index 00000000000..43cd1a6760a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/TestLog4J2LoggingSystem.java @@ -0,0 +1,47 @@ +/* + * 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.log4j2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.Configuration; + +class TestLog4J2LoggingSystem extends Log4J2LoggingSystem { + + private List availableClasses = new ArrayList<>(); + + TestLog4J2LoggingSystem() { + super(TestLog4J2LoggingSystem.class.getClassLoader()); + } + + Configuration getConfiguration() { + return ((org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false)).getConfiguration(); + } + + @Override + protected boolean isClassAvailable(String className) { + return this.availableClasses.contains(className); + } + + void availableClasses(String... classNames) { + Collections.addAll(this.availableClasses, classNames); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/multi-profile-names.xml b/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/multi-profile-names.xml new file mode 100644 index 00000000000..535f4a7ae24 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/multi-profile-names.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/production-profile.xml b/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/production-profile.xml new file mode 100644 index 00000000000..f0c3309f875 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/production-profile.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/profile-expression.xml b/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/profile-expression.xml new file mode 100644 index 00000000000..25d07059818 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/profile-expression.xml @@ -0,0 +1,8 @@ + + + + + + + +