hojooo 2 days ago committed by GitHub
parent
commit
caeb7829aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      core/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java
  2. 7
      core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java
  3. 125
      core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4j2LoggingSystemProperties.java
  4. 95
      core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4j2RollingPolicySystemProperty.java
  5. 182
      core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootTriggeringPolicy.java
  6. 48
      core/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  7. 14
      core/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml
  8. 4
      core/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java
  9. 162
      core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java
  10. 122
      core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2LoggingSystemPropertiesTests.java

6
core/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java

@ -26,6 +26,7 @@ import java.util.function.Function;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -180,7 +181,10 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
} }
protected final void applySystemProperties(Environment environment, @Nullable LogFile logFile) { protected final void applySystemProperties(Environment environment, @Nullable LogFile logFile) {
new LoggingSystemProperties(environment, getDefaultValueResolver(environment), null).apply(logFile); LoggingSystemProperties systemProperties = (environment instanceof ConfigurableEnvironment configurableEnvironment)
? getSystemProperties(configurableEnvironment)
: new LoggingSystemProperties(environment, getDefaultValueResolver(environment), null);
systemProperties.apply(logFile);
} }
/** /**

7
core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java

@ -58,8 +58,10 @@ import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration;
import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingInitializationContext;
import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.logging.LoggingSystemFactory; import org.springframework.boot.logging.LoggingSystemFactory;
import org.springframework.boot.logging.LoggingSystemProperties;
import org.springframework.core.Conventions; import org.springframework.core.Conventions;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@ -128,6 +130,11 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
this.loggerContext = loggerContext; this.loggerContext = loggerContext;
} }
@Override
public LoggingSystemProperties getSystemProperties(ConfigurableEnvironment environment) {
return new Log4j2LoggingSystemProperties(environment, getDefaultValueResolver(environment), null);
}
@Override @Override
protected String[] getStandardConfigLocations() { protected String[] getStandardConfigLocations() {
// With Log4J2 we use the ConfigurationFactory // With Log4J2 we use the ConfigurationFactory

125
core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4j2LoggingSystemProperties.java

@ -0,0 +1,125 @@
/*
* Copyright 2012-present 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.function.BiConsumer;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LoggingSystemProperties;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.unit.DataSize;
/**
* {@link LoggingSystemProperties} for Log4j2.
*
* @author HoJoo Moon
* @since 4.0.0
* @see Log4j2RollingPolicySystemProperty
*/
public class Log4j2LoggingSystemProperties extends LoggingSystemProperties {
public Log4j2LoggingSystemProperties(Environment environment) {
super(environment);
}
/**
* Create a new {@link Log4j2LoggingSystemProperties} instance.
* @param environment the source environment
* @param setter setter used to apply the property
*/
public Log4j2LoggingSystemProperties(Environment environment,
@Nullable BiConsumer<String, @Nullable String> setter) {
super(environment, setter);
}
/**
* Create a new {@link Log4j2LoggingSystemProperties} instance.
* @param environment the source environment
* @param defaultValueResolver function used to resolve default values or {@code null}
* @param setter setter used to apply the property or {@code null} for system
* properties
*/
public Log4j2LoggingSystemProperties(Environment environment,
Function<@Nullable String, @Nullable String> defaultValueResolver,
@Nullable BiConsumer<String, @Nullable String> setter) {
super(environment, defaultValueResolver, setter);
}
@Override
protected void apply(@Nullable LogFile logFile, PropertyResolver resolver) {
super.apply(logFile, resolver);
applyRollingPolicyProperties(resolver);
}
private void applyRollingPolicyProperties(PropertyResolver resolver) {
applyRollingPolicy(Log4j2RollingPolicySystemProperty.STRATEGY, resolver);
applyRollingPolicy(Log4j2RollingPolicySystemProperty.TIME_INTERVAL, resolver, Integer.class);
applyRollingPolicy(Log4j2RollingPolicySystemProperty.TIME_MODULATE, resolver, Boolean.class);
applyRollingPolicy(Log4j2RollingPolicySystemProperty.CRON_SCHEDULE, resolver);
applyRollingPolicy(Log4j2RollingPolicySystemProperty.FILE_NAME_PATTERN, resolver);
applyRollingPolicy(Log4j2RollingPolicySystemProperty.MAX_FILE_SIZE, resolver, DataSize.class);
applyRollingPolicy(Log4j2RollingPolicySystemProperty.MAX_HISTORY, resolver);
}
private void applyRollingPolicy(Log4j2RollingPolicySystemProperty property, PropertyResolver resolver) {
applyRollingPolicy(property, resolver, String.class);
}
private <T> void applyRollingPolicy(Log4j2RollingPolicySystemProperty property, PropertyResolver resolver,
Class<T> type) {
T value = getProperty(resolver, property.getApplicationPropertyName(), type);
if (value == null && property.getDeprecatedApplicationPropertyName() != null) {
value = getProperty(resolver, property.getDeprecatedApplicationPropertyName(), type);
}
if (value != null) {
String stringValue = String.valueOf((value instanceof DataSize dataSize) ? dataSize.toBytes() : value);
setSystemProperty(property.getEnvironmentVariableName(), stringValue);
}
}
@SuppressWarnings("unchecked")
private <T> @Nullable T getProperty(PropertyResolver resolver, String key, Class<T> type) {
try {
return resolver.getProperty(key, type);
}
catch (ConversionFailedException | ConverterNotFoundException ex) {
if (type != DataSize.class) {
throw ex;
}
// Fallback for Log4j2 compatibility - try parsing as string if DataSize
// conversion fails
String value = resolver.getProperty(key);
if (value != null) {
try {
return (T) DataSize.parse(value);
}
catch (Exception parseEx) {
ex.addSuppressed(parseEx);
throw ex;
}
}
return null;
}
}
}

