mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
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
This commit is contained in:
parent
9e9eb90d09
commit
039fbdfa7c
@ -17,11 +17,15 @@
|
||||
package org.springframework.boot.logging;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Deferred {@link Log} that can be used to store messages that shouldn't be written until
|
||||
* the logging system is fully initialized.
|
||||
@ -33,7 +37,29 @@ public class DeferredLog implements Log {
|
||||
|
||||
private volatile Log destination;
|
||||
|
||||
private final List<Line> lines = new ArrayList<>();
|
||||
private final Supplier<Log> destinationSupplier;
|
||||
|
||||
private final Lines lines;
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} instance.
|
||||
*/
|
||||
public DeferredLog() {
|
||||
this.destinationSupplier = null;
|
||||
this.lines = new Lines();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} instance managed by a {@link DeferredLogFactory}.
|
||||
* @param destination the switch-over destination
|
||||
* @param lines the lines backing all related deferred logs
|
||||
* @since 2.4.0
|
||||
*/
|
||||
DeferredLog(Supplier<Log> destination, Lines lines) {
|
||||
Assert.notNull(destination, "Destination must not be null");
|
||||
this.destinationSupplier = destination;
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTraceEnabled() {
|
||||
@ -143,11 +169,15 @@ public class DeferredLog implements Log {
|
||||
logTo(this.destination, level, message, t);
|
||||
}
|
||||
else {
|
||||
this.lines.add(new Line(level, message, t));
|
||||
this.lines.add(this.destinationSupplier, level, message, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void switchOver() {
|
||||
this.destination = this.destinationSupplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch from deferred logging to immediate logging to the specified destination.
|
||||
* @param destination the new log destination
|
||||
@ -213,7 +243,7 @@ public class DeferredLog implements Log {
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static void logTo(Log log, LogLevel level, Object message, Throwable throwable) {
|
||||
static void logTo(Log log, LogLevel level, Object message, Throwable throwable) {
|
||||
switch (level) {
|
||||
case TRACE:
|
||||
log.trace(message, throwable);
|
||||
@ -235,7 +265,28 @@ public class DeferredLog implements Log {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Line {
|
||||
static class Lines implements Iterable<Line> {
|
||||
|
||||
private final List<Line> lines = new ArrayList<>();
|
||||
|
||||
void add(Supplier<Log> destinationSupplier, LogLevel level, Object message, Throwable throwable) {
|
||||
this.lines.add(new Line(destinationSupplier, level, message, throwable));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
this.lines.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Line> iterator() {
|
||||
return this.lines.iterator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Line {
|
||||
|
||||
private final Supplier<Log> destinationSupplier;
|
||||
|
||||
private final LogLevel level;
|
||||
|
||||
@ -243,12 +294,17 @@ public class DeferredLog implements Log {
|
||||
|
||||
private final Throwable throwable;
|
||||
|
||||
Line(LogLevel level, Object message, Throwable throwable) {
|
||||
Line(Supplier<Log> destinationSupplier, LogLevel level, Object message, Throwable throwable) {
|
||||
this.destinationSupplier = destinationSupplier;
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
this.throwable = throwable;
|
||||
}
|
||||
|
||||
Log getDestination() {
|
||||
return this.destinationSupplier.get();
|
||||
}
|
||||
|
||||
LogLevel getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Factory that can be used to create multiple {@link DeferredLog} instances that will
|
||||
* switch over when appropriate.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
* @see DeferredLogs
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DeferredLogFactory {
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} for the given destination.
|
||||
* @param destination the ultimate log destination
|
||||
* @return a deferred log instance that will switch to the destination when
|
||||
* appropriate.
|
||||
*/
|
||||
default Log getLog(Class<?> destination) {
|
||||
return getLog(() -> LogFactory.getLog(destination));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} for the given destination.
|
||||
* @param destination the ultimate log destination
|
||||
* @return a deferred log instance that will switch to the destination when
|
||||
* appropriate.
|
||||
*/
|
||||
default Log getLog(Log destination) {
|
||||
return getLog(() -> destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} for the given destination.
|
||||
* @param destination the ultimate log destination
|
||||
* @return a deferred log instance that will switch to the destination when
|
||||
* appropriate.
|
||||
*/
|
||||
Log getLog(Supplier<Log> destination);
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.logging.DeferredLog.Line;
|
||||
import org.springframework.boot.logging.DeferredLog.Lines;
|
||||
|
||||
/**
|
||||
* A {@link DeferredLogFactory} implementation that manages a collection
|
||||
* {@link DeferredLog} instances.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class DeferredLogs implements DeferredLogFactory {
|
||||
|
||||
private final Lines lines = new Lines();
|
||||
|
||||
private final List<DeferredLog> loggers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} for the given destination.
|
||||
* @param destination the ultimate log destination
|
||||
* @return a deferred log instance that will switch to the destination when
|
||||
* appropriate.
|
||||
*/
|
||||
@Override
|
||||
public Log getLog(Class<?> destination) {
|
||||
return getLog(() -> LogFactory.getLog(destination));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} for the given destination.
|
||||
* @param destination the ultimate log destination
|
||||
* @return a deferred log instance that will switch to the destination when
|
||||
* appropriate.
|
||||
*/
|
||||
@Override
|
||||
public Log getLog(Log destination) {
|
||||
return getLog(() -> destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DeferredLog} for the given destination.
|
||||
* @param destination the ultimate log destination
|
||||
* @return a deferred log instance that will switch to the destination when
|
||||
* appropriate.
|
||||
*/
|
||||
@Override
|
||||
public Log getLog(Supplier<Log> destination) {
|
||||
synchronized (this.lines) {
|
||||
DeferredLog logger = new DeferredLog(destination, this.lines);
|
||||
this.loggers.add(logger);
|
||||
return logger;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch over all deferred logs to their supplied destination.
|
||||
*/
|
||||
public void switchOverAll() {
|
||||
synchronized (this.lines) {
|
||||
for (Line line : this.lines) {
|
||||
DeferredLog.logTo(line.getDestination(), line.getLevel(), line.getMessage(), line.getThrowable());
|
||||
}
|
||||
for (DeferredLog logger : this.loggers) {
|
||||
logger.switchOver();
|
||||
}
|
||||
this.lines.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link DeferredLogFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DeferredLogFactoryTests {
|
||||
|
||||
private DeferredLogFactory factory = (supplier) -> this.log = supplier.get();
|
||||
|
||||
private Log log;
|
||||
|
||||
@Test
|
||||
void getLogFromClassCreatesLogSupplier() {
|
||||
this.factory.getLog(DeferredLogFactoryTests.class);
|
||||
assertThat(this.log).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLogFromDestinationCreatesLogSupplier() {
|
||||
Log log = mock(Log.class);
|
||||
this.factory.getLog(log);
|
||||
assertThat(this.log).isSameAs(log);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
@ -16,11 +16,10 @@
|
||||
|
||||
package org.springframework.boot.logging;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.logging.DeferredLog.Lines;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -171,21 +170,16 @@ class DeferredLogTests {
|
||||
verifyNoInteractions(log2);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void switchTo() {
|
||||
List<String> lines = (List<String>) ReflectionTestUtils.getField(this.deferredLog, "lines");
|
||||
Lines lines = (Lines) ReflectionTestUtils.getField(this.deferredLog, "lines");
|
||||
assertThat(lines).isEmpty();
|
||||
|
||||
this.deferredLog.error(this.message, this.throwable);
|
||||
assertThat(lines).hasSize(1);
|
||||
|
||||
this.deferredLog.switchTo(this.log);
|
||||
assertThat(lines).isEmpty();
|
||||
|
||||
this.deferredLog.info("Message2");
|
||||
assertThat(lines).isEmpty();
|
||||
|
||||
verify(this.log).error(this.message, this.throwable);
|
||||
verify(this.log).info("Message2", null);
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link DeferredLogs}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DeferredLogsTests {
|
||||
|
||||
@Test
|
||||
void switchOverAllSwitchesLoggersWithOrderedOutput() {
|
||||
Log log1 = mock(Log.class);
|
||||
Log log2 = mock(Log.class);
|
||||
DeferredLogs loggers = new DeferredLogs();
|
||||
Log dlog1 = loggers.getLog(log1);
|
||||
Log dlog2 = loggers.getLog(log2);
|
||||
dlog1.info("a");
|
||||
dlog2.info("b");
|
||||
dlog1.info("c");
|
||||
dlog2.info("d");
|
||||
verifyNoInteractions(log1, log2);
|
||||
loggers.switchOverAll();
|
||||
InOrder ordered = inOrder(log1, log2);
|
||||
ordered.verify(log1).info("a", null);
|
||||
ordered.verify(log2).info("b", null);
|
||||
ordered.verify(log1).info("c", null);
|
||||
ordered.verify(log2).info("d", null);
|
||||
verifyNoMoreInteractions(log1, log2);
|
||||
dlog1.info("e");
|
||||
dlog2.info("f");
|
||||
ordered.verify(log1).info("e", null);
|
||||
ordered.verify(log2).info("f", null);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user