You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/12/16 16:38:26 UTC

(camel) 01/07: CAMEL-20242: camel-core: RouteController health check to be DOWN during starting routes. Supervising route controller option to be DOWN during restarting phase.

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch rc
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 6311c56ae018c7557357b57cefa325ae8ea3b96a
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Dec 16 12:04:46 2023 +0100

    CAMEL-20242: camel-core: RouteController health check to be DOWN during starting routes. Supervising route controller option to be DOWN during restarting phase.
---
 .../camel/spi/SupervisingRouteController.java      | 14 +++++
 .../engine/DefaultSupervisingRouteController.java  | 32 ++++++++++-
 .../camel/impl/console/RouteControllerConsole.java |  2 +
 .../camel/health-check/route-controller-check      |  2 +
 .../impl/health/RouteControllerHealthCheck.java    | 64 ++++++++++++++++++++++
 .../MainConfigurationPropertiesConfigurer.java     |  6 ++
 .../camel-main-configuration-metadata.json         |  1 +
 core/camel-main/src/main/docs/main.adoc            |  3 +-
 .../camel/main/DefaultConfigurationConfigurer.java |  1 +
 .../camel/main/DefaultConfigurationProperties.java | 30 ++++++++++
 .../org/apache/camel/util/backoff/BackOff.java     |  8 ++-
 .../commands/action/RouteControllerAction.java     | 19 ++++---
 12 files changed, 171 insertions(+), 11 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/SupervisingRouteController.java b/core/camel-api/src/main/java/org/apache/camel/spi/SupervisingRouteController.java
index dabd4edf1c8..4631919cfbc 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/SupervisingRouteController.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/SupervisingRouteController.java
@@ -128,6 +128,18 @@ public interface SupervisingRouteController extends RouteController {
      */
     boolean isUnhealthyOnExhausted();
 
+    boolean isUnhealthyOnRestarting();
+
+    /**
+     * Whether to mark the route as unhealthy (down) when the route failed to initially start, and is being controlled
+     * for restarting (backoff).
+     *
+     * Setting this to true allows health checks to know about this and can report the Camel application as DOWN.
+     *
+     * The default is false.
+     */
+    void setUnhealthyOnRestarting(boolean unhealthyOnRestarting);
+
     /**
      * Return the list of routes that are currently under restarting by this controller.
      *
@@ -161,4 +173,6 @@ public interface SupervisingRouteController extends RouteController {
      */
     Throwable getRestartException(String routeId);
 
+    boolean isStartingRoutes();
+
 }
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java
index 2f0e72ed0e5..8b943f34c20 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSupervisingRouteController.java
@@ -75,6 +75,7 @@ public class DefaultSupervisingRouteController extends DefaultRouteController im
     private final Set<String> nonSupervisedRoutes;
     private final RouteManager routeManager;
     private volatile CamelContextStartupListener listener;
+    private volatile boolean startingRoutes = true; // state during starting routes on bootstrap
     private volatile BackOffTimer timer;
     private volatile ScheduledExecutorService executorService;
     private volatile BackOff backOff;
@@ -88,6 +89,7 @@ public class DefaultSupervisingRouteController extends DefaultRouteController im
     private long backOffMaxAttempts;
     private double backOffMultiplier = 1.0d;
     private boolean unhealthyOnExhausted;
+    private boolean unhealthyOnRestarting;
 
     public DefaultSupervisingRouteController() {
         this.lock = new Object();
@@ -182,6 +184,14 @@ public class DefaultSupervisingRouteController extends DefaultRouteController im
         this.unhealthyOnExhausted = unhealthyOnExhausted;
     }
 