95
core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4j2RollingPolicySystemProperty.java

@ -0,0 +1,95 @@
/*
* Copyright 2012-present 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.jspecify.annotations.Nullable;
/**
* Log4j2 rolling policy system properties that can later be used by log configuration
* files.
*
* @author HoJoo Moon
* @since 4.0.0
* @see Log4j2LoggingSystemProperties
*/
public enum Log4j2RollingPolicySystemProperty {
/**
* Logging system property for the rolled-over log file name pattern.
*/
FILE_NAME_PATTERN("file-name-pattern", "logging.pattern.rolling-file-name"),
/**
* Logging system property for the file log max size.
*/
MAX_FILE_SIZE("max-file-size", "logging.file.max-size"),
/**
* Logging system property for the file log max history.
*/
MAX_HISTORY("max-history", "logging.file.max-history"),
/**
* Logging system property for the rolling policy strategy.
*/
STRATEGY("strategy", null),
/**
* Logging system property for the rolling policy time interval.
*/
TIME_INTERVAL("time-based.interval", null),
/**
* Logging system property for the rolling policy time modulate flag.
*/
TIME_MODULATE("time-based.modulate", null),
/**
* Logging system property for the cron based schedule.
*/
CRON_SCHEDULE("cron.schedule", null);
private final String environmentVariableName;
private final String applicationPropertyName;
private final @Nullable String deprecatedApplicationPropertyName;
Log4j2RollingPolicySystemProperty(String applicationPropertyName,
@Nullable String deprecatedApplicationPropertyName) {
this.environmentVariableName = "LOG4J2_ROLLINGPOLICY_" + name();
this.applicationPropertyName = "logging.log4j2.rollingpolicy." + applicationPropertyName;
this.deprecatedApplicationPropertyName = deprecatedApplicationPropertyName;
}
/**
* Return the name of environment variable that can be used to access this property.
* @return the environment variable name
*/
public String getEnvironmentVariableName() {
return this.environmentVariableName;
}
String getApplicationPropertyName() {
return this.applicationPropertyName;
}
@Nullable String getDeprecatedApplicationPropertyName() {
return this.deprecatedApplicationPropertyName;
}
}

182
core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/SpringBootTriggeringPolicy.java

