From bc92becfd8d9007950c29dcd8ff9fe40a75f2673 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 12 Sep 2018 15:50:08 -0700 Subject: [PATCH] Allow DeferredLogger to replay and switch loggers Add additional `switchTo` methods to allow a `DeferredLogger` to behave like a regular logger once it has been replayed. This commit also improves thread thread safety within the implementation. Closes gh-14452 --- .../config/ConfigFileApplicationListener.java | 2 +- .../boot/logging/DeferredLog.java | 144 ++++++++++++++---- .../boot/logging/DeferredLogTests.java | 11 +- 3 files changed, 122 insertions(+), 35 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index 418254f6165..198f41c3a66 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -189,7 +189,7 @@ public class ConfigFileApplicationListener } private void onApplicationPreparedEvent(ApplicationEvent event) { - this.logger.replayTo(ConfigFileApplicationListener.class); + this.logger.switchTo(ConfigFileApplicationListener.class); addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java index c30e9b674c1..578b8ee4d1c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -31,36 +31,50 @@ import org.apache.commons.logging.LogFactory; */ public class DeferredLog implements Log { - private List lines = new ArrayList<>(); + private volatile Log destination; + + private final List lines = new ArrayList<>(); @Override public boolean isTraceEnabled() { - return true; + synchronized (this.lines) { + return (this.destination != null) ? this.destination.isTraceEnabled() : true; + } } @Override public boolean isDebugEnabled() { - return true; + synchronized (this.lines) { + return (this.destination != null) ? this.destination.isDebugEnabled() : true; + } } @Override public boolean isInfoEnabled() { - return true; + synchronized (this.lines) { + return (this.destination != null) ? this.destination.isInfoEnabled() : true; + } } @Override public boolean isWarnEnabled() { - return true; + synchronized (this.lines) { + return (this.destination != null) ? this.destination.isWarnEnabled() : true; + } } @Override public boolean isErrorEnabled() { - return true; + synchronized (this.lines) { + return (this.destination != null) ? this.destination.isErrorEnabled() : true; + } } @Override public boolean isFatalEnabled() { - return true; + synchronized (this.lines) { + return (this.destination != null) ? this.destination.isFatalEnabled() : true; + } } @Override @@ -124,24 +138,75 @@ public class DeferredLog implements Log { } private void log(LogLevel level, Object message, Throwable t) { - this.lines.add(new Line(level, message, t)); + synchronized (this.lines) { + if (this.destination != null) { + logTo(this.destination, level, message, t); + } + this.lines.add(new Line(level, message, t)); + } } + /** + * Switch from deferred logging to immediate logging to the specified destination. + * @param destination the new log destination + */ + public void switchTo(Class destination) { + switchTo(LogFactory.getLog(destination)); + } + + /** + * Switch from deferred logging to immediate logging to the specified destination. + * @param destination the new log destination + */ + public void switchTo(Log destination) { + synchronized (this.lines) { + replayTo(destination); + this.destination = destination; + } + } + + /** + * Replay deferred logging to the specified destination. + * @param destination the destination for the deferred log messages + */ public void replayTo(Class destination) { replayTo(LogFactory.getLog(destination)); } + /** + * Replay deferred logging to the specified destination. + * @param destination the destination for the deferred log messages + */ public void replayTo(Log destination) { - for (Line line : this.lines) { - line.replayTo(destination); + synchronized (this.lines) { + for (Line line : this.lines) { + logTo(destination, line.getLevel(), line.getMessage(), + line.getThrowable()); + } + this.lines.clear(); } - this.lines.clear(); } + /** + * Replay from a source log to a destination log when the source is deferred. + * @param source the source logger + * @param destination the destination logger class + * @return the destination + * @deprecated since 2.1.0 in favor of {@link #switchTo(Class)} + */ + @Deprecated public static Log replay(Log source, Class destination) { return replay(source, LogFactory.getLog(destination)); } + /** + * Replay from a source log to a destination log when the source is deferred. + * @param source the source logger + * @param destination the destination logger + * @return the destination + * @deprecated since 2.1.0 in favor of {@link #switchTo(Log)} + */ + @Deprecated public static Log replay(Log source, Log destination) { if (source instanceof DeferredLog) { ((DeferredLog) source).replayTo(destination); @@ -149,6 +214,30 @@ public class DeferredLog implements Log { return destination; } + private static void logTo(Log log, LogLevel level, Object message, + Throwable throwable) { + switch (level) { + case TRACE: + log.trace(message, throwable); + return; + case DEBUG: + log.debug(message, throwable); + return; + case INFO: + log.info(message, throwable); + return; + case WARN: + log.warn(message, throwable); + return; + case ERROR: + log.error(message, throwable); + return; + case FATAL: + log.fatal(message, throwable); + return; + } + } + private static class Line { private final LogLevel level; @@ -163,27 +252,16 @@ public class DeferredLog implements Log { 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; - } + public LogLevel getLevel() { + return this.level; + } + + public Object getMessage() { + return this.message; + } + + public Throwable getThrowable() { + return this.throwable; } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java index 8d639874c6a..9b5adfbef44 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/DeferredLogTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -167,4 +167,13 @@ public class DeferredLogTests { verifyZeroInteractions(log2); } + @Test + public void switchTo() { + this.deferredLog.error(this.message, this.throwable); + this.deferredLog.switchTo(this.log); + this.deferredLog.info("Message2"); + verify(this.log).error(this.message, this.throwable); + verify(this.log).info("Message2", null); + } + }