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:30 UTC

(camel) 05/07: CAMEL-20242: camel-main - Health check should not be as verbose by default.

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 7caafe2cc6f0601c048fb10596a86b279f5acf6d
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Dec 16 16:47:22 2023 +0100

    CAMEL-20242: camel-main - Health check should not be as verbose by default.
---
 .../platform/http/main/MainHttpServer.java         | 55 ++++++++++-------
 .../camel/impl/console/HealthDevConsole.java       | 19 +++---
 .../impl/health/RouteControllerHealthCheck.java    | 70 ++++++++++++++++++++++
 3 files changed, 115 insertions(+), 29 deletions(-)

diff --git a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
index 32e1ff68f8e..840eb824bd0 100644
--- a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
+++ b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
@@ -323,28 +323,30 @@ public class MainHttpServer extends ServiceSupport implements CamelContextAware,
             public void handle(RoutingContext ctx) {
                 ctx.response().putHeader("content-type", "application/json");
 
+                HealthCheckRegistry registry = HealthCheckRegistry.get(camelContext);
+                String level = ctx.request().getParam("exposureLevel");
+                if (level == null) {
+                    level = registry.getExposureLevel();
+                }
+                String includeStackTrace = ctx.request().getParam("stackTrace");
+                String includeData = ctx.request().getParam("data");
+
                 boolean all = ctx.currentRoute() == health;
                 boolean liv = ctx.currentRoute() == live;
                 boolean rdy = ctx.currentRoute() == ready;
 
                 Collection<HealthCheck.Result> res;
                 if (all) {
-                    res = HealthCheckHelper.invoke(camelContext);
+                    res = HealthCheckHelper.invoke(camelContext, level);
                 } else if (liv) {
-                    res = HealthCheckHelper.invokeLiveness(camelContext);
+                    res = HealthCheckHelper.invokeLiveness(camelContext, level);
                 } else {
-                    res = HealthCheckHelper.invokeReadiness(camelContext);
+                    res = HealthCheckHelper.invokeReadiness(camelContext, level);
                 }
 
                 StringBuilder sb = new StringBuilder();
                 sb.append("{\n");
 
-                HealthCheckRegistry registry = HealthCheckRegistry.get(camelContext);
-                String level = ctx.request().getParam("exposureLevel");
-                if (level == null) {
-                    level = registry.getExposureLevel();
-                }
-
                 // are we UP
                 boolean up = HealthCheckHelper.isResultsUp(res, rdy);
 
@@ -354,12 +356,12 @@ public class MainHttpServer extends ServiceSupport implements CamelContextAware,
                 } else if ("full".equals(level)) {
                     // include all details
                     List<HealthCheck.Result> list = new ArrayList<>(res);
-                    healthCheckDetails(sb, list, up);
+                    healthCheckDetails(sb, list, up, level, includeStackTrace, includeData);
                 } else {
                     // include only DOWN details
                     List<HealthCheck.Result> downs = res.stream().filter(r -> r.getState().equals(HealthCheck.State.DOWN))
                             .collect(Collectors.toList());
-                    healthCheckDetails(sb, downs, up);
+                    healthCheckDetails(sb, downs, up, level, includeStackTrace, includeData);
                 }
                 sb.append("}\n");
 
@@ -391,7 +393,9 @@ public class MainHttpServer extends ServiceSupport implements CamelContextAware,
         }
     }
 
