mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-30 22:39:06 +00:00
Add support for Log4j2 rolling policy configuration
See gh-47260 Signed-off-by: hojooo <ghwn5833@gmail.com>
This commit is contained in:
+5
-1
@@ -26,6 +26,7 @@ import java.util.function.Function;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -180,7 +181,10 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
|
||||
}
|
||||
|
||||
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
@@ -58,8 +58,10 @@ import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration;
|
||||
import org.springframework.boot.logging.LoggingInitializationContext;
|
||||
import org.springframework.boot.logging.LoggingSystem;
|
||||
import org.springframework.boot.logging.LoggingSystemFactory;
|
||||
import org.springframework.boot.logging.LoggingSystemProperties;
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
@@ -128,6 +130,11 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
|
||||
this.loggerContext = loggerContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingSystemProperties getSystemProperties(ConfigurableEnvironment environment) {
|
||||
return new Log4j2LoggingSystemProperties(environment, getDefaultValueResolver(environment), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getStandardConfigLocations() {
|
||||
// With Log4J2 we use the ConfigurationFactory
|
||||
|
||||
+125
@@ -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
@@ -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
@@ -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
@@ -131,6 +131,54 @@
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
+10
-4
@@ -21,7 +21,8 @@
|
||||
<ThresholdFilter level="${sys:CONSOLE_LOG_THRESHOLD:-TRACE}"/>
|
||||
</Filters>
|
||||
</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>
|
||||
<SystemPropertyArbiter propertyName="FILE_LOG_STRUCTURED_FORMAT">
|
||||
<StructuredLogLayout format="${sys:FILE_LOG_STRUCTURED_FORMAT}" charset="${sys:FILE_LOG_CHARSET}"/>
|
||||
@@ -33,9 +34,14 @@
|
||||
<Filters>
|
||||
<ThresholdFilter level="${sys:FILE_LOG_THRESHOLD:-TRACE}"/>
|
||||
</Filters>
|
||||
<Policies>
|
||||
<SizeBasedTriggeringPolicy size="10 MB"/>
|
||||
</Policies>
|
||||
<Policies>
|
||||
<SpringBootTriggeringPolicy strategy="${sys:LOG4J2_ROLLINGPOLICY_STRATEGY:-size}"
|
||||
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>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
|
||||
+4
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import org.springframework.boot.logging.log4j2.Log4j2RollingPolicySystemProperty;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.contentOf;
|
||||
@@ -64,6 +65,9 @@ public abstract class AbstractLoggingSystemTests {
|
||||
for (LoggingSystemProperty property : LoggingSystemProperty.values()) {
|
||||
System.getProperties().remove(property.getEnvironmentVariableName());
|
||||
}
|
||||
for (Log4j2RollingPolicySystemProperty property : Log4j2RollingPolicySystemProperty.values()) {
|
||||
System.getProperties().remove(property.getEnvironmentVariableName());
|
||||
}
|
||||
MDC.clear();
|
||||
}
|
||||
|
||||
|
||||
+135
-31
@@ -35,6 +35,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
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.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.LoggerConfig;
|
||||
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
|
||||
@@ -116,6 +122,7 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
|
||||
this.loggingSystem.getConfiguration().stop();
|
||||
this.loggingSystem.cleanUp();
|
||||
PluginRegistry.getInstance().clear();
|
||||
clearRollingPolicySystemProperties();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -130,6 +137,12 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
|
||||
assertThat(configuration.getConfigurationSource().getFile()).isNotNull();
|
||||
}
|
||||
|
||||
private void clearRollingPolicySystemProperties() {
|
||||
for (Log4j2RollingPolicySystemProperty property : Log4j2RollingPolicySystemProperty.values()) {
|
||||
System.clearProperty(property.getEnvironmentVariableName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void withFile(CapturedOutput output) {
|
||||
this.loggingSystem.beforeInitialize();
|
||||
@@ -764,40 +777,95 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
|
||||
return defaultPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@Test
|
||||
void rollingPolicySystemPropertiesAreApplied() {
|
||||
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",
|
||||
"${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");
|
||||
}
|
||||
|
||||
@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 {
|
||||
@Test
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rollingPolicySizeAndTimeStrategyIsApplied() {
|
||||
this.environment.setProperty("logging.log4j2.rollingpolicy.strategy", "size-and-time");
|
||||
this.environment.setProperty("logging.log4j2.rollingpolicy.max-file-size", "5MB");
|
||||
this.environment.setProperty("logging.log4j2.rollingpolicy.time-based.interval", "3");
|
||||
TriggeringPolicy policy = getTriggeringPolicy();
|
||||
assertThat(findPolicy(policy, TimeBasedTriggeringPolicy.class)).isNotNull();
|
||||
assertThat(findPolicy(policy, SizeBasedTriggeringPolicy.class)).isNotNull();
|
||||
}
|
||||
|
||||
@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)
|
||||
@@ -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
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user