Skip to content

Commit b98d74f

Browse files
committed
Add Log4j2 rolling policy configuration support
Signed-off-by: hojooo <ghwn5833@gmail.com>
1 parent 780adbf commit b98d74f

File tree

8 files changed

+409
-5
lines changed

8 files changed

+409
-5
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,14 @@ private void load(LoggingInitializationContext initializationContext, String loc
324324
List<String> overrides = getOverrides(initializationContext);
325325
Environment environment = initializationContext.getEnvironment();
326326
Assert.state(environment != null, "'environment' must not be null");
327-
applySystemProperties(environment, logFile);
327+
applyLog4j2SystemProperties(environment, logFile);
328328
loadConfiguration(location, logFile, overrides);
329329
}
330330

331+
private void applyLog4j2SystemProperties(Environment environment, @Nullable LogFile logFile) {
332+
new Log4j2LoggingSystemProperties(environment, getDefaultValueResolver(environment), null).apply(logFile);
333+
}
334+
331335
private List<String> getOverrides(LoggingInitializationContext initializationContext) {
332336
Environment environment = initializationContext.getEnvironment();
333337
Assert.state(environment != null, "'environment' must not be null");
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.logging.log4j2;
18+
19+
import java.util.function.BiConsumer;
20+
import java.util.function.Function;
21+
22+
import org.jspecify.annotations.Nullable;
23+
24+
import org.springframework.boot.logging.LogFile;
25+
import org.springframework.boot.logging.LoggingSystemProperties;
26+
import org.springframework.core.convert.ConversionFailedException;
27+
import org.springframework.core.convert.ConverterNotFoundException;
28+
import org.springframework.core.env.Environment;
29+
import org.springframework.core.env.PropertyResolver;
30+
import org.springframework.util.unit.DataSize;
31+
32+
/**
33+
* {@link LoggingSystemProperties} for Log4j2.
34+
*
35+
* @author hojooo
36+
* @see Log4j2RollingPolicySystemProperty
37+
*/
38+
public class Log4j2LoggingSystemProperties extends LoggingSystemProperties {
39+
40+
public Log4j2LoggingSystemProperties(Environment environment) {
41+
super(environment);
42+
}
43+
44+
/**
45+
* Create a new {@link Log4j2LoggingSystemProperties} instance.
46+
* @param environment the source environment
47+
* @param setter setter used to apply the property
48+
*/
49+
public Log4j2LoggingSystemProperties(Environment environment,
50+
@Nullable BiConsumer<String, @Nullable String> setter) {
51+
super(environment, setter);
52+
}
53+
54+
/**
55+
* Create a new {@link Log4j2LoggingSystemProperties} instance.
56+
* @param environment the source environment
57+
* @param defaultValueResolver function used to resolve default values or {@code null}
58+
* @param setter setter used to apply the property or {@code null} for system
59+
* properties
60+
*/
61+
public Log4j2LoggingSystemProperties(Environment environment,
62+
Function<@Nullable String, @Nullable String> defaultValueResolver,
63+
@Nullable BiConsumer<String, @Nullable String> setter) {
64+
super(environment, defaultValueResolver, setter);
65+
}
66+
67+
@Override
68+
protected void apply(@Nullable LogFile logFile, PropertyResolver resolver) {
69+
super.apply(logFile, resolver);
70+
applyRollingPolicyProperties(resolver);
71+
}
72+
73+
private void applyRollingPolicyProperties(PropertyResolver resolver) {
74+
applyRollingPolicy(Log4j2RollingPolicySystemProperty.FILE_NAME_PATTERN, resolver);
75+
applyRollingPolicy(Log4j2RollingPolicySystemProperty.CLEAN_HISTORY_ON_START, resolver);
76+
applyRollingPolicy(Log4j2RollingPolicySystemProperty.MAX_FILE_SIZE, resolver, DataSize.class);
77+
applyRollingPolicy(Log4j2RollingPolicySystemProperty.TOTAL_SIZE_CAP, resolver, DataSize.class);
78+
applyRollingPolicy(Log4j2RollingPolicySystemProperty.MAX_HISTORY, resolver);
79+
}
80+
81+
private void applyRollingPolicy(Log4j2RollingPolicySystemProperty property, PropertyResolver resolver) {
82+
applyRollingPolicy(property, resolver, String.class);
83+
}
84+
85+
private <T> void applyRollingPolicy(Log4j2RollingPolicySystemProperty property, PropertyResolver resolver,
86+
Class<T> type) {
87+
T value = getProperty(resolver, property.getApplicationPropertyName(), type);
88+
value = (value != null) ? value : getProperty(resolver, property.getDeprecatedApplicationPropertyName(), type);
89+
if (value != null) {
90+
String stringValue = String.valueOf((value instanceof DataSize dataSize) ? dataSize.toBytes() : value);
91+
setSystemProperty(property.getEnvironmentVariableName(), stringValue);
92+
}
93+
}
94+
95+
@SuppressWarnings("unchecked")
96+
private <T> @Nullable T getProperty(PropertyResolver resolver, String key, Class<T> type) {
97+
try {
98+
return resolver.getProperty(key, type);
99+
}
100+
catch (ConversionFailedException | ConverterNotFoundException ex) {
101+
if (type != DataSize.class) {
102+
throw ex;
103+
}
104+
// Fallback for Log4j2 compatibility - try parsing as string if DataSize conversion fails
105+
String value = resolver.getProperty(key);
106+
if (value != null) {
107+
try {
108+
return (T) DataSize.parse(value);
109+
}
110+
catch (Exception parseEx) {
111+
ex.addSuppressed(parseEx);
112+
throw ex;
113+
}
114+
}
115+
return null;
116+
}
117+
}
118+
119+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.logging.log4j2;
18+
19+
/**
20+
* Log4j2 rolling policy system properties that can later be used by log configuration
21+
* files.
22+
*
23+
* @author hojooo
24+
* @see Log4j2LoggingSystemProperties
25+
*/
26+
public enum Log4j2RollingPolicySystemProperty {
27+
28+
/**
29+
* Logging system property for the rolled-over log file name pattern.
30+
*/
31+
FILE_NAME_PATTERN("file-name-pattern", "logging.pattern.rolling-file-name"),
32+
33+
/**
34+
* Logging system property for the clean history on start flag.
35+
*/
36+
CLEAN_HISTORY_ON_START("clean-history-on-start", "logging.file.clean-history-on-start"),
37+
38+
/**
39+
* Logging system property for the file log max size.
40+
*/
41+
MAX_FILE_SIZE("max-file-size", "logging.file.max-size"),
42+
43+
/**
44+
* Logging system property for the file total size cap.
45+
*/
46+
TOTAL_SIZE_CAP("total-size-cap", "logging.file.total-size-cap"),
47+
48+
/**
49+
* Logging system property for the file log max history.
50+
*/
51+
MAX_HISTORY("max-history", "logging.file.max-history");
52+
53+
private final String environmentVariableName;
54+
55+
private final String applicationPropertyName;
56+
57+
private final String deprecatedApplicationPropertyName;
58+
59+
Log4j2RollingPolicySystemProperty(String applicationPropertyName, String deprecatedApplicationPropertyName) {
60+
this.environmentVariableName = "LOG4J2_ROLLINGPOLICY_" + name();
61+
this.applicationPropertyName = "logging.log4j2.rollingpolicy." + applicationPropertyName;
62+
this.deprecatedApplicationPropertyName = deprecatedApplicationPropertyName;
63+
}
64+
65+
/**
66+
* Return the name of environment variable that can be used to access this property.
67+
* @return the environment variable name
68+
*/
69+
public String getEnvironmentVariableName() {
70+
return this.environmentVariableName;
71+
}
72+
73+
String getApplicationPropertyName() {
74+
return this.applicationPropertyName;
75+
}
76+
77+
String getDeprecatedApplicationPropertyName() {
78+
return this.deprecatedApplicationPropertyName;
79+
}
80+
81+
}

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,41 @@
134134
"type": "java.util.List<java.lang.String>",
135135
"description": "Overriding configuration files used to create a composite configuration. Can be prefixed with 'optional:' to only load the override if it exists."
136136
},
137+
{
138+
"name": "logging.log4j2.rollingpolicy.clean-history-on-start",
139+
"type": "java.lang.Boolean",
140+
"description": "Whether to clean the archive log files on startup.",
141+
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
142+
"defaultValue": false
143+
},
144+
{
145+
"name": "logging.log4j2.rollingpolicy.file-name-pattern",
146+
"type": "java.lang.String",
147+
"description": "Pattern for rolled-over log file names.",
148+
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
149+
"defaultValue": "${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz"
150+
},
151+
{
152+
"name": "logging.log4j2.rollingpolicy.max-file-size",
153+
"type": "org.springframework.util.unit.DataSize",
154+
"description": "Maximum log file size.",
155+
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
156+
"defaultValue": "10MB"
157+
},
158+
{
159+
"name": "logging.log4j2.rollingpolicy.max-history",
160+
"type": "java.lang.Integer",
161+
"description": "Maximum number of archive log files to keep.",
162+
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
163+
"defaultValue": 7
164+
},
165+
{
166+
"name": "logging.log4j2.rollingpolicy.total-size-cap",
167+
"type": "org.springframework.util.unit.DataSize",
168+
"description": "Total size of log backups to be kept.",
169+
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
170+
"defaultValue": "0B"
171+
},
137172
{
138173
"name": "logging.logback.rollingpolicy.clean-history-on-start",
139174
"type": "java.lang.Boolean",

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
<ThresholdFilter level="${sys:CONSOLE_LOG_THRESHOLD:-TRACE}"/>
2222
</Filters>
2323
</Console>
24-
<RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="${sys:LOG_PATH}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
24+
<RollingFile name="File" fileName="${sys:LOG_FILE}"
25+
filePattern="${sys:LOG4J2_ROLLINGPOLICY_FILE_NAME_PATTERN:-${sys:LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}">
2526
<Select>
2627
<SystemPropertyArbiter propertyName="FILE_LOG_STRUCTURED_FORMAT">
2728
<StructuredLogLayout format="${sys:FILE_LOG_STRUCTURED_FORMAT}" charset="${sys:FILE_LOG_CHARSET}"/>
@@ -33,9 +34,12 @@
3334
<Filters>
3435
<ThresholdFilter level="${sys:FILE_LOG_THRESHOLD:-TRACE}"/>
3536
</Filters>
36-
<Policies>
37-
<SizeBasedTriggeringPolicy size="10 MB"/>
38-
</Policies>
37+
<Policies>
38+
<SizeBasedTriggeringPolicy size="${sys:LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}"/>
39+
</Policies>
40+
<DefaultRolloverStrategy max="${sys:LOG4J2_ROLLINGPOLICY_MAX_HISTORY:-7}"
41+
cleanHistoryOnStart="${sys:LOG4J2_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}"
42+
totalSizeCap="${sys:LOG4J2_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}"/>
3943
</RollingFile>
4044
</Appenders>
4145
<Loggers>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.junit.jupiter.api.io.TempDir;
2828
import org.slf4j.MDC;
2929

30+
import org.springframework.boot.logging.log4j2.Log4j2RollingPolicySystemProperty;
3031
import org.springframework.util.StringUtils;
3132

3233
import static org.assertj.core.api.Assertions.contentOf;
@@ -63,6 +64,9 @@ void clear() {
6364
for (LoggingSystemProperty property : LoggingSystemProperty.values()) {
6465
System.getProperties().remove(property.getEnvironmentVariableName());
6566
}
67+
for (Log4j2RollingPolicySystemProperty property : Log4j2RollingPolicySystemProperty.values()) {
68+
System.getProperties().remove(property.getEnvironmentVariableName());
69+
}
6670
MDC.clear();
6771
}
6872

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import org.springframework.mock.env.MockEnvironment;
8181
import org.springframework.util.ClassUtils;
8282
import org.springframework.util.StringUtils;
83+
import org.springframework.util.unit.DataSize;
8384

8485
import static org.assertj.core.api.Assertions.assertThat;
8586
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -891,6 +892,44 @@ static class Nested {
891892

892893
}
893894

895+
@Test
896+
void rollingPolicySystemPropertiesAreApplied() {
897+
this.environment.setProperty("logging.log4j2.rollingpolicy.max-file-size", "50MB");
898+
this.environment.setProperty("logging.log4j2.rollingpolicy.max-history", "30");
899+
this.environment.setProperty("logging.log4j2.rollingpolicy.clean-history-on-start", "true");
900+
this.environment.setProperty("logging.log4j2.rollingpolicy.total-size-cap", "5GB");
901+
this.environment.setProperty("logging.log4j2.rollingpolicy.file-name-pattern",
902+
"${LOG_FILE}.%d{yyyy-MM-dd}.%i.log");
903+
File file = new File(tmpDir(), "log4j2-test.log");
904+
LogFile logFile = getLogFile(file.getPath(), null);
905+
this.loggingSystem.beforeInitialize();
906+
this.loggingSystem.initialize(this.initializationContext, null, logFile);
907+
String maxFileSize = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE");
908+
String maxHistory = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_HISTORY");
909+
String cleanHistoryOnStart = System.getProperty("LOG4J2_ROLLINGPOLICY_CLEAN_HISTORY_ON_START");
910+
String totalSizeCap = System.getProperty("LOG4J2_ROLLINGPOLICY_TOTAL_SIZE_CAP");
911+
String fileNamePattern = System.getProperty("LOG4J2_ROLLINGPOLICY_FILE_NAME_PATTERN");
912+
assertThat(maxFileSize).isEqualTo(String.valueOf(50 * 1024 * 1024));
913+
assertThat(maxHistory).isEqualTo("30");
914+
assertThat(cleanHistoryOnStart).isEqualTo("true");
915+
assertThat(totalSizeCap).isEqualTo(String.valueOf(DataSize.ofGigabytes(5).toBytes()));
916+
assertThat(fileNamePattern).isEqualTo("${LOG_FILE}.%d{yyyy-MM-dd}.%i.log");
917+
}
918+
919+
@Test
920+
void rollingPolicyDeprecatedPropertiesAreApplied() {
921+
this.environment.setProperty("logging.file.max-size", "20MB");
922+
this.environment.setProperty("logging.file.max-history", "15");
923+
File file = new File(tmpDir(), "log4j2-test.log");
924+
LogFile logFile = getLogFile(file.getPath(), null);
925+
this.loggingSystem.beforeInitialize();
926+
this.loggingSystem.initialize(this.initializationContext, null, logFile);
927+
String maxFileSize = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE");
928+
String maxHistory = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_HISTORY");
929+
assertThat(maxFileSize).isEqualTo(String.valueOf(20 * 1024 * 1024));
930+
assertThat(maxHistory).isEqualTo("15");
931+
}
932+
894933
@Target(ElementType.METHOD)
895934
@Retention(RetentionPolicy.RUNTIME)
896935
@WithResource(name = "override.xml", content = """

0 commit comments

Comments
 (0)