@@ -99,6 +99,7 @@ public class JettyStart extends Observable implements LifeCycle.Listener {
9999 @ GuardedBy ("this" ) private Optional <Thread > shutdownHookThread = Optional .empty ();
100100 @ GuardedBy ("this" ) private int primaryPort = 8080 ;
101101 @ GuardedBy ("this" ) private boolean webAppStartedSuccessfully = false ;
102+ @ GuardedBy ("this" ) private String webAppStartupFailureDetail = null ;
102103
103104
104105 public static void main (final String [] args ) {
@@ -134,6 +135,23 @@ private static void consoleOut(final String msg) {
134135 System .out .println (msg ); //NOSONAR this has to go to the console
135136 }
136137
138+ private static void consoleErr (final String msg ) {
139+ System .err .println (msg ); //NOSONAR surfaced in Surefire output when test log4j root is OFF
140+ }
141+
142+ private synchronized void recordStartupFailure (final String detail , final Throwable cause ) {
143+ webAppStartedSuccessfully = false ;
144+ webAppStartupFailureDetail = detail ;
145+ if (cause != null ) {
146+ logger .fatal ("Jetty startup failed: {}" , detail , cause );
147+ consoleErr ("Jetty startup failed: " + detail );
148+ cause .printStackTrace (System .err ); //NOSONAR CI diagnostics when log4j is disabled in tests
149+ } else {
150+ logger .fatal ("Jetty startup failed: {}" , detail );
151+ consoleErr ("Jetty startup failed: " + detail );
152+ }
153+ }
154+
137155 public synchronized void run () {
138156 run (true );
139157 }
@@ -246,12 +264,12 @@ public synchronized void run(final String[] args, final Observer observer) {
246264 DatabaseManager .registerDatabase (xmldb );
247265
248266 } catch (final Exception e ) {
249- logger .error ("configuration error: {}" , e .getMessage (), e );
250- e .printStackTrace ();
267+ recordStartupFailure ("configuration error: " + e .getMessage (), e );
251268 return ;
252269 }
253270
254271 try {
272+ webAppStartupFailureDetail = null ;
255273 webAppStartedSuccessfully = false ;
256274 // load jetty configurations
257275 final List <Path > configFiles = getEnabledConfigFiles (jettyConfig );
@@ -349,22 +367,23 @@ public synchronized void run(final String[] args, final Observer observer) {
349367
350368 awaitWebAppContextsStarted (getAllHandlers (server .getHandler ()));
351369 webAppStartedSuccessfully = true ;
370+ webAppStartupFailureDetail = null ;
352371
353372 setChanged ();
354373 notifyObservers (SIGNAL_STARTED );
355374
356375 } catch (final SocketException e ) {
357- webAppStartedSuccessfully = false ;
358- logger .error ("----------------------------------------------------------" );
359- logger .error ("ERROR: Could not bind to port because {}" , e .getMessage ());
360- logger .error (e .toString ());
361- logger .error ("----------------------------------------------------------" );
376+ recordStartupFailure ("Could not bind to port: " + e .getMessage (), e );
362377 setChanged ();
363378 notifyObservers (SIGNAL_ERROR );
364379
365380 } catch (final Exception e ) {
366- webAppStartedSuccessfully = false ;
367- logger .fatal ("An unexpected error occurred, web server can not be started: {}" , e .getMessage (), e );
381+ if (webAppStartupFailureDetail == null ) {
382+ recordStartupFailure (
383+ e .getMessage () != null ? e .getMessage () : e .getClass ().getSimpleName (), e );
384+ } else {
385+ recordStartupFailure (webAppStartupFailureDetail , e );
386+ }
368387 setChanged ();
369388 notifyObservers (SIGNAL_ERROR );
370389 }
@@ -558,7 +577,18 @@ private void awaitWebAppContextsStarted(final List<Handler> handlers) throws Int
558577 }
559578 throw new IllegalStateException (
560579 "Web application context did not become ready within " + timeoutMs + "ms: "
561- + describePendingWebApps (webApps , distributionLayout ));
580+ + describePendingWebApps (webApps , distributionLayout ),
581+ firstUnavailableCause (webApps ));
582+ }
583+
584+ private static Throwable firstUnavailableCause (final List <WebAppContext > webApps ) {
585+ for (final WebAppContext webApp : webApps ) {
586+ final Throwable unavailable = webApp .getUnavailableException ();
587+ if (unavailable != null ) {
588+ return unavailable ;
589+ }
590+ }
591+ return null ;
562592 }
563593
564594 private static boolean isDistributionLayout (final List <WebAppContext > webApps ) {
@@ -592,12 +622,31 @@ private static String describePendingWebApps(final List<WebAppContext> webApps,
592622 details .append (webApp .getContextPath ())
593623 .append (" started=" ).append (webApp .isStarted ())
594624 .append (" available=" ).append (webApp .isAvailable ())
595- .append (" requireAvailable=" ).append (requiresAvailability (webApp , distributionLayout ));
625+ .append (" requireAvailable=" ).append (requiresAvailability (webApp , distributionLayout ))
626+ .append (" war=" ).append (describeWebAppWar (webApp ));
627+ final Throwable unavailable = webApp .getUnavailableException ();
628+ if (unavailable != null ) {
629+ details .append (" unavailableCause=" ).append (unavailable .getClass ().getName ())
630+ .append (": " ).append (unavailable .getMessage ());
631+ }
596632 }
597633 }
598634 return details .isEmpty () ? "unknown" : details .toString ();
599635 }
600636
637+ private static String describeWebAppWar (final WebAppContext webApp ) {
638+ final String war = webApp .getWar ();
639+ if (war != null && !war .isBlank ()) {
640+ return war ;
641+ }
642+ try {
643+ final org .eclipse .jetty .util .resource .Resource baseResource = webApp .getBaseResource ();
644+ return baseResource != null ? String .valueOf (baseResource ) : "null" ;
645+ } catch (final Exception e ) {
646+ return "unresolved(" + e .getMessage () + ")" ;
647+ }
648+ }
649+
601650 private static boolean requiresAvailability (final WebAppContext webApp , final boolean distributionLayout ) {
602651 return !(distributionLayout && "/" .equals (webApp .getContextPath ()));
603652 }
@@ -813,4 +862,13 @@ public synchronized int getPrimaryPort() {
813862 public synchronized boolean isWebAppStartedSuccessfully () {
814863 return webAppStartedSuccessfully ;
815864 }
865+
866+ /**
867+ * When {@link #isWebAppStartedSuccessfully()} is {@code false}, holds the last startup failure
868+ * message for test diagnostics (also printed to {@code System.err} because module test log4j
869+ * configs often set {@code Root level="OFF"}).
870+ */
871+ public synchronized Optional <String > getWebAppStartupFailureDetail () {
872+ return Optional .ofNullable (webAppStartupFailureDetail );
873+ }
816874}
0 commit comments