Skip to content

Commit 039fbdf

Browse files
committed
Add DeferredLogFactory support
Add a new `DeferredLogFactory` interface and `DeferredLogs` implementation that can be used when a `DeferredLog` instance is needed but the `switchOver` method should be handled elsewhere. This interface has primarily been added so `EnvironmentPostProcessor` classes will no longer need to implement `ApplicationEventListener` just to switch over their logs. Closes gh-22496
1 parent 9e9eb90 commit 039fbdf

File tree

6 files changed

+331
-14
lines changed

6 files changed

+331
-14
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@
1717
package org.springframework.boot.logging;
1818

1919
import java.util.ArrayList;
20+
import java.util.Iterator;
2021
import java.util.List;
22+
import java.util.function.Supplier;
2123

2224
import org.apache.commons.logging.Log;
2325
import org.apache.commons.logging.LogFactory;
2426

27+
import org.springframework.util.Assert;
28+
2529
/**
2630
* Deferred {@link Log} that can be used to store messages that shouldn't be written until
2731
* the logging system is fully initialized.
@@ -33,7 +37,29 @@ public class DeferredLog implements Log {
3337

3438
private volatile Log destination;
3539

36-
private final List<Line> lines = new ArrayList<>();
40+
private final Supplier<Log> destinationSupplier;
41+
42+
private final Lines lines;
43+
44+
/**
45+
* Create a new {@link DeferredLog} instance.
46+
*/
47+
public DeferredLog() {
48+
this.destinationSupplier = null;
49+
this.lines = new Lines();
50+
}
51+
52+
/**
53+
* Create a new {@link DeferredLog} instance managed by a {@link DeferredLogFactory}.
54+
* @param destination the switch-over destination
55+
* @param lines the lines backing all related deferred logs
56+
* @since 2.4.0
57+
*/
58+
DeferredLog(Supplier<Log> destination, Lines lines) {
59+
Assert.notNull(destination, "Destination must not be null");
60+
this.destinationSupplier = destination;
61+
this.lines = lines;
62+
}
3763

3864
@Override
3965
public boolean isTraceEnabled() {
@@ -143,11 +169,15 @@ private void log(LogLevel level, Object message, Throwable t) {
143169
logTo(this.destination, level, message, t);
144170
}
145171
else {
146-
this.lines.add(new Line(level, message, t));
172+
this.lines.add(this.destinationSupplier, level, message, t);
147173
}
148174
}
149175
}
150176