-    private static void healthCheckDetails(StringBuilder sb, List<HealthCheck.Result> checks, boolean up) {
+    private static void healthCheckDetails(
+            StringBuilder sb, List<HealthCheck.Result> checks, boolean up, String level, String includeStackTrace,
+            String includeData) {
         healthCheckStatus(sb, up);
 
         if (!checks.isEmpty()) {
@@ -400,7 +404,7 @@ public class MainHttpServer extends ServiceSupport implements CamelContextAware,
             for (int i = 0; i < checks.size(); i++) {
                 HealthCheck.Result d = checks.get(i);
                 sb.append("        {\n");
-                reportHealthCheck(sb, d);
+                reportHealthCheck(sb, d, level, includeStackTrace, includeData);
                 if (i < checks.size() - 1) {
                     sb.append("        },\n");
                 } else {
@@ -411,20 +415,29 @@ public class MainHttpServer extends ServiceSupport implements CamelContextAware,
         }
     }
 
-    private static void reportHealthCheck(StringBuilder sb, HealthCheck.Result d) {
+    private static void reportHealthCheck(
+            StringBuilder sb, HealthCheck.Result d, String level, String includeStackTrace, String includeData) {
         sb.append("            \"name\": \"").append(d.getCheck().getId()).append("\",\n");
-        sb.append("            \"status\": \"").append(d.getState()).append("\",\n");
-        if (d.getError().isPresent()) {
+        sb.append("            \"status\": \"").append(d.getState()).append("\"");
+        if (("full".equals(level) || "true".equals(includeStackTrace)) && d.getError().isPresent()) {
+            // include error message in full exposure
+            sb.append(",\n");
             String msg = allCausedByErrorMessages(d.getError().get());
             sb.append("            \"error-message\": \"").append(msg)
-                    .append("\",\n");
-            sb.append("            \"error-stacktrace\": \"").append(errorStackTrace(d.getError().get()))
-                    .append("\",\n");
+                    .append("\"");
+            if ("true".equals(includeStackTrace)) {
+                sb.append(",\n");
+                sb.append("            \"error-stacktrace\": \"").append(errorStackTrace(d.getError().get()))
+                        .append("\"");
+            }
         }
         if (d.getMessage().isPresent()) {
-            sb.append("            \"message\": \"").append(d.getMessage().get()).append("\",\n");
+            sb.append(",\n");
+            sb.append("            \"message\": \"").append(d.getMessage().get()).append("\"");
         }
-        if (d.getDetails() != null && !d.getDetails().isEmpty()) {
+        // only include data if was enabled
+        if (("true".equals(includeData)) && d.getDetails() != null && !d.getDetails().isEmpty()) {
+            sb.append(",\n");
             // lets use sorted keys
             Iterator<String> it = new TreeSet<>(d.getDetails().keySet()).iterator();
             sb.append("            \"data\": {\n");
diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/HealthDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/HealthDevConsole.java
index 175849de7c2..85e2ab46327 100644
--- a/core/camel-console/src/main/java/org/apache/camel/impl/console/HealthDevConsole.java
+++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/HealthDevConsole.java
@@ -53,17 +53,20 @@ public class HealthDevConsole extends AbstractDevConsole {
                 sb.append(String.format("\n    %s: %s", res.getCheck().getId(), res.getState()));
             } else {
                 if (res.getMessage().isPresent()) {
-                    sb.append(String.format("\n    %s: %s (%s)", res.getCheck().getId(), res.getState(), res.getMessage()));
+                    sb.append(String.format("\n    %s: %s (%s)", res.getCheck().getId(), res.getState(), res.getMessage().get()));
                 } else {
                     sb.append(String.format("\n    %s: %s", res.getCheck().getId(), res.getState()));
                 }
-                Throwable cause = res.getError().orElse(null);
-                if (cause != null) {
-                    StringWriter sw = new StringWriter();
-                    PrintWriter pw = new PrintWriter(sw);
-                    cause.printStackTrace(pw);
-                    sb.append(pw);
-                    sb.append("\n\n");
+                if ("full".equals(exposureLevel)) {
+                    if (res.getError().isPresent()) {
+                        Throwable cause = res.getError().get();
+                        StringWriter sw = new StringWriter();
+                        PrintWriter pw = new PrintWriter(sw);
+                        cause.printStackTrace(pw);
+                        sb.append("\n\n");
+                        sb.append(sw);
+                        sb.append("\n\n");
+                    }
                 }
             }
         });
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
index 16b9b08caed..00639f256b6 100644
--- 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
@@ -16,11 +16,18 @@
  */
 package org.apache.camel.impl.health;
 
+import java.util.Comparator;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 import org.apache.camel.Ordered;
+import org.apache.camel.Route;
 import org.apache.camel.health.HealthCheckResultBuilder;
 import org.apache.camel.spi.RouteController;
+import org.apache.camel.spi.SupervisingRouteController;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.util.backoff.BackOffTimer;
 
 /**
  * Readiness {@link org.apache.camel.health.HealthCheck} for route controller.
@@ -46,6 +53,19 @@ public class RouteControllerHealthCheck extends AbstractHealthCheck {
         if (rc != null) {
             // should only be up if there are no unhealthy routes
             up = !rc.isUnhealthyRoutes();
+            // do we have any details about why we are not up
+            if (!up && rc instanceof SupervisingRouteController src) {
+                Set<Route> routes = new TreeSet<>(Comparator.comparing(Route::getId));
+                routes.addAll(src.getRestartingRoutes());
+                for (Route route : routes) {
+                    builderDetails(builder, src, route, false);
+                }
+                routes = new TreeSet<>(Comparator.comparing(Route::getId));
+                routes.addAll(src.getExhaustedRoutes());
+                for (Route route : routes) {
+                    builderDetails(builder, src, route, true);
+                }
+            }
         }
 
         if (up) {
@@ -56,4 +76,54 @@ public class RouteControllerHealthCheck extends AbstractHealthCheck {
         }
     }
 
+    private void builderDetails(HealthCheckResultBuilder builder, SupervisingRouteController src, Route route, boolean exhausted) {
+        String routeId = route.getRouteId();
+        Throwable cause = src.getRestartException(routeId);
+        if (cause != null) {
+            String status = src.getRouteStatus(routeId).name();
+            String uri = route.getEndpoint().getEndpointBaseUri();
+            uri = URISupport.sanitizeUri(uri);
+
+            BackOffTimer.Task state = src.getRestartingRouteState(routeId);
+            long attempts = state != null ? state.getCurrentAttempts() : 0;
+            long elapsed;
+            long last;
+            long next;
+            // we can only track elapsed/time for active supervised routes
+            elapsed = state != null && BackOffTimer.Task.Status.Active == state.getStatus()
+                    ? state.getCurrentElapsedTime() : 0;
+            last = state != null && BackOffTimer.Task.Status.Active == state.getStatus()
+                    ? state.getLastAttemptTime() : 0;
+            next = state != null && BackOffTimer.Task.Status.Active == state.getStatus()
+                    ? state.getNextAttemptTime() : 0;
+
+            String key = "route." + routeId;
+            builder.detail(key + ".id", routeId);
+            builder.detail(key + ".status", status);
+            builder.detail(key + ".phase", exhausted ? "Exhausted" : "Restarting");
+            builder.detail(key + ".uri", uri);
+            builder.detail(key + ".attempts", attempts);
+            builder.detail(key + ".lastAttempt", last);
+            builder.detail(key + ".nextAttempt", next);
+            builder.detail(key + ".elapsed", elapsed);
+            if (cause.getMessage() != null) {
+                builder.detail(key + ".error", cause.getMessage());
+                // only one exception can be stored so lets just store first found
+                if (builder.error() == null) {
+                    builder.error(cause);
+                    String msg;
+                    if (exhausted) {
+                        msg = String.format("Restarting route: %s is exhausted after %s attempts due %s."
+                                            + " No more attempts will be made and the route is no longer supervised by this route controller and remains as stopped.", routeId, attempts,
+                                cause.getMessage());
+                    } else {
+                        msg = String.format("Failed restarting route: %s attempt: %s due: %s", routeId, attempts,
+                                cause.getMessage());
+                    }
+                    builder.message(msg);
+                }
+            }
+        }
+    }
+
 }