Skip to content

Commit 5917b1c

Browse files
committed
Log4j2: Introduce SpringBootTriggeringPolicy and wire plugin discovery
- Add SpringBootTriggeringPolicy plugin supporting size, time, size-and-time, and cron strategies - Read rolling strategy parameters from LOG4J2_ROLLINGPOLICY_* system properties (fallback to attributes) - Register Boot plugin package in Log4J2LoggingSystem to ensure plugin discovery - Use SpringBootTriggeringPolicy under a top-level Policies wrapper in log4j2-file.xml Signed-off-by: hojooo <[email protected]>
1 parent ddda65e commit 5917b1c

File tree

3 files changed

+267
-4
lines changed

3 files changed

+267
-4
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.apache.logging.log4j.status.StatusConsoleListener;
5252
import org.apache.logging.log4j.status.StatusLogger;
5353
import org.apache.logging.log4j.util.PropertiesUtil;
54+
import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry;
5455
import org.jspecify.annotations.Nullable;
5556

5657
import org.springframework.boot.context.properties.bind.BindResult;
@@ -325,9 +326,25 @@ private void load(LoggingInitializationContext initializationContext, String loc
325326
Environment environment = initializationContext.getEnvironment();
326327
Assert.state(environment != null, "'environment' must not be null");
327328
applyLog4j2SystemProperties(environment, logFile);
329+
registerSpringBootPlugins();
328330
loadConfiguration(location, logFile, overrides);
329331
}
330332