@ -0,0 +1,182 @@
/*
* Copyright 2012-present 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.core.LogEvent;
import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.CronTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.util.Builder;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
* Factory for creating a standard Log4j2 {@link TriggeringPolicy} based on configuration
* attributes. The supported strategies are {@code size}, {@code time},
* {@code size-and-time}, and {@code cron}.
*
* @author HoJoo Moon
* @since 4.0.0
*/
@Plugin(name = "SpringBootTriggeringPolicy", category = Node.CATEGORY, elementType = "TriggeringPolicy",
deferChildren = true, printObject = true)
public abstract class SpringBootTriggeringPolicy implements TriggeringPolicy {
private SpringBootTriggeringPolicy() {
}
@Override
public void initialize(RollingFileManager manager) {
throw new UnsupportedOperationException("This class should not be instantiated");
}
@Override
public boolean isTriggeringEvent(LogEvent logEvent) {
throw new UnsupportedOperationException("This class should not be instantiated");
}
@PluginBuilderFactory
public static SpringBootTriggeringPolicyBuilder newBuilder() {
return new SpringBootTriggeringPolicyBuilder();
}
/**
* Builder for creating a {@link TriggeringPolicy}.
*/
public static class SpringBootTriggeringPolicyBuilder implements Builder<TriggeringPolicy> {
private static final String DEFAULT_STRATEGY = "size";
private static final String DEFAULT_MAX_FILE_SIZE = "10MB";
private static final int DEFAULT_TIME_INTERVAL = 1;
private static final String DEFAULT_CRON_EXPRESSION = "0 0 0 * * ?";
@PluginAttribute("strategy")
private @Nullable String strategy;
@PluginAttribute("maxFileSize")
private @Nullable String maxFileSize;
@PluginAttribute("timeInterval")
private @Nullable Integer timeInterval;
@PluginAttribute("timeModulate")
private @Nullable Boolean timeModulate;
@PluginAttribute("cronExpression")
private @Nullable String cronExpression;
@PluginConfiguration
private @Nullable Configuration configuration;
@Override
public TriggeringPolicy build() {
// Read strategy from system properties first, then from attributes
String resolvedStrategy = System.getProperty("LOG4J2_ROLLINGPOLICY_STRATEGY");
if (resolvedStrategy == null) {
resolvedStrategy = (this.strategy != null) ? this.strategy : DEFAULT_STRATEGY;
}
return switch (resolvedStrategy) {
case "time" -> createTimePolicy();
case "size-and-time" -> CompositeTriggeringPolicy.createPolicy(createSizePolicy(), createTimePolicy());
case "cron" -> createCronPolicy();
case "size" -> createSizePolicy();
default -> throw new IllegalArgumentException(
"Unsupported rolling policy strategy '%s'".formatted(resolvedStrategy));
};
}
private TriggeringPolicy createSizePolicy() {
// Read from system properties first, then from attributes
String size = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE");
if (size == null) {
size = (this.maxFileSize != null) ? this.maxFileSize : DEFAULT_MAX_FILE_SIZE;
}
return SizeBasedTriggeringPolicy.createPolicy(size);
}
private TriggeringPolicy createTimePolicy() {
// Read from system properties first, then from attributes
String intervalStr = System.getProperty("LOG4J2_ROLLINGPOLICY_TIME_INTERVAL");
int interval = (intervalStr != null) ? Integer.parseInt(intervalStr)
: (this.timeInterval != null) ? this.timeInterval : DEFAULT_TIME_INTERVAL;
String modulateStr = System.getProperty("LOG4J2_ROLLINGPOLICY_TIME_MODULATE");
boolean modulate = (modulateStr != null) ? Boolean.parseBoolean(modulateStr)
: (this.timeModulate != null) ? this.timeModulate : false;
return TimeBasedTriggeringPolicy.newBuilder().withInterval(interval).withModulate(modulate).build();
}
private TriggeringPolicy createCronPolicy() {
Assert.notNull(this.configuration, "configuration must not be null");
Configuration configuration = this.configuration;
// Read from system properties first, then from attributes
String schedule = System.getProperty("LOG4J2_ROLLINGPOLICY_CRON_SCHEDULE");
if (schedule == null) {
schedule = (this.cronExpression != null) ? this.cronExpression : DEFAULT_CRON_EXPRESSION;
}
return CronTriggeringPolicy.createPolicy(configuration, null, schedule);
}
SpringBootTriggeringPolicyBuilder setStrategy(@Nullable String strategy) {
this.strategy = strategy;
return this;
}
SpringBootTriggeringPolicyBuilder setMaxFileSize(@Nullable String maxFileSize) {
this.maxFileSize = maxFileSize;
return this;
}
SpringBootTriggeringPolicyBuilder setTimeInterval(@Nullable Integer timeInterval) {
this.timeInterval = timeInterval;
return this;
}
SpringBootTriggeringPolicyBuilder setTimeModulate(@Nullable Boolean timeModulate) {
this.timeModulate = timeModulate;
return this;
}
SpringBootTriggeringPolicyBuilder setCronExpression(@Nullable String cronExpression) {
this.cronExpression = cronExpression;
return this;
}
SpringBootTriggeringPolicyBuilder setConfiguration(Configuration configuration) {
this.configuration = configuration;
return this;
}
}
}

