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 <[email protected]> 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); + } + } + } + } + }