333+
private void registerSpringBootPlugins() {
334+
String pluginPackages = SpringBootTriggeringPolicy.class.getPackageName();
335+
String existing = System.getProperty("log4j.plugin.packages");
336+
if (StringUtils.hasText(existing)) {
337+
if (!existing.contains(pluginPackages)) {
338+
System.setProperty("log4j.plugin.packages", existing + "," + pluginPackages);
339+
PluginRegistry.getInstance().clear();
340+
}
341+
}
342+
else {
343+
System.setProperty("log4j.plugin.packages", pluginPackages);
344+
PluginRegistry.getInstance().clear();
345+
}
346+
}
347+
331348
private void applyLog4j2SystemProperties(Environment environment, @Nullable LogFile logFile) {
332349
new Log4j2LoggingSystemProperties(environment, getDefaultValueResolver(environment), null).apply(logFile);
333350
}
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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.Objects;
20+
import java.util.concurrent.TimeUnit;
21+
22+
import org.apache.logging.log4j.core.LifeCycle;
23+
import org.apache.logging.log4j.core.LifeCycle2;
24+
import org.apache.logging.log4j.core.LogEvent;
25+
import org.apache.logging.log4j.core.appender.rolling.AbstractTriggeringPolicy;
26+
import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
27+
import org.apache.logging.log4j.core.appender.rolling.CronTriggeringPolicy;
28+
import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
29+
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
30+
import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
31+
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
32+
import org.apache.logging.log4j.core.config.Configuration;
33+
import org.apache.logging.log4j.core.config.Node;
34+
import org.apache.logging.log4j.core.config.plugins.Plugin;
35+
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
36+
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
37+
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
38+
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
39+
import org.jspecify.annotations.Nullable;
40+
41+
/**
42+
* {@link TriggeringPolicy} that selects one of several standard Log4j2
43+
* {@link TriggeringPolicy TriggeringPolicies} based on configuration attributes.
44+
* The supported strategies are {@code size}, {@code time}, {@code size-and-time}, and {@code cron}.
45+
*
46+
* @author hojooo
47+
*/
48+
@Plugin(name = "SpringBootTriggeringPolicy", category = Node.CATEGORY, elementType = "TriggeringPolicy",
49+
deferChildren = true, printObject = true)
50+
public final class SpringBootTriggeringPolicy extends AbstractTriggeringPolicy {
51+
52+
private final TriggeringPolicy delegate;
53+
54+
private SpringBootTriggeringPolicy(TriggeringPolicy delegate) {
55+
this.delegate = delegate;
56+
}
57+
58+
TriggeringPolicy getDelegate() {
59+
return this.delegate;
60+
}
61+
62+
@Override
63+
public void initialize(RollingFileManager manager) {
64+
this.delegate.initialize(manager);
65+
}
66+
67+
@Override
68+
public boolean isTriggeringEvent(LogEvent event) {
69+
return this.delegate.isTriggeringEvent(event);
70+
}
71+
72+
@Override
73+
public void start() {
74+
super.start();
75+
if (this.delegate instanceof LifeCycle lifecycle) {
76+
lifecycle.start();
77+
}
78+
}
79+
80+
@Override
81+
public boolean stop(long timeout, TimeUnit timeUnit) {
82+
setStopping();
83+
boolean result = true;
84+
if (this.delegate instanceof LifeCycle2 lifecycle2) {
85+
result = lifecycle2.stop(timeout, timeUnit);
86+
}
87+
else if (this.delegate instanceof LifeCycle lifecycle) {
88+
lifecycle.stop();
89+
}
90+
setStopped();
91+
return result;
92+
}
93+
94+
@Override
95+
public boolean isStarted() {
96+
if (this.delegate instanceof LifeCycle lifecycle) {
97+
return lifecycle.isStarted();
98+
}
99+
return super.isStarted();
100+
}
101+
102+
@Override
103+
public String toString() {
104+
return "SpringBootTriggeringPolicy{" + this.delegate + "}";
105+
}
106+
107+
@PluginBuilderFactory
108+
public static SpringBootTriggeringPolicyBuilder newBuilder() {
109+
return new SpringBootTriggeringPolicyBuilder();
110+
}
111+
112+
@PluginFactory
113+
public static SpringBootTriggeringPolicy createPolicy(@PluginAttribute("strategy") @Nullable String strategy,
114+
@PluginAttribute("maxFileSize") @Nullable String maxFileSize,
115+
@PluginAttribute("timeInterval") @Nullable Integer timeInterval,
116+
@PluginAttribute("timeModulate") @Nullable Boolean timeModulate,
117+
@PluginAttribute("cronExpression") @Nullable String cronExpression,
118+
@PluginConfiguration Configuration configuration) {
119+
return newBuilder().setStrategy(strategy)
120+
.setMaxFileSize(maxFileSize)
121+
.setTimeInterval(timeInterval)
122+
.setTimeModulate(timeModulate)
123+
.setCronExpression(cronExpression)
124+
.setConfiguration(configuration)
125+
.build();
126+
}
127+
128+
/**
129+
* Builder for {@link SpringBootTriggeringPolicy}.
130+
*/
131+
public static class SpringBootTriggeringPolicyBuilder
132+
implements org.apache.logging.log4j.core.util.Builder<SpringBootTriggeringPolicy> {
133+
134+
private static final String DEFAULT_STRATEGY = "size";
135+
136+
private static final String DEFAULT_MAX_FILE_SIZE = "10MB";
137+
138+
private static final int DEFAULT_TIME_INTERVAL = 1;
139+
140+
private static final String DEFAULT_CRON_EXPRESSION = "0 0 0 * * ?";
141+
142+
@PluginAttribute("strategy")
143+
private @Nullable String strategy;
144+
145+
@PluginAttribute("maxFileSize")
146+
private @Nullable String maxFileSize;
147+
148+
@PluginAttribute("timeInterval")
149+
private @Nullable Integer timeInterval;
150+
151+
@PluginAttribute("timeModulate")
152+
private @Nullable Boolean timeModulate;
153+
154+
@PluginAttribute("cronExpression")
155+
private @Nullable String cronExpression;
156+
157+
@PluginConfiguration
158+
private @Nullable Configuration configuration;
159+
160+
@Override
161+
public SpringBootTriggeringPolicy build() {
162+
// Read strategy from system properties first, then from attributes
163+
String resolvedStrategy = System.getProperty("LOG4J2_ROLLINGPOLICY_STRATEGY");
164+
if (resolvedStrategy == null) {
165+
resolvedStrategy = (this.strategy != null) ? this.strategy : DEFAULT_STRATEGY;
166+
}
167+
TriggeringPolicy policy = switch (resolvedStrategy) {
168+
case "time" -> createTimePolicy();
169+
case "size-and-time" -> CompositeTriggeringPolicy.createPolicy(createSizePolicy(), createTimePolicy());
170+
case "cron" -> createCronPolicy();
171+
case "size" -> createSizePolicy();
172+
default -> throw new IllegalArgumentException(
173+
"Unsupported rolling policy strategy '%s'".formatted(resolvedStrategy));
174+
};
175+
return new SpringBootTriggeringPolicy(policy);
176+
}
177+
178+
private TriggeringPolicy createSizePolicy() {
179+
// Read from system properties first, then from attributes
180+
String size = System.getProperty("LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE");
181+
if (size == null) {
182+
size = (this.maxFileSize != null) ? this.maxFileSize : DEFAULT_MAX_FILE_SIZE;
183+
}
184+
return SizeBasedTriggeringPolicy.createPolicy(size);
185+
}
186+
187+
private TriggeringPolicy createTimePolicy() {
188+
// Read from system properties first, then from attributes
189+
String intervalStr = System.getProperty("LOG4J2_ROLLINGPOLICY_TIME_INTERVAL");
190+
int interval = (intervalStr != null) ? Integer.parseInt(intervalStr)
191+
: (this.timeInterval != null) ? this.timeInterval : DEFAULT_TIME_INTERVAL;
192+
193+
String modulateStr = System.getProperty("LOG4J2_ROLLINGPOLICY_TIME_MODULATE");
194+
boolean modulate = (modulateStr != null) ? Boolean.parseBoolean(modulateStr)
195+
: (this.timeModulate != null) ? this.timeModulate : false;
196+
197+
return TimeBasedTriggeringPolicy.newBuilder().withInterval(interval).withModulate(modulate).build();
198+
}
199+
200+
private TriggeringPolicy createCronPolicy() {
201+
Configuration configuration = Objects.requireNonNull(this.configuration, "configuration must not be null");
202+
203+
// Read from system properties first, then from attributes
204+
String schedule = System.getProperty("LOG4J2_ROLLINGPOLICY_CRON_SCHEDULE");
205+
if (schedule == null) {
206+
schedule = (this.cronExpression != null) ? this.cronExpression : DEFAULT_CRON_EXPRESSION;
207+
}
208+
209+
return CronTriggeringPolicy.createPolicy(configuration, null, schedule);
210+
}
211+
212+
SpringBootTriggeringPolicyBuilder setStrategy(@Nullable String strategy) {
213+
this.strategy = strategy;
214+
return this;
215+
}
216+
217+
SpringBootTriggeringPolicyBuilder setMaxFileSize(@Nullable String maxFileSize) {
218+
this.maxFileSize = maxFileSize;
219+
return this;
220+
}
221+
222+
SpringBootTriggeringPolicyBuilder setTimeInterval(@Nullable Integer timeInterval) {
223+
this.timeInterval = timeInterval;
224+
return this;
225+
}
226+
227+
SpringBootTriggeringPolicyBuilder setTimeModulate(@Nullable Boolean timeModulate) {
228+
this.timeModulate = timeModulate;
229+
return this;
230+
}
231+
232+
SpringBootTriggeringPolicyBuilder setCronExpression(@Nullable String cronExpression) {
233+
this.cronExpression = cronExpression;
234+
return this;
235+
}
236+
237+
SpringBootTriggeringPolicyBuilder setConfiguration(Configuration configuration) {
238+
this.configuration = configuration;
239+
return this;
240+
}
241+
242+
}
243+
244+
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@
3535
<ThresholdFilter level="${sys:FILE_LOG_THRESHOLD:-TRACE}"/>
3636
</Filters>
3737
<Policies>
38-
<SizeBasedTriggeringPolicy size="${sys:LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}"/>
38+
<SpringBootTriggeringPolicy strategy="${sys:LOG4J2_ROLLINGPOLICY_STRATEGY:-size}"
39+
maxFileSize="${sys:LOG4J2_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}"
40+
timeInterval="${sys:LOG4J2_ROLLINGPOLICY_TIME_INTERVAL:-1}"
41+
timeModulate="${sys:LOG4J2_ROLLINGPOLICY_TIME_MODULATE:-false}"
42+
cronExpression="${sys:LOG4J2_ROLLINGPOLICY_CRON_SCHEDULE}"/>
3943
</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}"/>
44+
<DefaultRolloverStrategy max="${sys:LOG4J2_ROLLINGPOLICY_MAX_HISTORY:-7}"/>
4345
</RollingFile>
4446
</Appenders>
4547
<Loggers>

0 commit comments

Comments
 (0)