diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java index 279e2b09b15..cae9873d803 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java @@ -19,6 +19,10 @@ package org.springframework.boot.availability; import java.util.HashMap; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; import org.springframework.util.Assert; @@ -34,6 +38,8 @@ import org.springframework.util.Assert; public class ApplicationAvailabilityBean implements ApplicationAvailability, ApplicationListener> { + private static final Log logger = LogFactory.getLog(ApplicationAvailability.class); + private final Map, AvailabilityChangeEvent> events = new HashMap<>(); @Override @@ -58,10 +64,31 @@ public class ApplicationAvailabilityBean @Override public void onApplicationEvent(AvailabilityChangeEvent event) { + logStateChange(event); Class stateType = getStateType(event.getState()); this.events.put(stateType, event); } + private void logStateChange(AvailabilityChangeEvent event) { + Class stateType = getStateType(event.getState()); + StringBuilder message = new StringBuilder( + "Application availability state " + stateType.getSimpleName() + " changed"); + AvailabilityChangeEvent lastChangeEvent = getLastChangeEvent(stateType); + if (lastChangeEvent != null) { + message.append(" from " + lastChangeEvent.getState()); + } + message.append(" to " + event.getState()); + if (event.getSource() != null) { + if (event.getSource() instanceof Throwable) { + message.append(": " + event.getSource()); + } + else if (!(event.getSource() instanceof ApplicationEventPublisher)) { + message.append(": " + event.getSource().getClass().getName()); + } + } + logger.info(message); + } + @SuppressWarnings("unchecked") private Class getStateType(AvailabilityState state) { if (state instanceof Enum) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/ApplicationAvailabilityBeanTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/ApplicationAvailabilityBeanTests.java index ac29c170aea..007a56b18bb 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/ApplicationAvailabilityBeanTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/availability/ApplicationAvailabilityBeanTests.java @@ -16,9 +16,14 @@ package org.springframework.boot.availability; +import java.io.IOException; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.testsupport.system.CapturedOutput; +import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -29,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Brian Clozel * @author Phillip Webb */ +@ExtendWith(OutputCaptureExtension.class) class ApplicationAvailabilityBeanTests { private AnnotationConfigApplicationContext context; @@ -87,6 +93,28 @@ class ApplicationAvailabilityBeanTests { assertThat(this.availability.getLastChangeEvent(TestState.class)).isNotNull(); } + @Test + void stateChangesAreLogged(CapturedOutput output) { + AvailabilityChangeEvent.publish(this.context, LivenessState.CORRECT); + assertThat(output).contains("Application availability state LivenessState changed to CORRECT\n"); + AvailabilityChangeEvent.publish(this.context, LivenessState.BROKEN); + assertThat(output).contains("Application availability state LivenessState changed from CORRECT to BROKEN\n"); + } + + @Test + void stateChangesAreLoggedWithExceptionSource(CapturedOutput output) { + AvailabilityChangeEvent.publish(this.context, new IOException("connection error"), LivenessState.BROKEN); + assertThat(output).contains("Application availability state LivenessState changed to BROKEN: " + + "java.io.IOException: connection error\n"); + } + + @Test + void stateChangesAreLoggedWithOtherSource(CapturedOutput output) { + AvailabilityChangeEvent.publish(this.context, new CustomEventSource(), LivenessState.BROKEN); + assertThat(output).contains("Application availability state LivenessState changed to BROKEN: " + + CustomEventSource.class.getName() + "\n"); + } + enum TestState implements AvailabilityState { ONE { @@ -107,4 +135,8 @@ class ApplicationAvailabilityBeanTests { } + static class CustomEventSource { + + } + }