177+
void switchOver() {
178+
this.destination = this.destinationSupplier.get();
179+
}
180+
151181
/**
152182
* Switch from deferred logging to immediate logging to the specified destination.
153183
* @param destination the new log destination
@@ -213,7 +243,7 @@ public static Log replay(Log source, Log destination) {
213243
return destination;
214244
}
215245

216-
private static void logTo(Log log, LogLevel level, Object message, Throwable throwable) {
246+
static void logTo(Log log, LogLevel level, Object message, Throwable throwable) {
217247
switch (level) {
218248
case TRACE:
219249
log.trace(message, throwable);
@@ -235,20 +265,46 @@ private static void logTo(Log log, LogLevel level, Object message, Throwable thr
235265
}
236266
}
237267

238-
private static class Line {
268+
static class Lines implements Iterable<Line> {
269+
270+
private final List<Line> lines = new ArrayList<>();
271+
272+
void add(Supplier<Log> destinationSupplier, LogLevel level, Object message, Throwable throwable) {
273+
this.lines.add(new Line(destinationSupplier, level, message, throwable));
274+
}
275+
276+
void clear() {
277+
this.lines.clear();
278+
}
279+
280+
@Override
281+
public Iterator<Line> iterator() {
282+
return this.lines.iterator();
283+
}
284+
285+
}
286+
287+
static class Line {
288+
289+
private final Supplier<Log> destinationSupplier;
239290

240291
private final LogLevel level;
241292

242293
private final Object message;
243294

244295
private final Throwable throwable;
245296

246-
Line(LogLevel level, Object message, Throwable throwable) {
297+
Line(Supplier<Log> destinationSupplier, LogLevel level, Object message, Throwable throwable) {
298+
this.destinationSupplier = destinationSupplier;
247299
this.level = level;
248300
this.message = message;
249301
this.throwable = throwable;
250302
}
251303

304+
Log getDestination() {
305+
return this.destinationSupplier.get();
306+
}
307+
252308
LogLevel getLevel() {
253309
return this.level;
254310
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2012-2020 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;
18+
19+
import java.util.function.Supplier;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
24+
/**
25+
* Factory that can be used to create multiple {@link DeferredLog} instances that will
26+
* switch over when appropriate.
27+
*
28+
* @author Phillip Webb
29+
* @since 2.4.0
30+
* @see DeferredLogs
31+
*/
32+
@FunctionalInterface
33+
public interface DeferredLogFactory {
34+
35+
/**
36+
* Create a new {@link DeferredLog} for the given destination.
37+
* @param destination the ultimate log destination
38+
* @return a deferred log instance that will switch to the destination when
39+
* appropriate.
40+
*/
41+
default Log getLog(Class<?> destination) {
42+
return getLog(() -> LogFactory.getLog(destination));
43+
}
44+
45+
/**
46+
* Create a new {@link DeferredLog} for the given destination.
47+
* @param destination the ultimate log destination
48+
* @return a deferred log instance that will switch to the destination when
49+
* appropriate.
50+
*/
51+
default Log getLog(Log destination) {
52+
return getLog(() -> destination);
53+
}
54+
55+
/**
56+
* Create a new {@link DeferredLog} for the given destination.
57+
* @param destination the ultimate log destination
58+
* @return a deferred log instance that will switch to the destination when
59+
* appropriate.
60+
*/
61+
Log getLog(Supplier<Log> destination);
62+
63+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2012-2020 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;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.function.Supplier;
22+
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
25+
26+
import org.springframework.boot.logging.DeferredLog.Line;
27+
import org.springframework.boot.logging.DeferredLog.Lines;
28+
29+
/**
30+
* A {@link DeferredLogFactory} implementation that manages a collection
31+
* {@link DeferredLog} instances.
32+
*
33+
* @author Phillip Webb
34+
* @since 2.4.0
35+
*/
36+
public class DeferredLogs implements DeferredLogFactory {
37+
38+
private final Lines lines = new Lines();
39+
40+
private final List<DeferredLog> loggers = new ArrayList<>();
41+
42+
/**
43+
* Create a new {@link DeferredLog} for the given destination.
44+
* @param destination the ultimate log destination
45+
* @return a deferred log instance that will switch to the destination when
46+
* appropriate.
47+
*/
48+
@Override
49+
public Log getLog(Class<?> destination) {
50+
return getLog(() -> LogFactory.getLog(destination));
51+
}
52+
53+
/**
54+
* Create a new {@link DeferredLog} for the given destination.
55+
* @param destination the ultimate log destination
56+
* @return a deferred log instance that will switch to the destination when
57+
* appropriate.
58+
*/
59+
@Override
60+
public Log getLog(Log destination) {
61+
return getLog(() -> destination);
62+
}
63+
64+
/**
65+
* Create a new {@link DeferredLog} for the given destination.
66+
* @param destination the ultimate log destination
67+
* @return a deferred log instance that will switch to the destination when
68+
* appropriate.
69+
*/
70+
@Override
71+
public Log getLog(Supplier<Log> destination) {
72+
synchronized (this.lines) {
73+
DeferredLog logger = new DeferredLog(destination, this.lines);
74+
this.loggers.add(logger);
75+
return logger;
76+
}
77+
}
78+
79+
/**
80+
* Switch over all deferred logs to their supplied destination.
81+
*/
82+
public void switchOverAll() {
83+
synchronized (this.lines) {
84+
for (Line line : this.lines) {
85+
DeferredLog.logTo(line.getDestination(), line.getLevel(), line.getMessage(), line.getThrowable());
86+
}
87+
for (DeferredLog logger : this.loggers) {
88+
logger.switchOver();
89+
}
90+
this.lines.clear();
91+
}
92+
93+
}
94+
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2012-2020 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;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.junit.jupiter.api.Test;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.mockito.Mockito.mock;
24+
25+
/**
26+
* Tests for {@link DeferredLogFactory}.
27+
*
28+
* @author Phillip Webb
29+
*/
30+
class DeferredLogFactoryTests {
31+
32+
private DeferredLogFactory factory = (supplier) -> this.log = supplier.get();
33+
34+
private Log log;
35+
36+
@Test
37+
void getLogFromClassCreatesLogSupplier() {
38+
this.factory.getLog(DeferredLogFactoryTests.class);
39+
assertThat(this.log).isNotNull();
40+
}
41+
42+
@Test
43+
void getLogFromDestinationCreatesLogSupplier() {
44+
Log log = mock(Log.class);
45+
this.factory.getLog(log);
46+
assertThat(this.log).isSameAs(log);
47+
}
48+
49+
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,10 @@
1616

1717
package org.springframework.boot.logging;
1818

19-
import java.util.List;
20-
2119
import org.apache.commons.logging.Log;
2220
import org.junit.jupiter.api.Test;
2321

22+
import org.springframework.boot.logging.DeferredLog.Lines;
2423
import org.springframework.test.util.ReflectionTestUtils;
2524

2625
import static org.assertj.core.api.Assertions.assertThat;
@@ -171,21 +170,16 @@ void clearsOnReplayTo() {
171170
verifyNoInteractions(log2);
172171
}
173172

174-
@SuppressWarnings("unchecked")
175173
@Test
176174
void switchTo() {
177-
List<String> lines = (List<String>) ReflectionTestUtils.getField(this.deferredLog, "lines");
175+
Lines lines = (Lines) ReflectionTestUtils.getField(this.deferredLog, "lines");
178176
assertThat(lines).isEmpty();
179-
180177
this.deferredLog.error(this.message, this.throwable);
181178
assertThat(lines).hasSize(1);
182-
183179
this.deferredLog.switchTo(this.log);
184180
assertThat(lines).isEmpty();
185-
186181
this.deferredLog.info("Message2");
187182
assertThat(lines).isEmpty();
188-
189183
verify(this.log).error(this.message, this.throwable);
190184
verify(this.log).info("Message2", null);
191185
}

0 commit comments

Comments
 (0)