diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java b/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java new file mode 100644 index 000000000000..e2f6c33127d4 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/LoggingCacheErrorHandler.java @@ -0,0 +1,105 @@ +/* + * Copyright 2002-2022 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.cache.interceptor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cache.Cache; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A {@link CacheErrorHandler} implementation that logs error message. Can be + * used when underlying cache errors should be ignored. + * + * @author Adam Ostrožlík + * @author Stephane Nicoll + * @since 5.3.16 + */ +public class LoggingCacheErrorHandler implements CacheErrorHandler { + + private final Log logger; + + private final boolean logStacktrace; + + + /** + * Create an instance with the {@link Log logger} to use. + * @param logger the logger to use + * @param logStacktrace whether to log stack trace + */ + public LoggingCacheErrorHandler(Log logger, boolean logStacktrace) { + Assert.notNull(logger, "Logger must not be null"); + this.logger = logger; + this.logStacktrace = logStacktrace; + } + + /** + * Create an instance that does not log stack traces. + */ + public LoggingCacheErrorHandler() { + this(LogFactory.getLog(LoggingCacheErrorHandler.class), false); + } + + + @Override + public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { + logCacheError(logger, + createMessage(cache, "failed to get entry with key '" + key + "'"), + exception); + } + + @Override + public void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value) { + logCacheError(logger, + createMessage(cache, "failed to put entry with key '" + key + "'"), + exception); + } + + @Override + public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { + logCacheError(logger, + createMessage(cache, "failed to evict entry with key '" + key + "'"), + exception); + } + + @Override + public void handleCacheClearError(RuntimeException exception, Cache cache) { + logCacheError(logger, createMessage(cache, "failed to clear entries"), exception); + } + + /** + * Log the specified message. + * @param logger the logger + * @param message the message + * @param ex the exception + */ + protected void logCacheError(Log logger, String message, RuntimeException ex) { + if (this.logStacktrace) { + logger.warn(message, ex); + } + else { + logger.warn(message); + } + } + + private String createMessage(Cache cache, String reason) { + return String.format("Cache '%s' %s", cache.getName(), reason); + } + +} diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java new file mode 100644 index 000000000000..39525d715ae6 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/LoggingCacheErrorHandlerTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2022 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.cache.interceptor; + +import org.apache.commons.logging.Log; +import org.junit.jupiter.api.Test; + +import org.springframework.cache.support.NoOpCache; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link LoggingCacheErrorHandler}. + * + * @author Adam Ostrožlík + * @author Stephane Nicoll + */ +public class LoggingCacheErrorHandlerTests { + + @Test + void handleGetCacheErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCacheGetError(new RuntimeException(), new NoOpCache("NOOP"), "key"); + verify(logger).warn("Cache 'NOOP' failed to get entry with key 'key'"); + } + + @Test + void handlePutCacheErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCachePutError(new RuntimeException(), new NoOpCache("NOOP"), "key", new Object()); + verify(logger).warn("Cache 'NOOP' failed to put entry with key 'key'"); + } + + @Test + void handleEvictCacheErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCacheEvictError(new RuntimeException(), new NoOpCache("NOOP"), "key"); + verify(logger).warn("Cache 'NOOP' failed to evict entry with key 'key'"); + } + + @Test + void handleClearErrorLogsAppropriateMessage() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, false); + handler.handleCacheClearError(new RuntimeException(), new NoOpCache("NOOP")); + verify(logger).warn("Cache 'NOOP' failed to clear entries"); + } + + @Test + void handleCacheErrorWithStacktrace() { + Log logger = mock(Log.class); + LoggingCacheErrorHandler handler = new LoggingCacheErrorHandler(logger, true); + RuntimeException exception = new RuntimeException(); + handler.handleCacheGetError(exception, new NoOpCache("NOOP"), "key"); + verify(logger).warn("Cache 'NOOP' failed to get entry with key 'key'", exception); + } + +}