diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 0fc3b65d862..d349e3914cf 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -74,6 +74,7 @@ import org.springframework.context.aot.AotApplicationContextInitializer; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; +import org.springframework.core.NativeDetector; import org.springframework.core.OrderComparator; import org.springframework.core.OrderComparator.OrderSourceProvider; import org.springframework.core.Ordered; @@ -832,7 +833,16 @@ public class SpringApplication { // Continue with normal handling of the original failure } if (logger.isErrorEnabled()) { - logger.error("Application run failed", failure); + if (NativeDetector.inNativeImage()) { + // Depending on how early the failure was, logging may not work in a + // native image so we output the stack trace directly to System.out + // instead. + System.out.println("Application run failed"); + failure.printStackTrace(System.out); + } + else { + logger.error("Application run failed", failure); + } registerLoggedException(failure); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 68e65878a22..abf7d36c108 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -71,6 +71,7 @@ import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.boot.convert.ApplicationConversionService; +import org.springframework.boot.testsupport.classpath.ForkedClassPath; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; @@ -749,6 +750,53 @@ class SpringApplicationTests { assertThat(output).contains("Application run failed"); } + @Test + void failureOnTheJvmLogsApplicationRunFailed(CapturedOutput output) { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebApplicationType(WebApplicationType.NONE); + ExitCodeListener exitCodeListener = new ExitCodeListener(); + application.addListeners(exitCodeListener); + @SuppressWarnings("unchecked") + ApplicationListener listener = mock(ApplicationListener.class); + application.addListeners(listener); + ExitStatusException failure = new ExitStatusException(); + willThrow(failure).given(listener).onApplicationEvent(isA(ApplicationReadyEvent.class)); + assertThatExceptionOfType(RuntimeException.class).isThrownBy(application::run); + then(listener).should().onApplicationEvent(isA(ApplicationReadyEvent.class)); + then(listener).should(never()).onApplicationEvent(isA(ApplicationFailedEvent.class)); + assertThat(exitCodeListener.getExitCode()).isEqualTo(11); + // Leading space only happens when logging + assertThat(output).contains(" Application run failed").contains("ExitStatusException"); + } + + @Test + @ForkedClassPath + void failureInANativeImageWritesFailureToSystemOut(CapturedOutput output) { + System.setProperty("org.graalvm.nativeimage.imagecode", "true"); + try { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebApplicationType(WebApplicationType.NONE); + ExitCodeListener exitCodeListener = new ExitCodeListener(); + application.addListeners(exitCodeListener); + @SuppressWarnings("unchecked") + ApplicationListener listener = mock(ApplicationListener.class); + application.addListeners(listener); + ExitStatusException failure = new ExitStatusException(); + willThrow(failure).given(listener).onApplicationEvent(isA(ApplicationReadyEvent.class)); + assertThatExceptionOfType(RuntimeException.class).isThrownBy(application::run); + then(listener).should().onApplicationEvent(isA(ApplicationReadyEvent.class)); + then(listener).should(never()).onApplicationEvent(isA(ApplicationFailedEvent.class)); + assertThat(exitCodeListener.getExitCode()).isEqualTo(11); + // Leading space only happens when logging + assertThat(output).doesNotContain(" Application run failed") + .contains("Application run failed") + .contains("ExitStatusException"); + } + finally { + System.clearProperty("org.graalvm.nativeimage.imagecode"); + } + } + @Test void loadSources() { Class[] sources = { ExampleConfig.class, TestCommandLineRunner.class };