From 5199eefaddf6652d6a38e622db944f78a6a33979 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 29 May 2015 23:39:01 -0700 Subject: [PATCH] Add DeferredLog to support early logging Add a `DeferredLog` utility class that can be used to allow early logging. Output to a DeferredLog is cached until a real Log is provided via the `replayTo` method. Closes gh-3081 --- .../boot/logging/DeferredLog.java | 182 ++++++++++++++++++ .../boot/logging/DeferredLogTests.java | 171 ++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java b/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java new file mode 100644 index 0000000000..b8b85ec871 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java @@ -0,0 +1,182 @@ +/* + * Copyright 2012-2015 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 + * + * http://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 org.apache.commons.logging.Log; + +/** + * Deferred {@link Log} that can be used to store messages that shouldn't be written until + * the logging system is fully initialized. + * + * @author Phillip Webb + * @since 1.3.0 + */ +public class DeferredLog implements Log { + + private List lines = new ArrayList(); + + @Override + public boolean isTraceEnabled() { + return true; + } + + @Override + public boolean isDebugEnabled() { + return true; + } + + @Override + public boolean isInfoEnabled() { + return true; + } + + @Override + public boolean isWarnEnabled() { + return true; + } + + @Override + public boolean isErrorEnabled() { + return true; + } + + @Override + public boolean isFatalEnabled() { + return true; + } + + @Override + public void trace(Object message) { + log(LogLevel.TRACE, message, null); + } + + @Override + public void trace(Object message, Throwable t) { + log(LogLevel.TRACE, message, t); + } + + @Override + public void debug(Object message) { + log(LogLevel.DEBUG, message, null); + } + + @Override + public void debug(Object message, Throwable t) { + log(LogLevel.DEBUG, message, t); + } + + @Override + public void info(Object message) { + log(LogLevel.INFO, message, null); + } + + @Override + public void info(Object message, Throwable t) { + log(LogLevel.INFO, message, t); + } + + @Override + public void warn(Object message) { + log(LogLevel.WARN, message, null); + } + + @Override + public void warn(Object message, Throwable t) { + log(LogLevel.WARN, message, t); + } + + @Override + public void error(Object message) { + log(LogLevel.ERROR, message, null); + } + + @Override + public void error(Object message, Throwable t) { + log(LogLevel.ERROR, message, t); + } + + @Override + public void fatal(Object message) { + log(LogLevel.FATAL, message, null); + } + + @Override + public void fatal(Object message, Throwable t) { + log(LogLevel.FATAL, message, t); + } + + private void log(LogLevel level, Object message, Throwable t) { + this.lines.add(new Line(level, message, t)); + } + + public void replayTo(Log log) { + for (Line line : this.lines) { + line.replayTo(log); + } + this.lines.clear(); + } + + public static Log replay(Log source, Log destination) { + if (source instanceof DeferredLog) { + ((DeferredLog) source).replayTo(destination); + } + return destination; + } + + private static class Line { + + private final LogLevel level; + + private final Object message; + + private final Throwable throwable; + + public Line(LogLevel level, Object message, Throwable throwable) { + this.level = level; + this.message = message; + this.throwable = throwable; + } + + public void replayTo(Log log) { + switch (this.level) { + case TRACE: + log.trace(this.message, this.throwable); + return; + case DEBUG: + log.debug(this.message, this.throwable); + return; + case INFO: + log.info(this.message, this.throwable); + return; + case WARN: + log.warn(this.message, this.throwable); + return; + case ERROR: + log.error(this.message, this.throwable); + return; + case FATAL: + log.fatal(this.message, this.throwable); + return; + } + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java b/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java new file mode 100644 index 0000000000..cafabf7281 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2012-2015 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 + * + * http://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.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Tests for {@link DeferredLog}. + * + * @author Phillip Webb + */ +public class DeferredLogTests { + + private DeferredLog deferredLog = new DeferredLog(); + + private Object message = "Message"; + + private Throwable throwable = new IllegalStateException(); + + private Log log = mock(Log.class); + + @Test + public void isTraceEnabled() throws Exception { + assertThat(this.deferredLog.isTraceEnabled(), equalTo(true)); + } + + @Test + public void isDebugEnabled() throws Exception { + assertThat(this.deferredLog.isDebugEnabled(), equalTo(true)); + } + + @Test + public void isInfoEnabled() throws Exception { + assertThat(this.deferredLog.isInfoEnabled(), equalTo(true)); + } + + @Test + public void isWarnEnabled() throws Exception { + assertThat(this.deferredLog.isWarnEnabled(), equalTo(true)); + } + + @Test + public void isErrorEnabled() throws Exception { + assertThat(this.deferredLog.isErrorEnabled(), equalTo(true)); + } + + @Test + public void isFatalEnabled() throws Exception { + assertThat(this.deferredLog.isFatalEnabled(), equalTo(true)); + } + + @Test + public void trace() throws Exception { + this.deferredLog.trace(this.message); + this.deferredLog.replayTo(this.log); + verify(this.log).trace(this.message, null); + } + + @Test + public void traceWithThrowable() throws Exception { + this.deferredLog.trace(this.message, this.throwable); + this.deferredLog.replayTo(this.log); + verify(this.log).trace(this.message, this.throwable); + } + + @Test + public void debug() throws Exception { + this.deferredLog.debug(this.message); + this.deferredLog.replayTo(this.log); + verify(this.log).debug(this.message, null); + } + + @Test + public void debugWithThrowable() throws Exception { + this.deferredLog.debug(this.message, this.throwable); + this.deferredLog.replayTo(this.log); + verify(this.log).debug(this.message, this.throwable); + } + + @Test + public void info() throws Exception { + this.deferredLog.info(this.message); + this.deferredLog.replayTo(this.log); + verify(this.log).info(this.message, null); + } + + @Test + public void infoWithThrowable() throws Exception { + this.deferredLog.info(this.message, this.throwable); + this.deferredLog.replayTo(this.log); + verify(this.log).info(this.message, this.throwable); + } + + @Test + public void warn() throws Exception { + this.deferredLog.warn(this.message); + this.deferredLog.replayTo(this.log); + verify(this.log).warn(this.message, null); + } + + @Test + public void warnWithThrowable() throws Exception { + this.deferredLog.warn(this.message, this.throwable); + this.deferredLog.replayTo(this.log); + verify(this.log).warn(this.message, this.throwable); + } + + @Test + public void error() throws Exception { + this.deferredLog.error(this.message); + this.deferredLog.replayTo(this.log); + verify(this.log).error(this.message, null); + } + + @Test + public void errorWithThrowable() throws Exception { + this.deferredLog.error(this.message, this.throwable); + this.deferredLog.replayTo(this.log); + verify(this.log).error(this.message, this.throwable); + } + + @Test + public void fatal() throws Exception { + this.deferredLog.fatal(this.message); + this.deferredLog.replayTo(this.log); + verify(this.log).fatal(this.message, null); + } + + @Test + public void fatalWithThrowable() throws Exception { + this.deferredLog.fatal(this.message, this.throwable); + this.deferredLog.replayTo(this.log); + verify(this.log).fatal(this.message, this.throwable); + } + + @Test + public void clearsOnReplayTo() throws Exception { + this.deferredLog.info("1"); + this.deferredLog.fatal("2"); + Log log2 = mock(Log.class); + this.deferredLog.replayTo(this.log); + this.deferredLog.replayTo(log2); + verify(this.log).info("1", null); + verify(this.log).fatal("2", null); + verifyNoMoreInteractions(this.log); + verifyZeroInteractions(log2); + } + +}