diff --git a/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java b/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java index 571504560e2..0509d041590 100644 --- a/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java +++ b/spring-core/src/main/java/org/springframework/core/log/LogAccessor.java @@ -120,16 +120,20 @@ public class LogAccessor { * @param message the message to log */ public void fatal(CharSequence message) { - this.log.fatal(message); + if (this.log.isFatalEnabled()) { + this.log.fatal(message); + } } /** - * Log an error with fatal log level. + * Log an exception with fatal log level. * @param cause the exception to log * @param message the message to log */ public void fatal(Throwable cause, CharSequence message) { - this.log.fatal(message, cause); + if (this.log.isFatalEnabled()) { + this.log.fatal(message, cause); + } } /** @@ -137,16 +141,20 @@ public class LogAccessor { * @param message the message to log */ public void error(CharSequence message) { - this.log.error(message); + if (this.log.isErrorEnabled()) { + this.log.error(message); + } } /** - * Log an error with error log level. + * Log an exception with error log level. * @param cause the exception to log * @param message the message to log */ public void error(Throwable cause, CharSequence message) { - this.log.error(message, cause); + if (this.log.isErrorEnabled()) { + this.log.error(message, cause); + } } /** @@ -154,16 +162,20 @@ public class LogAccessor { * @param message the message to log */ public void warn(CharSequence message) { - this.log.warn(message); + if (this.log.isWarnEnabled()) { + this.log.warn(message); + } } /** - * Log an error with warn log level. + * Log an exception with warn log level. * @param cause the exception to log * @param message the message to log */ public void warn(Throwable cause, CharSequence message) { - this.log.warn(message, cause); + if (this.log.isWarnEnabled()) { + this.log.warn(message, cause); + } } /** @@ -171,16 +183,20 @@ public class LogAccessor { * @param message the message to log */ public void info(CharSequence message) { - this.log.info(message); + if (this.log.isInfoEnabled()) { + this.log.info(message); + } } /** - * Log an error with info log level. + * Log an exception with info log level. * @param cause the exception to log * @param message the message to log */ public void info(Throwable cause, CharSequence message) { - this.log.info(message, cause); + if (this.log.isInfoEnabled()) { + this.log.info(message, cause); + } } /** @@ -188,16 +204,20 @@ public class LogAccessor { * @param message the message to log */ public void debug(CharSequence message) { - this.log.debug(message); + if (this.log.isDebugEnabled()) { + this.log.debug(message); + } } /** - * Log an error with debug log level. + * Log an exception with debug log level. * @param cause the exception to log * @param message the message to log */ public void debug(Throwable cause, CharSequence message) { - this.log.debug(message, cause); + if (this.log.isDebugEnabled()) { + this.log.debug(message, cause); + } } /** @@ -205,16 +225,20 @@ public class LogAccessor { * @param message the message to log */ public void trace(CharSequence message) { - this.log.trace(message); + if (this.log.isTraceEnabled()) { + this.log.trace(message); + } } /** - * Log an error with trace log level. + * Log an exception with trace log level. * @param cause the exception to log * @param message the message to log */ public void trace(Throwable cause, CharSequence message) { - this.log.trace(message, cause); + if (this.log.isTraceEnabled()) { + this.log.trace(message, cause); + } } @@ -231,7 +255,7 @@ public class LogAccessor { } /** - * Log an error with fatal log level. + * Log an exception with fatal log level. * @param cause the exception to log * @param messageSupplier a lazy supplier for the message to log */ @@ -252,7 +276,7 @@ public class LogAccessor { } /** - * Log an error with error log level. + * Log an exception with error log level. * @param cause the exception to log * @param messageSupplier a lazy supplier for the message to log */ @@ -273,7 +297,7 @@ public class LogAccessor { } /** - * Log an error with warn log level. + * Log an exception with warn log level. * @param cause the exception to log * @param messageSupplier a lazy supplier for the message to log */ @@ -294,7 +318,7 @@ public class LogAccessor { } /** - * Log an error with info log level. + * Log an exception with info log level. * @param cause the exception to log * @param messageSupplier a lazy supplier for the message to log */ @@ -315,7 +339,7 @@ public class LogAccessor { } /** - * Log an error with debug log level. + * Log an exception with debug log level. * @param cause the exception to log * @param messageSupplier a lazy supplier for the message to log */ @@ -336,7 +360,7 @@ public class LogAccessor { } /** - * Log an error with trace log level. + * Log an exception with trace log level. * @param cause the exception to log * @param messageSupplier a lazy supplier for the message to log */ diff --git a/spring-core/src/test/java/org/springframework/core/log/LogAccessorTests.java b/spring-core/src/test/java/org/springframework/core/log/LogAccessorTests.java new file mode 100644 index 00000000000..524dc4bc4c3 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/log/LogAccessorTests.java @@ -0,0 +1,174 @@ +/* + * Copyright 2002-present 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.core.log; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.logging.Log; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; +import org.mockito.stubbing.Answer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.argumentSet; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link LogAccessor}. + * + * @author Sam Brannen + * @since 7.0.10 + */ +class LogAccessorTests { + + private final Log log = mock(); + + private final LogAccessor logAccessor = new LogAccessor(this.log); + + + @BeforeEach + void renderMessagesWhenDelegatingToLog() { + // Simulate a Log implementation (such as commons-logging's SLF4J bridge) that renders the + // message unconditionally, in order to verify that LogAccessor itself guards against that. + Answer render = invocation -> { + invocation.getArgument(0).toString(); + return null; + }; + doAnswer(render).when(this.log).fatal(any()); + doAnswer(render).when(this.log).fatal(any(), any()); + doAnswer(render).when(this.log).error(any()); + doAnswer(render).when(this.log).error(any(), any()); + doAnswer(render).when(this.log).warn(any()); + doAnswer(render).when(this.log).warn(any(), any()); + doAnswer(render).when(this.log).info(any()); + doAnswer(render).when(this.log).info(any(), any()); + doAnswer(render).when(this.log).debug(any()); + doAnswer(render).when(this.log).debug(any(), any()); + doAnswer(render).when(this.log).trace(any()); + doAnswer(render).when(this.log).trace(any(), any()); + } + + @ParameterizedTest + @FieldSource("levels") + void messageIsNotRenderedWhenLevelIsDisabled(LevelEnabler enabler, LogInvoker invoker) { + enabler.enable(this.log, false); + + AtomicBoolean rendered = new AtomicBoolean(); + invoker.invoke(this.logAccessor, lazyMessage(rendered)); + + assertThat(rendered).isFalse(); + } + + @ParameterizedTest + @FieldSource("levels") + void messageIsRenderedWhenLevelIsEnabled(LevelEnabler enabler, LogInvoker invoker) { + enabler.enable(this.log, true); + + AtomicBoolean rendered = new AtomicBoolean(); + invoker.invoke(this.logAccessor, lazyMessage(rendered)); + + assertThat(rendered).isTrue(); + } + + @ParameterizedTest + @FieldSource("levelsWithCause") + void messageWithCauseIsNotRenderedWhenLevelIsDisabled(LevelEnabler enabler, CauseLogInvoker invoker) { + enabler.enable(this.log, false); + + AtomicBoolean rendered = new AtomicBoolean(); + invoker.invoke(this.logAccessor, new RuntimeException(), lazyMessage(rendered)); + + assertThat(rendered).isFalse(); + } + + @ParameterizedTest + @FieldSource("levelsWithCause") + void messageWithCauseIsRenderedWhenLevelIsEnabled(LevelEnabler enabler, CauseLogInvoker invoker) { + enabler.enable(this.log, true); + + AtomicBoolean rendered = new AtomicBoolean(); + invoker.invoke(this.logAccessor, new RuntimeException(), lazyMessage(rendered)); + + assertThat(rendered).isTrue(); + } + + + private static LogMessage lazyMessage(AtomicBoolean rendered) { + return LogMessage.of(() -> { + rendered.set(true); + return "message"; + }); + } + + @SuppressWarnings("unused") + private static List levels = List.of( + argumentSet("fatal", (LevelEnabler) (log, enabled) -> when(log.isFatalEnabled()).thenReturn(enabled), + (LogInvoker) LogAccessor::fatal), + argumentSet("error", (LevelEnabler) (log, enabled) -> when(log.isErrorEnabled()).thenReturn(enabled), + (LogInvoker) LogAccessor::error), + argumentSet("warn", (LevelEnabler) (log, enabled) -> when(log.isWarnEnabled()).thenReturn(enabled), + (LogInvoker) LogAccessor::warn), + argumentSet("info", (LevelEnabler) (log, enabled) -> when(log.isInfoEnabled()).thenReturn(enabled), + (LogInvoker) LogAccessor::info), + argumentSet("debug", (LevelEnabler) (log, enabled) -> when(log.isDebugEnabled()).thenReturn(enabled), + (LogInvoker) LogAccessor::debug), + argumentSet("trace", (LevelEnabler) (log, enabled) -> when(log.isTraceEnabled()).thenReturn(enabled), + (LogInvoker) LogAccessor::trace) + ); + + @SuppressWarnings("unused") + private static List levelsWithCause = List.of( + argumentSet("fatal", (LevelEnabler) (log, enabled) -> when(log.isFatalEnabled()).thenReturn(enabled), + (CauseLogInvoker) LogAccessor::fatal), + argumentSet("error", (LevelEnabler) (log, enabled) -> when(log.isErrorEnabled()).thenReturn(enabled), + (CauseLogInvoker) LogAccessor::error), + argumentSet("warn", (LevelEnabler) (log, enabled) -> when(log.isWarnEnabled()).thenReturn(enabled), + (CauseLogInvoker) LogAccessor::warn), + argumentSet("info", (LevelEnabler) (log, enabled) -> when(log.isInfoEnabled()).thenReturn(enabled), + (CauseLogInvoker) LogAccessor::info), + argumentSet("debug", (LevelEnabler) (log, enabled) -> when(log.isDebugEnabled()).thenReturn(enabled), + (CauseLogInvoker) LogAccessor::debug), + argumentSet("trace", (LevelEnabler) (log, enabled) -> when(log.isTraceEnabled()).thenReturn(enabled), + (CauseLogInvoker) LogAccessor::trace) + ); + + + @FunctionalInterface + private interface LevelEnabler { + + void enable(Log log, boolean enabled); + } + + @FunctionalInterface + private interface LogInvoker { + + void invoke(LogAccessor logAccessor, CharSequence message); + } + + @FunctionalInterface + private interface CauseLogInvoker { + + void invoke(LogAccessor logAccessor, Throwable cause, CharSequence message); + } + +}