Merge pull request #33774 from mhalbritter
* pr/33774: Log failing calls to health indicators Closes gh-33774
This commit is contained in:
		
						commit
						cbb74673ca
					
				|  | @ -31,6 +31,7 @@ import org.springframework.util.StringUtils; | ||||||
|  * |  * | ||||||
|  * @author Stephane Nicoll |  * @author Stephane Nicoll | ||||||
|  * @author Nikolay Rybak |  * @author Nikolay Rybak | ||||||
|  |  * @author Moritz Halbritter | ||||||
|  * @since 2.0.0 |  * @since 2.0.0 | ||||||
|  */ |  */ | ||||||
| public abstract class AbstractReactiveHealthIndicator implements ReactiveHealthIndicator { | public abstract class AbstractReactiveHealthIndicator implements ReactiveHealthIndicator { | ||||||
|  | @ -75,19 +76,24 @@ public abstract class AbstractReactiveHealthIndicator implements ReactiveHealthI | ||||||
| 
 | 
 | ||||||
| 	@Override | 	@Override | ||||||
| 	public final Mono<Health> health() { | 	public final Mono<Health> health() { | ||||||
|  | 		Mono<Health> result; | ||||||
| 		try { | 		try { | ||||||
| 			return doHealthCheck(new Health.Builder()).onErrorResume(this::handleFailure); | 			result = doHealthCheck(new Health.Builder()).onErrorResume(this::handleFailure); | ||||||
| 		} | 		} | ||||||
| 		catch (Exception ex) { | 		catch (Exception ex) { | ||||||
| 			return handleFailure(ex); | 			result = handleFailure(ex); | ||||||
|  | 		} | ||||||
|  | 		return result.doOnNext((health) -> logExceptionIfPresent(health.getException())); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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<Health> handleFailure(Throwable ex) { | 	private Mono<Health> handleFailure(Throwable ex) { | ||||||
| 		if (this.logger.isWarnEnabled()) { |  | ||||||
| 			String message = this.healthCheckFailedMessage.apply(ex); |  | ||||||
| 			this.logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE, ex); |  | ||||||
| 		} |  | ||||||
| 		return Mono.just(new Health.Builder().down(ex).build()); | 		return Mono.just(new Health.Builder().down(ex).build()); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import java.util.Collections; | ||||||
| import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| 
 | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
| import com.fasterxml.jackson.annotation.JsonInclude; | import com.fasterxml.jackson.annotation.JsonInclude; | ||||||
| import com.fasterxml.jackson.annotation.JsonInclude.Include; | import com.fasterxml.jackson.annotation.JsonInclude.Include; | ||||||
| 
 | 
 | ||||||
|  | @ -46,6 +47,7 @@ import org.springframework.util.Assert; | ||||||
|  * @author Christian Dupuis |  * @author Christian Dupuis | ||||||
|  * @author Phillip Webb |  * @author Phillip Webb | ||||||
|  * @author Michael Pratt |  * @author Michael Pratt | ||||||
|  |  * @author Moritz Halbritter | ||||||
|  * @since 1.1.0 |  * @since 1.1.0 | ||||||
|  */ |  */ | ||||||
| @JsonInclude(Include.NON_EMPTY) | @JsonInclude(Include.NON_EMPTY) | ||||||
|  | @ -55,6 +57,8 @@ public final class Health extends HealthComponent { | ||||||
| 
 | 
 | ||||||
| 	private final Map<String, Object> details; | 	private final Map<String, Object> details; | ||||||
| 
 | 
 | ||||||
|  | 	private final Throwable exception; | ||||||
|  | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * Create a new {@link Health} instance with the specified status and details. | 	 * Create a new {@link Health} instance with the specified status and details. | ||||||
| 	 * @param builder the Builder to use | 	 * @param builder the Builder to use | ||||||
|  | @ -63,11 +67,13 @@ public final class Health extends HealthComponent { | ||||||
| 		Assert.notNull(builder, "Builder must not be null"); | 		Assert.notNull(builder, "Builder must not be null"); | ||||||
| 		this.status = builder.status; | 		this.status = builder.status; | ||||||
| 		this.details = Collections.unmodifiableMap(builder.details); | 		this.details = Collections.unmodifiableMap(builder.details); | ||||||
|  | 		this.exception = builder.exception; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	Health(Status status, Map<String, Object> details) { | 	Health(Status status, Map<String, Object> details) { | ||||||
| 		this.status = status; | 		this.status = status; | ||||||
| 		this.details = details; | 		this.details = details; | ||||||
|  | 		this.exception = null; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
|  | @ -101,6 +107,11 @@ public final class Health extends HealthComponent { | ||||||
| 		return status(getStatus()).build(); | 		return status(getStatus()).build(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@JsonIgnore | ||||||
|  | 	Throwable getException() { | ||||||
|  | 		return this.exception; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Override | 	@Override | ||||||
| 	public boolean equals(Object obj) { | 	public boolean equals(Object obj) { | ||||||
| 		if (obj == this) { | 		if (obj == this) { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,118 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2012-2019 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.boot.actuate.health; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | import org.junit.jupiter.api.extension.ExtendWith; | ||||||
|  | import reactor.core.publisher.Mono; | ||||||
|  | 
 | ||||||
|  | import org.springframework.boot.actuate.health.Health.Builder; | ||||||
|  | import org.springframework.boot.test.system.CapturedOutput; | ||||||
|  | import org.springframework.boot.test.system.OutputCaptureExtension; | ||||||
|  | 
 | ||||||
|  | import static org.assertj.core.api.Assertions.assertThat; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Tests for {@link AbstractReactiveHealthIndicator}. | ||||||
|  |  * | ||||||
|  |  * @author Moritz Halbritter | ||||||
|  |  */ | ||||||
|  | @ExtendWith(OutputCaptureExtension.class) | ||||||
|  | class AbstractReactiveHealthIndicatorTests { | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void healthCheckWhenUpDoesNotLogHealthCheckFailedMessage(CapturedOutput output) { | ||||||
|  | 		Health health = new AbstractReactiveHealthIndicator("Test message") { | ||||||
|  | 			@Override | ||||||
|  | 			protected Mono<Health> doHealthCheck(Builder builder) { | ||||||
|  | 				return Mono.just(builder.up().build()); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		}.health().block(); | ||||||
|  | 		assertThat(health).isNotNull(); | ||||||
|  | 		assertThat(health.getStatus()).isEqualTo(Status.UP); | ||||||
|  | 		assertThat(output).doesNotContain("Test message"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void healthCheckWhenDownWithExceptionThrownDoesNotLogHealthCheckFailedMessage(CapturedOutput output) { | ||||||
|  | 		Health health = new AbstractReactiveHealthIndicator("Test message") { | ||||||
|  | 			@Override | ||||||
|  | 			protected Mono<Health> doHealthCheck(Builder builder) { | ||||||
|  | 				throw new IllegalStateException("Test exception"); | ||||||
|  | 			} | ||||||
|  | 		}.health().block(); | ||||||
|  | 		assertThat(health).isNotNull(); | ||||||
|  | 		assertThat(health.getStatus()).isEqualTo(Status.DOWN); | ||||||
|  | 		assertThat(output).contains("Test message").contains("Test exception"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void healthCheckWhenDownWithExceptionConfiguredDoesNotLogHealthCheckFailedMessage(CapturedOutput output) { | ||||||
|  | 		Health health = new AbstractReactiveHealthIndicator("Test message") { | ||||||
|  | 			@Override | ||||||
|  | 			protected Mono<Health> doHealthCheck(Builder builder) { | ||||||
|  | 				return Mono.just(builder.down().withException(new IllegalStateException("Test exception")).build()); | ||||||
|  | 			} | ||||||
|  | 		}.health().block(); | ||||||
|  | 		assertThat(health).isNotNull(); | ||||||
|  | 		assertThat(health.getStatus()).isEqualTo(Status.DOWN); | ||||||
|  | 		assertThat(output).contains("Test message").contains("Test exception"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void healthCheckWhenDownWithExceptionConfiguredDoesNotLogHealthCheckFailedMessageTwice(CapturedOutput output) { | ||||||
|  | 		Health health = new AbstractReactiveHealthIndicator("Test message") { | ||||||
|  | 			@Override | ||||||
|  | 			protected Mono<Health> doHealthCheck(Builder builder) { | ||||||
|  | 				IllegalStateException ex = new IllegalStateException("Test exception"); | ||||||
|  | 				builder.down().withException(ex); | ||||||
|  | 				throw ex; | ||||||
|  | 			} | ||||||
|  | 		}.health().block(); | ||||||
|  | 		assertThat(health).isNotNull(); | ||||||
|  | 		assertThat(health.getStatus()).isEqualTo(Status.DOWN); | ||||||
|  | 		assertThat(output).contains("Test message").containsOnlyOnce("Test exception"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void healthCheckWhenDownWithExceptionAndNoFailureMessageLogsDefaultMessage(CapturedOutput output) { | ||||||
|  | 		Health health = new AbstractReactiveHealthIndicator() { | ||||||
|  | 			@Override | ||||||
|  | 			protected Mono<Health> doHealthCheck(Builder builder) { | ||||||
|  | 				return Mono.just(builder.down().withException(new IllegalStateException("Test exception")).build()); | ||||||
|  | 			} | ||||||
|  | 		}.health().block(); | ||||||
|  | 		assertThat(health).isNotNull(); | ||||||
|  | 		assertThat(health.getStatus()).isEqualTo(Status.DOWN); | ||||||
|  | 		assertThat(output).contains("Health check failed").contains("Test exception"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void healthCheckWhenDownWithErrorLogsDefaultMessage(CapturedOutput output) { | ||||||
|  | 		Health health = new AbstractReactiveHealthIndicator("Test Message") { | ||||||
|  | 			@Override | ||||||
|  | 			protected Mono<Health> doHealthCheck(Builder builder) { | ||||||
|  | 				return Mono.just(builder.down().withException(new Error("Test error")).build()); | ||||||
|  | 			} | ||||||
|  | 		}.health().block(); | ||||||
|  | 		assertThat(health).isNotNull(); | ||||||
|  | 		assertThat(health.getStatus()).isEqualTo(Status.DOWN); | ||||||
|  | 		assertThat(output).contains("Health check failed").contains("Test error"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue