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 new file mode 100644 index 0000000000..f002813dce --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringProfileArbiter.java @@ -0,0 +1,108 @@ +/* + * 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 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.Node; +import org.apache.logging.log4j.core.config.arbiters.Arbiter; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginLoggerContext; +import org.apache.logging.log4j.status.StatusLogger; + +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.util.StringUtils; + +/** + * An Arbiter that uses the active Spring profile to determine if configuration should be + * included. + * + * @author Ralph Goers + */ +@Plugin(name = "SpringProfile", category = Node.CATEGORY, elementType = Arbiter.ELEMENT_TYPE, deferChildren = true, + printObject = true) +final class SpringProfileArbiter implements Arbiter { + + private final Environment environment; + + private final Profiles profiles; + + private SpringProfileArbiter(Environment environment, String[] profiles) { + this.environment = environment; + this.profiles = Profiles.of(profiles); + } + + @Override + public boolean isCondition() { + return (this.environment != null) ? this.environment.acceptsProfiles(this.profiles) : false; + } + + @PluginBuilderFactory + static Builder newBuilder() { + return new Builder(); + } + + /** + * Standard Builder to create the Arbiter. + */ + public static final class Builder implements org.apache.logging.log4j.core.util.Builder { + + private static final Logger statusLogger = StatusLogger.getLogger(); + + @PluginBuilderAttribute + private String name; + + @PluginConfiguration + private Configuration configuration; + + @PluginLoggerContext + private LoggerContext loggerContext; + + private Builder() { + } + + /** + * Sets the profile name or expression. + * @param name the profile name or expression + * @return this + * @see Profiles#of(String...) + */ + public Builder setName(String name) { + this.name = name; + return this; + } + + @Override + public SpringProfileArbiter build() { + Environment environment = Log4J2LoggingSystem.getEnvironment(this.loggerContext); + if (environment == null) { + statusLogger.warn("Cannot create Arbiter, no Spring Environment 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 ec12461e83..8d0955e54f 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 0000000000..ab1f6e746a --- /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 0000000000..43cd1a6760 --- /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 0000000000..535f4a7ae2 --- /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 0000000000..f0c3309f87 --- /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 0000000000..25d0705981 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/org/springframework/boot/logging/log4j2/profile-expression.xml @@ -0,0 +1,8 @@ + + + + + + + +