48
core/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -139,6 +139,54 @@
"type": "java.util.List<java.lang.String>", "type": "java.util.List<java.lang.String>",
"description": "Overriding configuration files used to create a composite configuration. Can be prefixed with 'optional:' to only load the override if it exists." "description": "Overriding configuration files used to create a composite configuration. Can be prefixed with 'optional:' to only load the override if it exists."
}, },
{
"name": "logging.log4j2.rollingpolicy.cron.schedule",
"type": "java.lang.String",
"description": "Cron expression used when the strategy is 'cron'.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
},
{
"name": "logging.log4j2.rollingpolicy.file-name-pattern",
"type": "java.lang.String",
"description": "Pattern for rolled-over log file names.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": "${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz"
},
{
"name": "logging.log4j2.rollingpolicy.max-file-size",
"type": "org.springframework.util.unit.DataSize",
"description": "Maximum log file size.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": "10MB"
},
{
"name": "logging.log4j2.rollingpolicy.max-history",
"type": "java.lang.Integer",
"description": "Maximum number of archive log files to keep.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": 7
},
{
"name": "logging.log4j2.rollingpolicy.strategy",
"type": "java.lang.String",
"description": "Rolling policy strategy. Supported values are 'size', 'time', 'size-and-time', and 'cron'.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": "size"
},
{
"name": "logging.log4j2.rollingpolicy.time-based.interval",
"type": "java.lang.Integer",
"description": "Time based triggering interval when the strategy is 'time' or 'size-and-time'.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": 1
},
{
"name": "logging.log4j2.rollingpolicy.time-based.modulate",
"type": "java.lang.Boolean",
"description": "Whether to align the next rollover time to occur at the top of the interval when the strategy is time based.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": false
},
{ {
"name": "logging.logback.rollingpolicy.clean-history-on-start", "name": "logging.logback.rollingpolicy.clean-history-on-start",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",

14
core/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml

@ -21,7 +21,8 @@
<ThresholdFilter level="${sys:CONSOLE_LOG_THRESHOLD:-TRACE}"/> <ThresholdFilter level="${sys:CONSOLE_LOG_THRESHOLD:-TRACE}"/>
</Filters> </Filters>
</Console> </Console>
<RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="${sys:LOG_PATH}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz"> <RollingFile name="File" fileName="${sys:LOG_FILE}"
filePattern="${sys:LOG4J2_ROLLINGPOLICY_FILE_NAME_PATTERN:-${sys:LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}">
<Select> <Select>
<SystemPropertyArbiter propertyName="FILE_LOG_STRUCTURED_FORMAT"> <SystemPropertyArbiter propertyName="FILE_LOG_STRUCTURED_FORMAT">
<StructuredLogLayout format="${sys:FILE_LOG_STRUCTURED_FORMAT}" charset="${sys:FILE_LOG_CHARSET}"/> <StructuredLogLayout format="${sys:FILE_LOG_STRUCTURED_FORMAT}" charset="${sys:FILE_LOG_CHARSET}"/>
@ -33,9 +34,14 @@
<Filters> <Filters>
<ThresholdFilter level="${sys:FILE_LOG_THRESHOLD:-TRACE}"/> <ThresholdFilter level="${sys:FILE_LOG_THRESHOLD:-TRACE}"/>
</Filters> </Filters>
<Policies> <Policies>
<SizeBasedTriggeringPolicy size="10 MB"/> <SpringBootTriggeringPolicy strategy="${sys:LOG4J2_ROLLINGPOLICY_STRATEGY:-size}"
</Policies> maxFileSize="${sys:LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}"
timeInterval="${sys:LOG4J2_ROLLINGPOLICY_TIME_INTERVAL:-1}"
timeModulate="${sys:LOG4J2_ROLLINGPOLICY_TIME_MODULATE:-false}"
cronExpression="${sys:LOG4J2_ROLLINGPOLICY_CRON_SCHEDULE}"/>
</Policies>
<DefaultRolloverStrategy max="${sys:LOG4J2_ROLLINGPOLICY_MAX_HISTORY:-7}"/>
</RollingFile> </RollingFile>
</Appenders> </Appenders>
<Loggers> <Loggers>

4
core/spring-boot/src/test/java/org/springframework/boot/logging/AbstractLoggingSystemTests.java

@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.slf4j.MDC; import org.slf4j.MDC;
import org.springframework.boot.logging.log4j2.Log4j2RollingPolicySystemProperty;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.contentOf; import static org.assertj.core.api.Assertions.contentOf;
@ -64,6 +65,9 @@ public abstract class AbstractLoggingSystemTests {
for (LoggingSystemProperty property : LoggingSystemProperty.values()) { for (LoggingSystemProperty property : LoggingSystemProperty.values()) {
System.getProperties().remove(property.getEnvironmentVariableName()); System.getProperties().remove(property.getEnvironmentVariableName());
} }
for (Log4j2RollingPolicySystemProperty property : Log4j2RollingPolicySystemProperty.values()) {
System.getProperties().remove(property.getEnvironmentVariableName());
}
MDC.clear(); MDC.clear();
} }

162
core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java

@ -35,6 +35,12 @@ import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.CronTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
@ -116,6 +122,7 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
this.loggingSystem.getConfiguration().stop(); this.loggingSystem.getConfiguration().stop();
this.loggingSystem.cleanUp(); this.loggingSystem.cleanUp();
PluginRegistry.getInstance().clear(); PluginRegistry.getInstance().clear();
clearRollingPolicySystemProperties();
} }
@Test @Test
@ -130,6 +137,12 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
assertThat(configuration.getConfigurationSource().getFile()).isNotNull(); assertThat(configuration.getConfigurationSource().getFile()).isNotNull();
} }
private void clearRollingPolicySystemProperties() {
for (Log4j2RollingPolicySystemProperty property : Log4j2RollingPolicySystemProperty.values()) {
System.clearProperty(property.getEnvironmentVariableName());
}
}
@Test @Test
void withFile(CapturedOutput output) { void withFile(CapturedOutput output) {
this.loggingSystem.beforeInitialize(); this.loggingSystem.beforeInitialize();
@ -764,40 +777,95 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
return defaultPath; return defaultPath;
} }
/** @Test
* Used for testing that loggers in nested classes are returned by void rollingPolicySystemPropertiesAreApplied() {
* {@link Log4J2LoggingSystem#getLoggerConfigurations()} . this.environment.setProperty("logging.log4j2.rollingpolicy.max-file-size", "50MB");
*/ this.environment.setProperty("logging.log4j2.rollingpolicy.max-history", "30");
static class Nested { this.environment.setProperty("logging.log4j2.rollingpolicy.file-name-pattern",
"${LOG_FILE}.%d{yyyy-MM-dd}.%i.log");
File file = new File(tmpDir(), "log4j2-test.log");
LogFile logFile = getLogFile(file.getPath(), null);
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext, null, logFile);
String maxFileSize = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE");
String maxHistory = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_HISTORY");
String fileNamePattern = System.getProperty("LOG4J2_ROLLINGPOLICY_FILE_NAME_PATTERN");
assertThat(maxFileSize).isEqualTo(String.valueOf(50 * 1024 * 1024));
assertThat(maxHistory).isEqualTo("30");
assertThat(fileNamePattern).isEqualTo("${LOG_FILE}.%d{yyyy-MM-dd}.%i.log");
}
@SuppressWarnings("unused") @Test
private static final Log logger = LogFactory.getLog(Nested.class); void rollingPolicyDeprecatedPropertiesAreApplied() {
this.environment.setProperty("logging.file.max-size", "20MB");
this.environment.setProperty("logging.file.max-history", "15");
File file = new File(tmpDir(), "log4j2-test.log");
LogFile logFile = getLogFile(file.getPath(), null);
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext, null, logFile);
String maxFileSize = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE");
String maxHistory = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_HISTORY");
assertThat(maxFileSize).isEqualTo(String.valueOf(20 * 1024 * 1024));
assertThat(maxHistory).isEqualTo("15");
}
@Test
void rollingPolicyTimeStrategyIsApplied() {
this.environment.setProperty("logging.log4j2.rollingpolicy.strategy", "time");
this.environment.setProperty("logging.log4j2.rollingpolicy.time-based.interval", "2");
TriggeringPolicy policy = getTriggeringPolicy();
TimeBasedTriggeringPolicy timePolicy = findPolicy(policy, TimeBasedTriggeringPolicy.class);
assertThat(timePolicy).isNotNull();
assertThat(timePolicy.getInterval()).isEqualTo(2);
} }
@Target(ElementType.METHOD) @Test
@Retention(RetentionPolicy.RUNTIME) void rollingPolicySizeAndTimeStrategyIsApplied() {
@WithResource(name = "nondefault.xml", this.environment.setProperty("logging.log4j2.rollingpolicy.strategy", "size-and-time");
content = """ this.environment.setProperty("logging.log4j2.rollingpolicy.max-file-size", "5MB");
<Configuration status="WARN" monitorInterval="30"> this.environment.setProperty("logging.log4j2.rollingpolicy.time-based.interval", "3");
<Properties> TriggeringPolicy policy = getTriggeringPolicy();
<Property name="PID">????</Property> assertThat(findPolicy(policy, TimeBasedTriggeringPolicy.class)).isNotNull();
<Property name="LOG_PATTERN">${sys:LOG_FILE} %d{yyyy-MM-dd HH:mm:ss.SSS}] service%X{context} - ${sys:PID} %5p [%t] --- %c{1}: %m%n</Property> assertThat(findPolicy(policy, SizeBasedTriggeringPolicy.class)).isNotNull();
</Properties> }
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
""")
private @interface WithNonDefaultXmlResource {
@Test
void rollingPolicyCronStrategyIsApplied() {
this.environment.setProperty("logging.log4j2.rollingpolicy.strategy", "cron");
this.environment.setProperty("logging.log4j2.rollingpolicy.cron.schedule", "0 0 0 * * ?");
TriggeringPolicy policy = getTriggeringPolicy();
CronTriggeringPolicy cronPolicy = findPolicy(policy, CronTriggeringPolicy.class);
assertThat(cronPolicy).isNotNull();
assertThat(cronPolicy.getCronExpression().getCronExpression()).isEqualTo("0 0 0 * * ?");
}
private TriggeringPolicy getTriggeringPolicy() {
File file = new File(tmpDir(), "target-file.log");
LogFile logFile = getLogFile(file.getPath(), null);
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext,
"classpath:org/springframework/boot/logging/log4j2/log4j2-file.xml", logFile);
LoggerContext loggerContext = this.loggingSystem.getLoggerContext();
Configuration configuration = loggerContext.getConfiguration();
RollingFileAppender appender = (RollingFileAppender) configuration.getAppenders().get("File");
assertThat(appender).isNotNull();
return appender.getManager().getTriggeringPolicy();
}
@SuppressWarnings("unchecked")
private <T extends TriggeringPolicy> @Nullable T findPolicy(TriggeringPolicy policy, Class<T> type) {
if (type.isInstance(policy)) {
return (T) policy;
}
if (policy instanceof CompositeTriggeringPolicy composite) {
for (TriggeringPolicy child : composite.getTriggeringPolicies()) {
T found = findPolicy(child, type);
if (found != null) {
return found;
}
}
}
return null;
} }
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@ -839,4 +907,40 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
} }
/**
* Used for testing that loggers in nested classes are returned by
* {@link Log4J2LoggingSystem#getLoggerConfigurations()} .
*/
static class Nested {
@SuppressWarnings("unused")
private static final Log logger = LogFactory.getLog(Nested.class);
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@WithResource(name = "nondefault.xml",
content = """
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="PID">????</Property>
<Property name="LOG_PATTERN">${sys:LOG_FILE} %d{yyyy-MM-dd HH:mm:ss.SSS}] service%X{context} - ${sys:PID} %5p [%t] --- %c{1}: %m%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
""")
private @interface WithNonDefaultXmlResource {
}
} }

