This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 7c55523d99e CAMEL-21481: camel-jbang - Capture reload error when doing route reloads (#16399) 7c55523d99e is described below commit 7c55523d99ebbbf0fee379228c4a030e8029b13c Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Nov 28 18:09:28 2024 +0100 CAMEL-21481: camel-jbang - Capture reload error when doing route reloads (#16399) * CAMEL-21481: camel-jbang - Capture reload error when doing route reloads --- .../java/org/apache/camel/spi/ReloadStrategy.java | 5 +++ .../camel/impl/console/ContextDevConsole.java | 39 ++++++++++++++++---- .../camel/impl/console/ReloadDevConsole.java | 20 ++++++++++ .../support/DefaultContextReloadStrategy.java | 8 ++++ .../support/FileWatcherResourceReloadStrategy.java | 2 + .../support/ResourceReloadStrategySupport.java | 18 +++++++++ .../camel/support/RouteOnDemandReloadStrategy.java | 2 + .../core/commands/process/CamelContextStatus.java | 43 +++++++++++++++++++--- .../jbang/core/commands/process/ListProcess.java | 31 +++++++++++++++- 9 files changed, 153 insertions(+), 15 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ReloadStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/ReloadStrategy.java index c390f732172..ca32988afb2 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/ReloadStrategy.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/ReloadStrategy.java @@ -48,4 +48,9 @@ public interface ReloadStrategy extends StaticService, CamelContextAware { * Reset the counters. */ void resetCounters(); + + /** + * Gets the last error if reloading failed + */ + Exception getLastError(); } diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java index 5981c5d7a8d..9f6b5ae2d67 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java @@ -16,6 +16,7 @@ */ package org.apache.camel.impl.console; +import java.util.Collections; import java.util.Date; import java.util.Locale; import java.util.Map; @@ -26,8 +27,10 @@ import org.apache.camel.api.management.mbean.ManagedCamelContextMBean; import org.apache.camel.spi.ReloadStrategy; import org.apache.camel.spi.annotations.DevConsole; import org.apache.camel.support.CamelContextHelper; +import org.apache.camel.support.ExceptionHelper; import org.apache.camel.support.console.AbstractDevConsole; import org.apache.camel.util.TimeUtils; +import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; @DevConsole(name = "context", displayName = "CamelContext", description = "Overall information about the CamelContext") @@ -57,9 +60,11 @@ public class ContextDevConsole extends AbstractDevConsole { ManagedCamelContextMBean mb = mcc.getManagedCamelContext(); if (mb != null) { int reloaded = 0; + int reloadedFailed = 0; Set<ReloadStrategy> rs = getCamelContext().hasServices(ReloadStrategy.class); for (ReloadStrategy r : rs) { reloaded += r.getReloadCounter(); + reloadedFailed += r.getFailedCounter(); } String load1 = getLoad1(mb); String load5 = getLoad5(mb); @@ -80,7 +85,7 @@ public class ContextDevConsole extends AbstractDevConsole { } else { sb.append(String.format("\n Idle Since: %s", "")); } - sb.append(String.format("\n Reloaded: %s", reloaded)); + sb.append(String.format("\n Reloaded: %s/%s", reloaded, reloadedFailed)); sb.append(String.format("\n Mean Time: %s", TimeUtils.printDuration(mb.getMeanProcessingTime(), true))); sb.append(String.format("\n Max Time: %s", TimeUtils.printDuration(mb.getMaxProcessingTime(), true))); sb.append(String.format("\n Min Time: %s", TimeUtils.printDuration(mb.getMinProcessingTime(), true))); @@ -131,11 +136,6 @@ public class ContextDevConsole extends AbstractDevConsole { if (mb != null) { JsonObject stats = new JsonObject(); - int reloaded = 0; - Set<ReloadStrategy> rs = getCamelContext().hasServices(ReloadStrategy.class); - for (ReloadStrategy r : rs) { - reloaded += r.getReloadCounter(); - } String load1 = getLoad1(mb); String load5 = getLoad5(mb); String load15 = getLoad15(mb); @@ -155,7 +155,6 @@ public class ContextDevConsole extends AbstractDevConsole { stats.put("remoteExchangesTotal", mb.getRemoteExchangesTotal()); stats.put("remoteExchangesFailed", mb.getRemoteExchangesFailed()); stats.put("remoteExchangesInflight", mb.getRemoteExchangesInflight()); - stats.put("reloaded", reloaded); stats.put("meanProcessingTime", mb.getMeanProcessingTime()); stats.put("maxProcessingTime", mb.getMaxProcessingTime()); stats.put("minProcessingTime", mb.getMinProcessingTime()); @@ -175,6 +174,32 @@ public class ContextDevConsole extends AbstractDevConsole { if (last != null) { stats.put("lastFailedExchangeTimestamp", last.getTime()); } + // reload stats + int reloaded = 0; + int reloadedFailed = 0; + Exception reloadCause = null; + Set<ReloadStrategy> rs = getCamelContext().hasServices(ReloadStrategy.class); + for (ReloadStrategy r : rs) { + reloaded += r.getReloadCounter(); + reloadedFailed += r.getFailedCounter(); + if (reloadCause == null) { + reloadCause = r.getLastError(); + } + } + JsonObject ro = new JsonObject(); + ro.put("reloaded", reloaded); + ro.put("failed", reloadedFailed); + if (reloadCause != null) { + JsonObject eo = new JsonObject(); + eo.put("message", reloadCause.getMessage()); + JsonArray arr2 = new JsonArray(); + final String trace = ExceptionHelper.stackTraceToString(reloadCause); + eo.put("stackTrace", arr2); + Collections.addAll(arr2, trace.split("\n")); + ro.put("lastError", eo); + } + stats.put("reload", ro); + root.put("statistics", stats); } } diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/ReloadDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/ReloadDevConsole.java index 4c899caccb4..a9f6e21a597 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/ReloadDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/ReloadDevConsole.java @@ -16,6 +16,7 @@ */ package org.apache.camel.impl.console; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -24,6 +25,7 @@ import java.util.concurrent.TimeUnit; import org.apache.camel.spi.ReloadStrategy; import org.apache.camel.spi.annotations.DevConsole; +import org.apache.camel.support.ExceptionHelper; import org.apache.camel.support.console.AbstractDevConsole; import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; @@ -71,6 +73,14 @@ public class ReloadDevConsole extends AbstractDevConsole { sb.append(String.format("\nReloadStrategy: %s", r.getClass().getName())); sb.append(String.format("\n Reloaded: %s", r.getReloadCounter())); sb.append(String.format("\n Failed: %s", r.getFailedCounter())); + Exception cause = r.getLastError(); + if (cause != null) { + sb.append(String.format("\n Error Message: %s", cause.getMessage())); + final String stackTrace = ExceptionHelper.stackTraceToString(cause); + sb.append("\n\n"); + sb.append(stackTrace); + sb.append("\n\n"); + } } } if (trigger) { @@ -117,6 +127,16 @@ public class ReloadDevConsole extends AbstractDevConsole { jo.put("className", r.getClass().getName()); jo.put("reloaded", r.getReloadCounter()); jo.put("failed", r.getFailedCounter()); + Throwable cause = r.getLastError(); + if (cause != null) { + JsonObject eo = new JsonObject(); + eo.put("message", cause.getMessage()); + JsonArray arr2 = new JsonArray(); + final String trace = ExceptionHelper.stackTraceToString(cause); + eo.put("stackTrace", arr2); + Collections.addAll(arr2, trace.split("\n")); + jo.put("lastError", eo); + } } } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java index 068060ad069..54ec13fa789 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/DefaultContextReloadStrategy.java @@ -37,6 +37,7 @@ public class DefaultContextReloadStrategy extends ServiceSupport implements Cont private CamelContext camelContext; private int succeeded; private int failed; + private Exception lastError; @Override public CamelContext getCamelContext() { @@ -57,12 +58,14 @@ public class DefaultContextReloadStrategy extends ServiceSupport implements Cont public void onReload(Object source) { LOG.info("Reloading CamelContext ({}) triggered by: {}", camelContext.getName(), source); try { + lastError = null; EventHelper.notifyContextReloading(getCamelContext(), source); reloadProperties(source); reloadRoutes(source); incSucceededCounter(); EventHelper.notifyContextReloaded(getCamelContext(), source); } catch (Exception e) { + lastError = e; incFailedCounter(); LOG.warn("Error reloading CamelContext ({}) due to: {}", camelContext.getName(), e.getMessage(), e); EventHelper.notifyContextReloadFailure(getCamelContext(), source, e); @@ -106,6 +109,11 @@ public class DefaultContextReloadStrategy extends ServiceSupport implements Cont failed = 0; } + @Override + public Exception getLastError() { + return lastError; + } + protected void incSucceededCounter() { succeeded++; } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java index 554c02b92d2..9415307fe7f 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java @@ -305,12 +305,14 @@ public class FileWatcherResourceReloadStrategy extends ResourceReloadStrategySup if (accept) { LOG.debug("Accepted Modified/Created file: {}", name); try { + setLastError(null); // must use file resource loader as we cannot load from classpath Resource resource = PluginHelper.getResourceLoader(getCamelContext()).resolveResource("file:" + name); getResourceReload().onReload(name, resource); incSucceededCounter(); } catch (Exception e) { + setLastError(e); incFailedCounter(); LOG.warn("Error reloading routes from file: {} due to: {}. This exception is ignored.", name, e.getMessage(), e); diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ResourceReloadStrategySupport.java b/core/camel-support/src/main/java/org/apache/camel/support/ResourceReloadStrategySupport.java index 73c55f78f48..75a2ea44a5d 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/ResourceReloadStrategySupport.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/ResourceReloadStrategySupport.java @@ -32,6 +32,7 @@ public abstract class ResourceReloadStrategySupport extends ServiceSupport imple private CamelContext camelContext; private int succeeded; private int failed; + private Exception lastError; @Override public CamelContext getCamelContext() { @@ -77,6 +78,23 @@ public abstract class ResourceReloadStrategySupport extends ServiceSupport imple failed = 0; } + public void setLastError(Exception throwable) { + this.lastError = throwable; + } + + @Override + public Exception getLastError() { + return lastError; + } + + @ManagedOperation(description = "Last error message") + public String lastErrorMessage() { + if (lastError != null) { + return lastError.getMessage(); + } + return null; + } + protected void incSucceededCounter() { succeeded++; } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java index 552b9a64760..a178c48da8e 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/RouteOnDemandReloadStrategy.java @@ -66,6 +66,7 @@ public class RouteOnDemandReloadStrategy extends RouteWatcherReloadStrategy { public void onReload(Object source) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { + setLastError(null); // use bootstrap classloader from camel so its consistent ClassLoader acl = getCamelContext().getApplicationContextClassLoader(); if (acl != null) { @@ -74,6 +75,7 @@ public class RouteOnDemandReloadStrategy extends RouteWatcherReloadStrategy { doOnReload(source); incSucceededCounter(); } catch (Exception e) { + setLastError(e); incFailedCounter(); LOG.warn("Error reloading routes due to {}. This exception is ignored.", e.getMessage(), e); } finally { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatus.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatus.java index 706b2f6c541..0833ea3214f 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatus.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatus.java @@ -104,7 +104,6 @@ public class CamelContextStatus extends ProcessWatchCommand { if (num != null) { row.inflightRemote = num.toString(); } - row.reloaded = stats.get("reloaded").toString(); Object last = stats.get("lastProcessingTime"); if (last != null) { row.last = last.toString(); @@ -128,6 +127,16 @@ public class CamelContextStatus extends ProcessWatchCommand { long time = Long.parseLong(last.toString()); row.sinceLastFailed = TimeUtils.printSince(time); } + row.reloaded = (String) stats.get("reloaded"); // backwards compatible + stats = (Map<String, ?>) stats.get("reload"); + if (stats != null) { + row.reloaded = stats.get("reloaded").toString(); + row.reloadedFailed = stats.get("failed").toString(); + stats = (Map<String, ?>) stats.get("lastError"); + if (stats != null) { + row.reloadedError = stats.get("message").toString(); + } + } } JsonArray array = (JsonArray) root.get("routes"); for (int i = 0; i < array.size(); i++) { @@ -163,9 +172,8 @@ public class CamelContextStatus extends ProcessWatchCommand { new Column().header("PROFILE").dataAlign(HorizontalAlign.LEFT).with(this::getProfile), new Column().header("READY").dataAlign(HorizontalAlign.CENTER).with(r -> r.ready), new Column().header("STATUS").headerAlign(HorizontalAlign.CENTER) - .with(r -> extractState(r.state)), - new Column().header("RELOAD").headerAlign(HorizontalAlign.CENTER) - .with(r -> r.reloaded), + .with(this::getStatus), + new Column().header("RELOAD").with(this::getReloaded), new Column().header("AGE").headerAlign(HorizontalAlign.CENTER).with(r -> r.age), new Column().header("ROUTE").with(this::getRoutes), new Column().header("MSG/S").with(this::getThroughput), @@ -174,8 +182,11 @@ public class CamelContextStatus extends ProcessWatchCommand { new Column().header("FAIL").with(this::getFailed), new Column().header("INFLIGHT").with(this::getInflight), new Column().header("LAST").with(r -> r.last), - new Column().header("DELTA").with(this::getDelta), - new Column().header("SINCE-LAST").with(this::getSinceLast)))); + new Column().header("SINCE-LAST").with(this::getSinceLast), + new Column().header("") // empty header as we only show info when there is an error + .headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT) + .maxWidth(70, OverflowBehaviour.NEWLINE) + .with(this::getDescription)))); } return 0; @@ -221,6 +232,20 @@ public class CamelContextStatus extends ProcessWatchCommand { } } + private String getStatus(Row r) { + if (r.reloadedError != null) { + return "Error"; + } + return extractState(r.state); + } + + private String getDescription(Row r) { + if (r.reloadedError != null) { + return "Reload failed due to: " + r.reloadedError; + } + return null; + } + private String getTotal(Row r) { return r.total; } @@ -289,6 +314,10 @@ public class CamelContextStatus extends ProcessWatchCommand { return s; } + protected String getReloaded(Row row) { + return row.reloaded + "/" + row.reloadedFailed; + } + protected String getRoutes(Row r) { return r.routeStarted + "/" + r.routeTotal; } @@ -305,6 +334,8 @@ public class CamelContextStatus extends ProcessWatchCommand { int routeTotal; int state; String reloaded; + String reloadedFailed; + String reloadedError; String age; long uptime; String throughput; diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcess.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcess.java index 9a5d0f73ef8..db486d1ea33 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcess.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcess.java @@ -96,7 +96,15 @@ public class ListProcess extends ProcessWatchCommand { if (num != null) { row.inflightRemote = num.toString(); } + stats = (Map<String, ?>) stats.get("reload"); + if (stats != null) { + stats = (Map<String, ?>) stats.get("lastError"); + } + if (stats != null) { + row.reloadError = (String) stats.get("message"); + } } + rows.add(row); } }); @@ -115,18 +123,36 @@ public class ListProcess extends ProcessWatchCommand { .with(r -> r.name), new Column().header("READY").dataAlign(HorizontalAlign.CENTER).with(r -> r.ready), new Column().header("STATUS").headerAlign(HorizontalAlign.CENTER) - .with(r -> extractState(r.state)), + .with(this::getStatus), new Column().header("AGE").headerAlign(HorizontalAlign.CENTER).with(r -> r.ago), new Column().header("TOTAL").with(this::getTotal), new Column().header("REMOTE").with(this::getTotalRemote), new Column().header("FAIL").with(this::getFailed), - new Column().header("INFLIGHT").with(this::getInflight)))); + new Column().header("INFLIGHT").with(this::getInflight), + new Column().header("") // empty header as we only show info when there is an error + .headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT) + .maxWidth(70, OverflowBehaviour.NEWLINE) + .with(this::getDescription)))); } } return 0; } + private String getStatus(Row r) { + if (r.reloadError != null) { + return "Error"; + } + return extractState(r.state); + } + + private String getDescription(Row r) { + if (r.reloadError != null) { + return "Reload failed due to: " + r.reloadError; + } + return null; + } + private String getTotal(Row r) { return r.total; } @@ -184,6 +210,7 @@ public class ListProcess extends ProcessWatchCommand { String failedRemote; String inflight; String inflightRemote; + String reloadError; } }