diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java index ebf13309c9e0..dac07b4766c9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java @@ -18,12 +18,8 @@ import java.util.function.Function; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.springframework.boot.actuate.health.Health.Builder; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Base {@link HealthIndicator} implementations that encapsulates creation of @@ -40,10 +36,6 @@ public abstract class AbstractHealthIndicator implements HealthIndicator { private static final String NO_MESSAGE = null; - private static final String DEFAULT_MESSAGE = "Health check failed"; - - private final Log logger = LogFactory.getLog(getClass()); - private final Function healthCheckFailedMessage; /** @@ -84,17 +76,10 @@ public final Health health() { catch (Exception ex) { builder.down(ex); } - logExceptionIfPresent(builder.getException()); + HealthLogger.logExceptionIfPresent(builder.getException()); return builder.build(); } - private void logExceptionIfPresent(Throwable throwable) { - if (throwable != null && this.logger.isWarnEnabled()) { - String message = (throwable instanceof Exception ex) ? this.healthCheckFailedMessage.apply(ex) : null; - this.logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE, throwable); - } - } - /** * Actual health check logic. * @param builder the {@link Builder} to report health status and details diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractReactiveHealthIndicator.java index 0b6e2c792693..f8dc6fe09495 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractReactiveHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractReactiveHealthIndicator.java @@ -18,12 +18,9 @@ import java.util.function.Function; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Base {@link ReactiveHealthIndicator} implementations that encapsulates creation of @@ -38,10 +35,6 @@ public abstract class AbstractReactiveHealthIndicator implements ReactiveHealthI private static final String NO_MESSAGE = null; - private static final String DEFAULT_MESSAGE = "Health check failed"; - - private final Log logger = LogFactory.getLog(getClass()); - private final Function healthCheckFailedMessage; /** @@ -79,22 +72,15 @@ public final Mono health() { try { Health.Builder builder = new Health.Builder(); Mono result = doHealthCheck(builder).onErrorResume(this::handleFailure); - return result.doOnNext((health) -> logExceptionIfPresent(builder.getException())); + return result.doOnNext((health) -> HealthLogger.logExceptionIfPresent(builder.getException())); } catch (Exception ex) { return handleFailure(ex); } } - private void logExceptionIfPresent(Throwable ex) { - if (ex != null && this.logger.isWarnEnabled()) { - String message = (ex instanceof Exception) ? this.healthCheckFailedMessage.apply(ex) : null; - this.logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE, ex); - } - } - private Mono handleFailure(Throwable ex) { - logExceptionIfPresent(ex); + HealthLogger.logExceptionIfPresent(ex); return Mono.just(new Health.Builder().down(ex).build()); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java index b0a063414fcc..1c8b61d1a59d 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java @@ -55,6 +55,8 @@ public final class Health extends HealthComponent { private final Map details; + private final Throwable exception; + /** * Create a new {@link Health} instance with the specified status and details. * @param builder the Builder to use @@ -63,11 +65,13 @@ private Health(Builder builder) { Assert.notNull(builder, "Builder must not be null"); this.status = builder.status; this.details = Collections.unmodifiableMap(builder.details); + this.exception = builder.exception; } Health(Status status, Map details) { this.status = status; this.details = details; + this.exception = null; } /** @@ -88,6 +92,14 @@ public Map getDetails() { return this.details; } + /** + * Return the exception of the health. + * @return the exception (or {@code null}) + */ + public Throwable getException() { + return this.exception; + } + /** * Return a new instance of this {@link Health} with all {@link #getDetails() details} * removed. diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java index 79445877bd62..07c46b31ac98 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java @@ -169,7 +169,9 @@ private T getAggregateContribution(ApiVersion apiVersion, HealthEndpointGroup gr private T getLoggedHealth(C contributor, String name, boolean showDetails) { Instant start = Instant.now(); try { - return getHealth(contributor, showDetails); + T health = getHealth(contributor, showDetails); + HealthLogger.logExceptionIfPresent(health.getException()); + return health; } finally { if (logger.isWarnEnabled() && this.slowIndicatorLoggingThreshold != null) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthLogger.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthLogger.java new file mode 100644 index 000000000000..eaf9a96a87c9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthLogger.java @@ -0,0 +1,18 @@ +package org.springframework.boot.actuate.health; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.StringUtils; + +public class HealthLogger { + + private static final Log logger = LogFactory.getLog(HealthLogger.class); + private static final String DEFAULT_MESSAGE = "Health check failed"; + + public static void logExceptionIfPresent(Throwable throwable) { + if (throwable != null && logger.isWarnEnabled()) { + String message = (throwable instanceof Exception ex) ? ex.getMessage() : null; + logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE, throwable); + } + } +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthLoggerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthLoggerTests.java new file mode 100644 index 000000000000..95fe62e7df50 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthLoggerTests.java @@ -0,0 +1,36 @@ +package org.springframework.boot.actuate.health; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(OutputCaptureExtension.class) +class HealthLoggerTests { + + private static final Log logger = LogFactory.getLog(HealthLogger.class); + + @Test + void logExceptionIfPresentShouldLogException(CapturedOutput output) { + Exception exception = new Exception("Test exception"); + HealthLogger.logExceptionIfPresent(exception); + assertThat(output).contains("Test exception"); + } + + @Test + void logExceptionIfPresentShouldNotLogWhenNoException(CapturedOutput output) { + HealthLogger.logExceptionIfPresent(null); + assertThat(output).doesNotContain("Health check failed"); + } + + @Test + void logExceptionIfPresentShouldLogDefaultMessageWhenNoMessage(CapturedOutput output) { + Exception exception = new Exception(); + HealthLogger.logExceptionIfPresent(exception); + assertThat(output).contains("Health check failed"); + } +}