122
core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4j2LoggingSystemPropertiesTests.java

@ -0,0 +1,122 @@
/*
* Copyright 2012-present 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.HashSet;
import java.util.Set;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LoggingSystemProperty;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Log4j2LoggingSystemProperties}.
*
* @author hojooo
*/
class Log4j2LoggingSystemPropertiesTests {
private Set<Object> systemPropertyNames;
private MockEnvironment environment;
@BeforeEach
void captureSystemPropertyNames() {
for (LoggingSystemProperty property : LoggingSystemProperty.values()) {
System.getProperties().remove(property.getEnvironmentVariableName());
}
this.systemPropertyNames = new HashSet<>(System.getProperties().keySet());
this.environment = new MockEnvironment();
this.environment
.setConversionService((ConfigurableConversionService) ApplicationConversionService.getSharedInstance());
}
@AfterEach
void restoreSystemProperties() {
System.getProperties().keySet().retainAll(this.systemPropertyNames);
}
@Test
void appliesLog4j2RollingPolicyProperties() {
this.environment.setProperty("logging.log4j2.rollingpolicy.max-file-size", "50MB");
this.environment.setProperty("logging.log4j2.rollingpolicy.max-history", "30");
this.environment.setProperty("logging.log4j2.rollingpolicy.file-name-pattern", "test.%d{yyyy-MM-dd}.%i.log");
this.environment.setProperty("logging.log4j2.rollingpolicy.strategy", "time");
this.environment.setProperty("logging.log4j2.rollingpolicy.time-based.interval", "2");
this.environment.setProperty("logging.log4j2.rollingpolicy.time-based.modulate", "true");
this.environment.setProperty("logging.log4j2.rollingpolicy.cron.schedule", "0 0 0 * * ?");
new Log4j2LoggingSystemProperties(this.environment).apply(null);
assertThat(System.getProperties())
.containsEntry("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE", String.valueOf(DataSize.ofMegabytes(50).toBytes()))
.containsEntry("LOG4J2_ROLLINGPOLICY_MAX_HISTORY", "30")
.containsEntry("LOG4J2_ROLLINGPOLICY_FILE_NAME_PATTERN", "test.%d{yyyy-MM-dd}.%i.log")
.containsEntry("LOG4J2_ROLLINGPOLICY_STRATEGY", "time")
.containsEntry("LOG4J2_ROLLINGPOLICY_TIME_INTERVAL", "2")
.containsEntry("LOG4J2_ROLLINGPOLICY_TIME_MODULATE", "true")
.containsEntry("LOG4J2_ROLLINGPOLICY_CRON_SCHEDULE", "0 0 0 * * ?");
}
@Test
void appliesLog4j2RollingPolicyPropertiesWithDefaults() {
new Log4j2LoggingSystemProperties(this.environment).apply(null);
assertThat(System.getProperties().keySet())
.noneMatch((key) -> ((String) key).startsWith("LOG4J2_ROLLINGPOLICY"));
}
@Test
void appliesDeprecatedProperties() {
this.environment.setProperty("logging.file.max-size", "20MB");
this.environment.setProperty("logging.file.max-history", "15");
new Log4j2LoggingSystemProperties(this.environment).apply(null);
assertThat(System.getProperties())
.containsEntry("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE", String.valueOf(DataSize.ofMegabytes(20).toBytes()))
.containsEntry("LOG4J2_ROLLINGPOLICY_MAX_HISTORY", "15");
}
@Test
void newPropertiesOverrideDeprecatedProperties() {
this.environment.setProperty("logging.log4j2.rollingpolicy.max-file-size", "100MB");
this.environment.setProperty("logging.file.max-size", "20MB");
this.environment.setProperty("logging.log4j2.rollingpolicy.max-history", "50");
this.environment.setProperty("logging.file.max-history", "15");
new Log4j2LoggingSystemProperties(this.environment).apply(null);
assertThat(System.getProperties())
.containsEntry("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE", String.valueOf(DataSize.ofMegabytes(100).toBytes()))
.containsEntry("LOG4J2_ROLLINGPOLICY_MAX_HISTORY", "50");
}
@Test
void appliesWithLogFile() {
this.environment.setProperty("logging.log4j2.rollingpolicy.max-file-size", "25MB");
LogFile logFile = LogFile.get(this.environment);
new Log4j2LoggingSystemProperties(this.environment).apply(logFile);
assertThat(System.getProperties()).containsEntry("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE",
String.valueOf(DataSize.ofMegabytes(25).toBytes()));
}
}
Loading…
Cancel
Save