Skip to content

Commit 172b3d5

Browse files
committed
Improve diagnostics when native image fails before logging is set up
Closes gh-40429
1 parent 335d42b commit 172b3d5

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.springframework.context.support.AbstractApplicationContext;
7575
import org.springframework.context.support.GenericApplicationContext;
7676
import org.springframework.core.GenericTypeResolver;
77+
import org.springframework.core.NativeDetector;
7778
import org.springframework.core.OrderComparator;
7879
import org.springframework.core.OrderComparator.OrderSourceProvider;
7980
import org.springframework.core.Ordered;
@@ -832,7 +833,16 @@ private void reportFailure(Collection<SpringBootExceptionReporter> exceptionRepo
832833
// Continue with normal handling of the original failure
833834
}
834835
if (logger.isErrorEnabled()) {
835-
logger.error("Application run failed", failure);
836+
if (NativeDetector.inNativeImage()) {
837+
// Depending on how early the failure was, logging may not work in a
838+
// native image so we output the stack trace directly to System.out
839+
// instead.
840+
System.out.println("Application run failed");
841+
failure.printStackTrace(System.out);
842+
}
843+
else {
844+
logger.error("Application run failed", failure);
845+
}
836846
registerLoggedException(failure);
837847
}
838848
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java

+48
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import org.springframework.boot.context.event.ApplicationStartingEvent;
7272
import org.springframework.boot.context.event.SpringApplicationEvent;
7373
import org.springframework.boot.convert.ApplicationConversionService;
74+
import org.springframework.boot.testsupport.classpath.ForkedClassPath;
7475
import org.springframework.boot.testsupport.system.CapturedOutput;
7576
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
7677
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
@@ -749,6 +750,53 @@ void failureInReadyEventListenerCloseApplicationContext(CapturedOutput output) {
749750
assertThat(output).contains("Application run failed");
750751
}
751752

753+
@Test
754+
void failureOnTheJvmLogsApplicationRunFailed(CapturedOutput output) {
755+
SpringApplication application = new SpringApplication(ExampleConfig.class);
756+
application.setWebApplicationType(WebApplicationType.NONE);
757+
ExitCodeListener exitCodeListener = new ExitCodeListener();
758+
application.addListeners(exitCodeListener);
759+
@SuppressWarnings("unchecked")
760+
ApplicationListener<SpringApplicationEvent> listener = mock(ApplicationListener.class);
761+
application.addListeners(listener);
762+
ExitStatusException failure = new ExitStatusException();
763+
willThrow(failure).given(listener).onApplicationEvent(isA(ApplicationReadyEvent.class));
764+
assertThatExceptionOfType(RuntimeException.class).isThrownBy(application::run);
765+
then(listener).should().onApplicationEvent(isA(ApplicationReadyEvent.class));
766+
then(listener).should(never()).onApplicationEvent(isA(ApplicationFailedEvent.class));
767+
assertThat(exitCodeListener.getExitCode()).isEqualTo(11);
768+
// Leading space only happens when logging
769+
assertThat(output).contains(" Application run failed").contains("ExitStatusException");
770+
}
771+
772+
@Test
773+
@ForkedClassPath
774+
void failureInANativeImageWritesFailureToSystemOut(CapturedOutput output) {
775+
System.setProperty("org.graalvm.nativeimage.imagecode", "true");
776+
try {
777+
SpringApplication application = new SpringApplication(ExampleConfig.class);
778+
application.setWebApplicationType(WebApplicationType.NONE);
779+
ExitCodeListener exitCodeListener = new ExitCodeListener();
780+
application.addListeners(exitCodeListener);
781+
@SuppressWarnings("unchecked")
782+
ApplicationListener<SpringApplicationEvent> listener = mock(ApplicationListener.class);
783+
application.addListeners(listener);
784+
ExitStatusException failure = new ExitStatusException();
785+
willThrow(failure).given(listener).onApplicationEvent(isA(ApplicationReadyEvent.class));
786+
assertThatExceptionOfType(RuntimeException.class).isThrownBy(application::run);
787+
then(listener).should().onApplicationEvent(isA(ApplicationReadyEvent.class));
788+
then(listener).should(never()).onApplicationEvent(isA(ApplicationFailedEvent.class));
789+
assertThat(exitCodeListener.getExitCode()).isEqualTo(11);
790+
// Leading space only happens when logging
791+
assertThat(output).doesNotContain(" Application run failed")
792+
.contains("Application run failed")
793+
.contains("ExitStatusException");
794+
}
795+
finally {
796+
System.clearProperty("org.graalvm.nativeimage.imagecode");
797+
}
798+
}
799+
752800
@Test
753801
void loadSources() {
754802
Class<?>[] sources = { ExampleConfig.class, TestCommandLineRunner.class };

0 commit comments

Comments
 (0)