+    public boolean isUnhealthyOnRestarting() {
+        return unhealthyOnRestarting;
+    }
+
+    public void setUnhealthyOnRestarting(boolean unhealthyOnRestarting) {
+        this.unhealthyOnRestarting = unhealthyOnRestarting;
+    }
+
     protected BackOff getBackOff(String id) {
         // currently all routes use the same backoff
         return backOff;
@@ -237,6 +247,11 @@ public class DefaultSupervisingRouteController extends DefaultRouteController im
     // Route management
     // *********************************
 
+    @Override
+    public boolean isStartingRoutes() {
+        return startingRoutes;
+    }
+
     @Override
     public void startRoute(String routeId) throws Exception {
         final Optional<RouteHolder> route = routes.stream().filter(r -> r.getId().equals(routeId)).findFirst();
@@ -457,6 +472,14 @@ public class DefaultSupervisingRouteController extends DefaultRouteController im
     }
 
     private void startSupervisedRoutes() {
+        try {
+            doStartSupervisedRoutes();
+        } finally {
+            startingRoutes = false;
+        }
+    }
+
+    private void doStartSupervisedRoutes() {
         if (!isRunAllowed()) {
             return;
         }
@@ -482,7 +505,7 @@ public class DefaultSupervisingRouteController extends DefaultRouteController im
 
         if (getCamelContext().getStartupSummaryLevel() != StartupSummaryLevel.Off
                 && getCamelContext().getStartupSummaryLevel() != StartupSummaryLevel.Oneline) {
-            // log after first round of attempts
+            // log after first round of attempts (some routes may be scheduled for restart)
             logRouteStartupSummary();
         }
     }
@@ -595,6 +618,13 @@ public class DefaultSupervisingRouteController extends DefaultRouteController im
                         BackOffTimer.Task task = timer.schedule(backOff, context -> {
                             final BackOffTimer.Task state = getBackOffContext(r.getId()).orElse(null);
                             long attempt = state != null ? state.getCurrentAttempts() : 0;
+
+                            if (!getCamelContext().isRunAllowed()) {
+                                // Camel is shutting down so do not attempt to start route
+                                logger.info("Restarting route: {} attempt: {} is cancelled due CamelContext is shutting down", r.getId(), attempt);
+                                return true;
+                            }
+
                             try {
                                 logger.info("Restarting route: {} attempt: {}", r.getId(), attempt);
                                 doStartRoute(r, false, rx -> DefaultSupervisingRouteController.super.startRoute(rx.getId()));
diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteControllerConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteControllerConsole.java
index cacfc0c8f86..c43e1fa1960 100644
--- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteControllerConsole.java
+++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteControllerConsole.java
@@ -76,6 +76,7 @@ public class RouteControllerConsole extends AbstractDevConsole {
             sb.append(String.format("\nBackoff Max Elapsed Time: %d", src.getBackOffMaxElapsedTime()));
             sb.append(String.format("\nBackoff Max Attempts: %d", src.getBackOffMaxAttempts()));
             sb.append(String.format("\nThread Pool Size: %d", src.getThreadPoolSize()));
+            sb.append(String.format("\nUnhealthy On Restarting: %b", src.isUnhealthyOnRestarting()));
             sb.append(String.format("\nUnhealthy On Exhaust: %b", src.isUnhealthyOnExhausted()));
             sb.append("\n\nRoutes:\n");
 
@@ -187,6 +188,7 @@ public class RouteControllerConsole extends AbstractDevConsole {
             root.put("backoffMaxElapsedTime", src.getBackOffMaxElapsedTime());
             root.put("backoffMaxAttempts", src.getBackOffMaxAttempts());
             root.put("threadPoolSize", src.getThreadPoolSize());
+            root.put("unhealthyOnRestarting", src.isUnhealthyOnRestarting());
             root.put("unhealthyOnExhausted", src.isUnhealthyOnExhausted());
             root.put("routes", list);
 
diff --git a/core/camel-health/src/generated/resources/META-INF/services/org/apache/camel/health-check/route-controller-check b/core/camel-health/src/generated/resources/META-INF/services/org/apache/camel/health-check/route-controller-check
new file mode 100644
index 00000000000..d6acdd88860
--- /dev/null
+++ b/core/camel-health/src/generated/resources/META-INF/services/org/apache/camel/health-check/route-controller-check
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.health.RouteControllerHealthCheck
diff --git a/core/camel-health/src/main/java/org/apache/camel/impl/health/RouteControllerHealthCheck.java b/core/camel-health/src/main/java/org/apache/camel/impl/health/RouteControllerHealthCheck.java
new file mode 100644
index 00000000000..35d37c3bbe1
--- /dev/null
+++ b/core/camel-health/src/main/java/org/apache/camel/impl/health/RouteControllerHealthCheck.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *      http://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.apache.camel.impl.health;
+
+import java.util.Map;
+
+import org.apache.camel.Ordered;
+import org.apache.camel.health.HealthCheckResultBuilder;
+import org.apache.camel.spi.RouteController;
+import org.apache.camel.support.service.ServiceHelper;
+
+/**
+ * Readiness {@link org.apache.camel.health.HealthCheck} for route controller.
+ */
+@org.apache.camel.spi.annotations.HealthCheck("route-controller-check")
+public class RouteControllerHealthCheck extends AbstractHealthCheck {
+
+    public RouteControllerHealthCheck() {
+        super("camel", "route-controller");
+    }
+
+    @Override
+    public int getOrder() {
+        // controller should be early
+        return Ordered.HIGHEST + 1000;
+    }
+
+    @Override
+    protected void doCall(HealthCheckResultBuilder builder, Map<String, Object> options) {
+        boolean up = false;
+
+        RouteController rc = getCamelContext().getRouteController();
+        if (rc != null) {
+            // should be down if starting routes as all routes must be complete before ready
+            up = ServiceHelper.isStarted(rc);
+            if (up) {
+                // is starting routes in progress then we should be DOWN
+                up = !rc.isStartingRoutes();
+            }
+        }
+
+        if (up) {
+            builder.up();
+        } else {
+            builder.detail("route.controller", "Starting routes in progress");
+            builder.down();
+        }
+    }
+
+}
diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
index fab74363ba5..35c2a6e7c7a 100644
--- a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
+++ b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
@@ -173,6 +173,8 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp
         case "RouteControllerThreadPoolSize": target.setRouteControllerThreadPoolSize(property(camelContext, int.class, value)); return true;
         case "routecontrollerunhealthyonexhausted":
         case "RouteControllerUnhealthyOnExhausted": target.setRouteControllerUnhealthyOnExhausted(property(camelContext, boolean.class, value)); return true;
+        case "routecontrollerunhealthyonrestarting":
+        case "RouteControllerUnhealthyOnRestarting": target.setRouteControllerUnhealthyOnRestarting(property(camelContext, boolean.class, value)); return true;
         case "routefilterexcludepattern":
         case "RouteFilterExcludePattern": target.setRouteFilterExcludePattern(property(camelContext, java.lang.String.class, value)); return true;
         case "routefilterincludepattern":
@@ -434,6 +436,8 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp
         case "RouteControllerThreadPoolSize": return int.class;
         case "routecontrollerunhealthyonexhausted":
         case "RouteControllerUnhealthyOnExhausted": return boolean.class;
+        case "routecontrollerunhealthyonrestarting":
+        case "RouteControllerUnhealthyOnRestarting": return boolean.class;
         case "routefilterexcludepattern":
         case "RouteFilterExcludePattern": return java.lang.String.class;
         case "routefilterincludepattern":
@@ -696,6 +700,8 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp
         case "RouteControllerThreadPoolSize": return target.getRouteControllerThreadPoolSize();
         case "routecontrollerunhealthyonexhausted":
         case "RouteControllerUnhealthyOnExhausted": return target.isRouteControllerUnhealthyOnExhausted();
+        case "routecontrollerunhealthyonrestarting":
+        case "RouteControllerUnhealthyOnRestarting": return target.isRouteControllerUnhealthyOnRestarting();
         case "routefilterexcludepattern":
         case "RouteFilterExcludePattern": return target.getRouteFilterExcludePattern();
         case "routefilterincludepattern":
diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 5577ce62cb1..1bf93cf0c12 100644
--- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -93,6 +93,7 @@
     { "name": "camel.main.routeControllerSuperviseEnabled", "description": "To enable using supervising route controller which allows Camel to startup and then the controller takes care of starting the routes in a safe manner. This can be used when you want to startup Camel despite a route may otherwise fail fast during startup and cause Camel to fail to startup as well. By delegating the route startup to the supervising route controller then its manages the startup using a background th [...]
     { "name": "camel.main.routeControllerThreadPoolSize", "description": "The number of threads used by the route controller scheduled thread pool that are used for restarting routes. The pool uses 1 thread by default, but you can increase this to allow the controller to concurrently attempt to restart multiple routes in case more than one route has problems starting.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int" },
     { "name": "camel.main.routeControllerUnhealthyOnExhausted", "description": "Whether to mark the route as unhealthy (down) when all restarting attempts (backoff) have failed and the route is not successfully started and the route manager is giving up. Setting this to true allows health checks to know about this and can report the Camel application as DOWN. The default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "bool [...]
+    { "name": "camel.main.routeControllerUnhealthyOnRestarting", "description": "Whether to mark the route as unhealthy (down) when the route failed to initially start, and is being controlled for restarting (backoff). Setting this to true allows health checks to know about this and can report the Camel application as DOWN. The default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.main.routeFilterExcludePattern", "description": "Used for filtering routes routes matching the given pattern, which follows the following rules: - Match by route id - Match by route input endpoint uri The matching is using exact match, by wildcard and regular expression as documented by PatternHelper#matchPattern(String,String) . For example to only include routes which starts with foo in their route id's, use: include=foo&#42; And to exclude routes which starts from [...]
     { "name": "camel.main.routeFilterIncludePattern", "description": "Used for filtering routes matching the given pattern, which follows the following rules: - Match by route id - Match by route input endpoint uri The matching is using exact match, by wildcard and regular expression as documented by PatternHelper#matchPattern(String,String) . For example to only include routes which starts with foo in their route id's, use: include=foo&#42; And to exclude routes which starts from JMS en [...]
     { "name": "camel.main.routesBuilderClasses", "description": "Sets classes names that implement RoutesBuilder .", "sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": "string", "javaType": "java.lang.String" },
diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc
index b4bbd92d60a..be566a90ec8 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -19,7 +19,7 @@ The following tables lists all the options:
 
 // main options: START
 === Camel Main configurations
-The camel.main supports 127 options, which are listed below.
+The camel.main supports 128 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -100,6 +100,7 @@ The camel.main supports 127 options, which are listed below.
 | *camel.main.routeController{zwsp}SuperviseEnabled* | To enable using supervising route controller which allows Camel to startup and then the controller takes care of starting the routes in a safe manner. This can be used when you want to startup Camel despite a route may otherwise fail fast during startup and cause Camel to fail to startup as well. By delegating the route startup to the supervising route controller then its manages the startup using a background thread. The controller  [...]
 | *camel.main.routeController{zwsp}ThreadPoolSize* | The number of threads used by the route controller scheduled thread pool that are used for restarting routes. The pool uses 1 thread by default, but you can increase this to allow the controller to concurrently attempt to restart multiple routes in case more than one route has problems starting. |  | int
 | *camel.main.routeController{zwsp}UnhealthyOnExhausted* | Whether to mark the route as unhealthy (down) when all restarting attempts (backoff) have failed and the route is not successfully started and the route manager is giving up. Setting this to true allows health checks to know about this and can report the Camel application as DOWN. The default is false. | false | boolean
+| *camel.main.routeController{zwsp}UnhealthyOnRestarting* | Whether to mark the route as unhealthy (down) when the route failed to initially start, and is being controlled for restarting (backoff). Setting this to true allows health checks to know about this and can report the Camel application as DOWN. The default is false. | false | boolean
 | *camel.main.routeFilterExclude{zwsp}Pattern* | Used for filtering routes routes matching the given pattern, which follows the following rules: - Match by route id - Match by route input endpoint uri The matching is using exact match, by wildcard and regular expression as documented by PatternHelper#matchPattern(String,String) . For example to only include routes which starts with foo in their route id's, use: include=foo&#42; And to exclude routes which starts from JMS endpoints, use:  [...]
 | *camel.main.routeFilterInclude{zwsp}Pattern* | Used for filtering routes matching the given pattern, which follows the following rules: - Match by route id - Match by route input endpoint uri The matching is using exact match, by wildcard and regular expression as documented by PatternHelper#matchPattern(String,String) . For example to only include routes which starts with foo in their route id's, use: include=foo&#42; And to exclude routes which starts from JMS endpoints, use: exclude [...]
 | *camel.main.routesBuilder{zwsp}Classes* | Sets classes names that implement RoutesBuilder . |  | String
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
index a8c0a980f04..09bd2dceab3 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
@@ -378,6 +378,7 @@ public final class DefaultConfigurationConfigurer {
                 src.setBackOffMultiplier(config.getRouteControllerBackOffMultiplier());
             }
             src.setUnhealthyOnExhausted(config.isRouteControllerUnhealthyOnExhausted());
+            src.setUnhealthyOnRestarting(config.isRouteControllerUnhealthyOnRestarting());
         }
     }
 
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index d65aaecc9d1..d918b39705b 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -152,6 +152,7 @@ public abstract class DefaultConfigurationProperties<T> {
     private long routeControllerBackOffMaxAttempts;
     private double routeControllerBackOffMultiplier;
     private boolean routeControllerUnhealthyOnExhausted;
+    private boolean routeControllerUnhealthyOnRestarting;
     // startup recorder
     @Metadata(enums = "false,off,java-flight-recorder,jfr,logging,backlog")
     private String startupRecorder;
@@ -1654,6 +1655,22 @@ public abstract class DefaultConfigurationProperties<T> {
         this.routeControllerUnhealthyOnExhausted = routeControllerUnhealthyOnExhausted;
     }
 
+    public boolean isRouteControllerUnhealthyOnRestarting() {
+        return routeControllerUnhealthyOnRestarting;
+    }
+
+    /**
+     * Whether to mark the route as unhealthy (down) when the route failed to initially start, and is being controlled
+     * for restarting (backoff).
+     *
+     * Setting this to true allows health checks to know about this and can report the Camel application as DOWN.
+     *
+     * The default is false.
+     */
+    public void setRouteControllerUnhealthyOnRestarting(boolean routeControllerUnhealthyOnRestarting) {
+        this.routeControllerUnhealthyOnRestarting = routeControllerUnhealthyOnRestarting;
+    }
+
     public String getStartupRecorder() {
         return startupRecorder;
     }
@@ -2878,6 +2895,19 @@ public abstract class DefaultConfigurationProperties<T> {
         return (T) this;
     }
 
+    /**
+     * Whether to mark the route as unhealthy (down) when the route failed to initially start, and is being controlled
+     * for restarting (backoff).
+     *
+     * Setting this to true allows health checks to know about this and can report the Camel application as DOWN.
+     *
+     * The default is false.
+     */
+    public T withRouteControllerUnhealthyOnRestarting(boolean routeControllerUnhealthyOnRestarting) {
+        this.routeControllerUnhealthyOnRestarting = routeControllerUnhealthyOnRestarting;
+        return (T) this;
+    }
+
     /**
      * To use startup recorder for capturing execution time during starting Camel. The recorder can be one of: false (or
      * off), logging, backlog, java-flight-recorder (or jfr).
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java b/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java
index 7037a2f15d1..3939d50eed8 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/backoff/BackOff.java
@@ -120,8 +120,12 @@ public final class BackOff {
         if (maxElapsedTime != MAX_DURATION) {
             sb.append(", maxElapsedTime=").append(maxElapsedTime.toMillis());
         }
-        sb.append(", maxAttempts=").append(maxAttempts);
-        sb.append(", multiplier=").append(multiplier);
+        if (maxAttempts != Long.MAX_VALUE) {
+            sb.append(", maxAttempts=").append(maxAttempts);
+        }
+        if (multiplier != DEFAULT_MULTIPLIER) {
+            sb.append(", multiplier=").append(multiplier);
+        }
         sb.append("]");
         return sb.toString();
     }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/RouteControllerAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/RouteControllerAction.java
index 08554025b69..e9f501b6576 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/RouteControllerAction.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/RouteControllerAction.java
@@ -133,7 +133,7 @@ public class RouteControllerAction extends ActionWatchCommand {
                     row.attempts = jt.getLong("attempts");
                     long time = jt.getLong("lastAttempt");
                     if (time > 0) {
-                        row.lastAttempt = TimeUtils.printDuration(time);
+                        row.lastAttempt = TimeUtils.printSince(time);
                     }
                     time = jt.getLong("nextAttempt");
                     if (time > 0) {
@@ -175,7 +175,8 @@ public class RouteControllerAction extends ActionWatchCommand {
                     System.out.printf("\tBackoff Max Elapsed Time: %d%n", jo.getInteger("backoffMaxElapsedTime"));
                     System.out.printf("\tBackoff Max Attempts: %d%n", jo.getInteger("backoffMaxAttempts"));
                     System.out.printf("\tThread Pool Size: %d%n", jo.getInteger("threadPoolSize"));
-                    System.out.printf("\tUnhealthy on Exhaust: %b%n", jo.getBoolean("unhealthyOnExhausted"));
+                    System.out.printf("\tUnhealthy On Restarting: %b%n", jo.getBoolean("unhealthyOnRestarting"));
+                    System.out.printf("\tUnhealthy On Exhaust: %b%n", jo.getBoolean("unhealthyOnExhausted"));
                     System.out.println("\n");
                 }
                 dumpTable(rows, true);
@@ -204,6 +205,7 @@ public class RouteControllerAction extends ActionWatchCommand {
                 new Column().header("STATE").headerAlign(HorizontalAlign.RIGHT).with(this::getSupervising),
                 new Column().visible(supervised).header("ATTEMPT").headerAlign(HorizontalAlign.CENTER)
                         .dataAlign(HorizontalAlign.CENTER).with(this::getAttempts),
+                new Column().visible(supervised).header("ELAPSED").headerAlign(HorizontalAlign.CENTER).with(this::getElapsed),
                 new Column().visible(supervised).header("LAST-AGO").headerAlign(HorizontalAlign.CENTER).with(this::getLast),
                 new Column().visible(supervised).header("ERROR-MESSAGE").headerAlign(HorizontalAlign.LEFT)
                         .dataAlign(HorizontalAlign.LEFT)
@@ -291,11 +293,14 @@ public class RouteControllerAction extends ActionWatchCommand {
 
     protected String getLast(Row r) {
         if (r.lastAttempt != null && !r.lastAttempt.isEmpty()) {
-            String s = r.lastAttempt;
-            if (r.elapsed != null && !r.elapsed.isEmpty()) {
-                s += " (" + r.elapsed + ")";
-            }
-            return s;
+            return r.lastAttempt;
+        }
+        return "";
+    }
+
+    protected String getElapsed(Row r) {
+        if (r.elapsed != null && !r.elapsed.isEmpty()) {
+            return r.elapsed;
         }
         return "";
     }