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

commit af01508713924648345a4a4198debae3db9898df
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Feb 13 13:54:14 2023 +0100

    CAMEL-19040: Backlog tracer - Capture exception and also first/last to know 
better what is input and output from Camel.
---
 .../java/org/apache/camel/spi/BacklogTracer.java   | 10 +++
 .../camel/spi/BacklogTracerEventMessage.java       | 40 +++++++++
 .../camel/impl/debugger/BacklogDebugger.java       |  6 +-
 .../apache/camel/impl/debugger/BacklogTracer.java  | 11 +++
 .../debugger/DefaultBacklogTracerEventMessage.java | 94 +++++++++++++++++++++-
 .../camel/impl/engine/CamelInternalProcessor.java  | 63 ++++++++++++---
 .../camel/management/BacklogTracerFilterTest.java  |  2 +-
 .../apache/camel/management/BacklogTracerTest.java | 42 +++++-----
 .../camel/management/ManagedFromRestGetTest.java   |  4 +-
 .../management/ManagedFromRestPlaceholderTest.java |  4 +-
 .../org/apache/camel/support/MessageHelper.java    | 82 +++++++++++++++++++
 11 files changed, 320 insertions(+), 38 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java
index 580955018b0..9ff19a3aa21 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java
@@ -109,6 +109,16 @@ public interface BacklogTracer {
      */
     void setIncludeExchangeProperties(boolean includeExchangeProperties);
 
+    /**
+     * Trace messages to include exception if the message failed
+     */
+    boolean isIncludeException();
+
+    /**
+     * Trace messages to include exception if the message failed
+     */
+    void setIncludeException(boolean includeException);
+
     /**
      * Filter for tracing by route or node id
      */
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java
 
b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java
index 6b2d66e8a07..95e7da612c7 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java
@@ -31,6 +31,16 @@ public interface BacklogTracerEventMessage {
      */
     long getUid();
 
+    /**
+     * Whether this is a new incoming message and this is the first trace.
+     */
+    boolean isFirst();
+
+    /**
+     * Whether this is the last trace of the message (its complete).
+     */
+    boolean isLast();
+
     /**
      * Timestamp of the traced event
      */
@@ -61,6 +71,36 @@ public interface BacklogTracerEventMessage {
      */
     String getMessageAsJSon();
 
+    /**
+     * Time elapsed for processing the given node (in millis).
+     */
+    long getElapsed();
+
+    /**
+     * Whether the message is done processing the given node
+     */
+    boolean isDone();
+
+    /**
+     * Did the message fail during processing (i.e. was an exception thrown)
+     */
+    boolean isFailed();
+
+    /**
+     * Was there an exception thrown during processing
+     */
+    boolean hasException();
+
+    /**
+     * The exception as XML (exception type, message and stacktrace)
+     */
+    String getExceptionAsXml();
+
+    /**
+     * The exception as JSon (exception type, message and stacktrace)
+     */
+    String getExceptionAsJSon();
+
     /**
      * Dumps the event message as XML using the {@link #ROOT_TAG} as root tag.
      * <p/>
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java
index 18c92cf43cd..cb1e41fc048 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java
@@ -608,7 +608,7 @@ public final class BacklogDebugger extends ServiceSupport {
         suspendedBreakpointMessages.computeIfPresent(
                 nodeId,
                 (nId, message) -> new DefaultBacklogTracerEventMessage(
-                        message.getUid(), message.getTimestamp(), 
message.getRouteId(), message.getToNode(),
+                        false, false, message.getUid(), 
message.getTimestamp(), message.getRouteId(), message.getToNode(),
                         message.getExchangeId(),
                         dumpAsXml(suspendedExchange.getExchange()),
                         dumpAsJSon(suspendedExchange.getExchange())));
@@ -670,7 +670,7 @@ public final class BacklogDebugger extends ServiceSupport {
 
             BacklogTracerEventMessage msg
                     = new DefaultBacklogTracerEventMessage(
-                            uid, timestamp, routeId, toNode, exchangeId, 
messageAsXml, messageAsJSon);
+                            false, false, uid, timestamp, routeId, toNode, 
exchangeId, messageAsXml, messageAsJSon);
             suspendedBreakpointMessages.put(nodeId, msg);
 
             // suspend at this breakpoint
@@ -738,7 +738,7 @@ public final class BacklogDebugger extends ServiceSupport {
 
             BacklogTracerEventMessage msg
                     = new DefaultBacklogTracerEventMessage(
-                            uid, timestamp, routeId, toNode, exchangeId, 
messageAsXml, messageAsJSon);
+                            false, false, uid, timestamp, routeId, toNode, 
exchangeId, messageAsXml, messageAsJSon);
             suspendedBreakpointMessages.put(toNode, msg);
 
             // suspend at this breakpoint
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java
index a89131c84c9..f03e661c748 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java
@@ -61,6 +61,7 @@ public final class BacklogTracer extends ServiceSupport 
implements org.apache.ca
     private boolean bodyIncludeStreams;
     private boolean bodyIncludeFiles = true;
     private boolean includeExchangeProperties = true;
+    private boolean includeException = true;
     // a pattern to filter tracing nodes
     private String tracePattern;
     private String[] patterns;
@@ -233,6 +234,16 @@ public final class BacklogTracer extends ServiceSupport 
implements org.apache.ca
         this.includeExchangeProperties = includeExchangeProperties;
     }
 
+    @Override
+    public boolean isIncludeException() {
+        return includeException;
+    }
+
+    @Override
+    public void setIncludeException(boolean includeException) {
+        this.includeException = includeException;
+    }
+
     @Override
     public String getTracePattern() {
         return tracePattern;
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
index 2b1a7009eb7..7f1bff48b3d 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
@@ -20,6 +20,7 @@ import java.text.SimpleDateFormat;
 import java.util.Map;
 
 import org.apache.camel.spi.BacklogTracerEventMessage;
+import org.apache.camel.util.StopWatch;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsonable;
 import org.apache.camel.util.json.Jsoner;
@@ -29,6 +30,9 @@ import org.apache.camel.util.json.Jsoner;
  */
 public final class DefaultBacklogTracerEventMessage implements 
BacklogTracerEventMessage {
 
+    private final StopWatch watch;
+    private final boolean first;
+    private final boolean last;
     private final long uid;
     private final long timestamp;
     private final String routeId;
@@ -36,9 +40,18 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
     private final String exchangeId;
     private final String messageAsXml;
     private final String messageAsJSon;
-
-    public DefaultBacklogTracerEventMessage(long uid, long timestamp, String 
routeId, String toNode, String exchangeId,
+    private String exceptionAsXml;
+    private String exceptionAsJSon;
+    private long duration;
+    private boolean done;
+    private boolean failed;
+
+    public DefaultBacklogTracerEventMessage(boolean first, boolean last, long 
uid, long timestamp,
+                                            String routeId, String toNode, 
String exchangeId,
                                             String messageAsXml, String 
messageAsJSon) {
+        this.watch = new StopWatch();
+        this.first = first;
+        this.last = last;
         this.uid = uid;
         this.timestamp = timestamp;
         this.routeId = routeId;
@@ -48,11 +61,29 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
         this.messageAsJSon = messageAsJSon;
     }
 
+    /**
+     * Callback when the message has been processed at the given node
+     */
+    public void doneProcessing() {
+        done = true;
+        duration = watch.taken();
+    }
+
     @Override
     public long getUid() {
         return uid;
     }
 
+    @Override
+    public boolean isFirst() {
+        return first;
+    }
+
+    @Override
+    public boolean isLast() {
+        return last;
+    }
+
     @Override
     public long getTimestamp() {
         return timestamp;
@@ -83,6 +114,43 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
         return messageAsJSon;
     }
 
+    @Override
+    public long getElapsed() {
+        return done ? duration : watch.taken();
+    }
+
+    @Override
+    public boolean isDone() {
+        return done;
+    }
+
+    public boolean isFailed() {
+        return hasException();
+    }
+
+    @Override
+    public boolean hasException() {
+        return exceptionAsXml != null || exceptionAsJSon != null;
+    }
+
+    @Override
+    public String getExceptionAsXml() {
+        return exceptionAsXml;
+    }
+
+    public void setExceptionAsXml(String exceptionAsXml) {
+        this.exceptionAsXml = exceptionAsXml;
+    }
+
+    @Override
+    public String getExceptionAsJSon() {
+        return exceptionAsJSon;
+    }
+
+    public void setExceptionAsJSon(String exceptionAsJSon) {
+        this.exceptionAsJSon = exceptionAsJSon;
+    }
+
     @Override
     public String toString() {
         return "DefaultBacklogTracerEventMessage[" + exchangeId + " at " + 
toNode + "]";
@@ -105,8 +173,13 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
         StringBuilder sb = new StringBuilder();
         sb.append(prefix).append("<").append(ROOT_TAG).append(">\n");
         sb.append(prefix).append("  <uid>").append(uid).append("</uid>\n");
+        sb.append(prefix).append("  
<first>").append(first).append("</first>\n");
+        sb.append(prefix).append("  <last>").append(last).append("</last>\n");
         String ts = new SimpleDateFormat(TIMESTAMP_FORMAT).format(timestamp);
         sb.append(prefix).append("  
<timestamp>").append(ts).append("</timestamp>\n");
+        sb.append(prefix).append("  
<elapsed>").append(getElapsed()).append("</elapsed>\n");
+        sb.append(prefix).append("  
<done>").append(isDone()).append("</done>\n");
+        sb.append(prefix).append("  
<failed>").append(isFailed()).append("</failed>\n");
         // route id is optional and we then use an empty value for no route id
         sb.append(prefix).append("  <routeId>").append(routeId != null ? 
routeId : "").append("</routeId>\n");
         if (toNode != null) {
@@ -117,6 +190,9 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
         }
         sb.append(prefix).append("  
<exchangeId>").append(exchangeId).append("</exchangeId>\n");
         sb.append(prefix).append(messageAsXml).append("\n");
+        if (exceptionAsXml != null) {
+            sb.append(prefix).append(exceptionAsXml).append("\n");
+        }
         sb.append(prefix).append("</").append(ROOT_TAG).append(">");
         return sb.toString();
     }
@@ -135,6 +211,8 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
     public Map<String, Object> asJSon() {
         JsonObject jo = new JsonObject();
         jo.put("uid", uid);
+        jo.put("first", first);
+        jo.put("last", last);
         if (routeId != null) {
             jo.put("routeId", routeId);
         }
@@ -144,6 +222,9 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
         if (timestamp > 0) {
             jo.put("timestamp", timestamp);
         }
+        jo.put("elapsed", getElapsed());
+        jo.put("done", isDone());
+        jo.put("failed", isFailed());
         try {
             // parse back to json object and avoid double message root
             JsonObject msg = (JsonObject) Jsoner.deserialize(messageAsJSon);
@@ -151,6 +232,15 @@ public final class DefaultBacklogTracerEventMessage 
implements BacklogTracerEven
         } catch (Exception e) {
             // ignore
         }
+        if (exceptionAsJSon != null) {
+            try {
+                // parse back to json object and avoid double message root
+                JsonObject msg = (JsonObject) 
Jsoner.deserialize(exceptionAsJSon);
+                jo.put("exception", msg.get("exception"));
+            } catch (Exception e) {
+                // ignore
+            }
+        }
         return jo;
     }
 }
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
index d95c803f1d1..715afafd99f 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java
@@ -563,7 +563,8 @@ public class CamelInternalProcessor extends 
DelegateAsyncProcessor implements In
     /**
      * Advice to execute the {@link BacklogTracer} if enabled.
      */
-    public static final class BacklogTracerAdvice implements 
CamelInternalProcessorAdvice, Ordered {
+    public static final class BacklogTracerAdvice
+            implements 
CamelInternalProcessorAdvice<DefaultBacklogTracerEventMessage>, Ordered {
 
         private final BacklogTracer backlogTracer;
         private final NamedNode processorDefinition;
@@ -579,7 +580,7 @@ public class CamelInternalProcessor extends 
DelegateAsyncProcessor implements In
         }
 
         @Override
-        public Object before(Exchange exchange) throws Exception {
+        public DefaultBacklogTracerEventMessage before(Exchange exchange) 
throws Exception {
             if (backlogTracer.shouldTrace(processorDefinition, exchange)) {
                 long timestamp = System.currentTimeMillis();
                 String toNode = processorDefinition.getId();
@@ -596,28 +597,72 @@ public class CamelInternalProcessor extends 
DelegateAsyncProcessor implements In
                 String routeId = routeDefinition != null ? 
routeDefinition.getRouteId() : null;
                 if (first) {
                     long created = exchange.getCreated();
-                    DefaultBacklogTracerEventMessage pseudo = new 
DefaultBacklogTracerEventMessage(
-                            backlogTracer.incrementTraceCounter(), created, 
routeId, null, exchangeId, messageAsXml,
+                    DefaultBacklogTracerEventMessage pseudoFirst = new 
DefaultBacklogTracerEventMessage(
+                            true, false, 
backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId,
+                            messageAsXml,
                             messageAsJSon);
-                    backlogTracer.traceEvent(pseudo);
+                    backlogTracer.traceEvent(pseudoFirst);
+                    exchange.adapt(ExtendedExchange.class).addOnCompletion(new 
SynchronizationAdapter() {
+                        @Override
+                        public void onDone(Exchange exchange) {
+                            // create pseudo last
+                            String routeId = routeDefinition != null ? 
routeDefinition.getRouteId() : null;
+                            String exchangeId = exchange.getExchangeId();
+                            boolean includeExchangeProperties = 
backlogTracer.isIncludeExchangeProperties();
+                            long created = exchange.getCreated();
+                            String messageAsXml = 
MessageHelper.dumpAsXml(exchange.getIn(), includeExchangeProperties, true, 4,
+                                    true, 
backlogTracer.isBodyIncludeStreams(), backlogTracer.isBodyIncludeFiles(),
+                                    backlogTracer.getBodyMaxChars());
+                            String messageAsJSon
+                                    = 
MessageHelper.dumpAsJSon(exchange.getIn(), includeExchangeProperties, true, 4,
+                                            true, 
backlogTracer.isBodyIncludeStreams(), backlogTracer.isBodyIncludeFiles(),
+                                            backlogTracer.getBodyMaxChars(), 
true);
+                            DefaultBacklogTracerEventMessage pseudoLast = new 
DefaultBacklogTracerEventMessage(
+                                    false, true, 
backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId,
+                                    messageAsXml,
+                                    messageAsJSon);
+                            backlogTracer.traceEvent(pseudoLast);
+                            doneProcessing(exchange, pseudoLast);
+                            doneProcessing(exchange, pseudoFirst);
+                        }
+                    });
                 }
                 DefaultBacklogTracerEventMessage event = new 
DefaultBacklogTracerEventMessage(
-                        backlogTracer.incrementTraceCounter(), timestamp, 
routeId, toNode, exchangeId, messageAsXml,
+                        false, false, backlogTracer.incrementTraceCounter(), 
timestamp, routeId, toNode, exchangeId,
+                        messageAsXml,
                         messageAsJSon);
                 backlogTracer.traceEvent(event);
+
+                return event;
             }
 
             return null;
         }
 
         @Override
-        public void after(Exchange exchange, Object data) throws Exception {
-            // noop
+        public void after(Exchange exchange, DefaultBacklogTracerEventMessage 
data) throws Exception {
+            doneProcessing(exchange, data);
+        }
+
+        private void doneProcessing(Exchange exchange, 
DefaultBacklogTracerEventMessage data) {
+            if (data != null) {
+                data.doneProcessing();
+                if (!data.isFirst()) {
+                    // we want to capture if there was an exception
+                    Throwable e = exchange.getException();
+                    if (e != null) {
+                        String xml = MessageHelper.dumpExceptionAsXML(e, 4);
+                        data.setExceptionAsXml(xml);
+                        String json = MessageHelper.dumpExceptionAsJSon(e, 4, 
true);
+                        data.setExceptionAsJSon(json);
+                    }
+                }
+            }
         }
 
         @Override
         public boolean hasState() {
-            return false;
+            return true;
         }
 
         @Override
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java
index dc7d6d034bd..d4c1766b217 100644
--- 
a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java
@@ -70,7 +70,7 @@ public class BacklogTracerFilterTest extends 
ManagementTestSupport {
                 = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, 
"dumpAllTracedMessages", null, null);
 
         assertNotNull(events);
-        assertEquals(3, events.size());
+        assertEquals(4, events.size());
 
         BacklogTracerEventMessage event = events.get(0);
         assertEquals(null, event.getToNode());
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java
index 68a68a0ee74..9d0ee97df6f 100644
--- 
a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java
@@ -151,7 +151,11 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
                 = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, 
"dumpAllTracedMessages", null, null);
 
         assertNotNull(events);
-        assertEquals(6, events.size());
+        assertEquals(8, events.size());
+
+        // first and last events
+        assertTrue(events.get(0).isFirst());
+        assertTrue(events.get(7).isLast());
 
         BacklogTracerEventMessage event0 = events.get(0);
         assertEquals("route1", event0.getRouteId());
@@ -177,7 +181,7 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
                      + "    </message>",
                 event2.getMessageAsXml());
 
-        BacklogTracerEventMessage event3 = events.get(3);
+        BacklogTracerEventMessage event3 = events.get(4);
         assertEquals("route1", event3.getRouteId());
         assertEquals(null, event3.getToNode());
         assertEquals("    <message exchangeId=\"" + 
fooExchanges.get(1).getExchangeId() + "\">\n"
@@ -185,7 +189,7 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
                      + "    </message>",
                 event3.getMessageAsXml());
 
-        BacklogTracerEventMessage event4 = events.get(4);
+        BacklogTracerEventMessage event4 = events.get(5);
         assertEquals("route1", event4.getRouteId());
         assertEquals("foo", event4.getToNode());
         assertEquals("    <message exchangeId=\"" + 
fooExchanges.get(1).getExchangeId() + "\">\n"
@@ -193,7 +197,7 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
                      + "    </message>",
                 event3.getMessageAsXml());
 
-        BacklogTracerEventMessage event5 = events.get(5);
+        BacklogTracerEventMessage event5 = events.get(6);
         assertEquals("route1", event5.getRouteId());
         assertEquals("bar", event5.getToNode());
         assertEquals("    <message exchangeId=\"" + 
barExchanges.get(1).getExchangeId() + "\">\n"
@@ -231,7 +235,7 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
         assertNotNull(dom);
 
         NodeList list = dom.getElementsByTagName("backlogTracerEventMessage");
-        assertEquals(6, list.getLength());
+        assertEquals(8, list.getLength());
     }
 
     @SuppressWarnings("unchecked")
@@ -263,12 +267,12 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
                 = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, 
"dumpAllTracedMessages", null, null);
 
         assertNotNull(events);
-        assertEquals(6, events.size());
+        assertEquals(8, events.size());
 
         // and if we get again they are still there
         events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, 
"dumpAllTracedMessages", null, null);
         assertNotNull(events);
-        assertEquals(6, events.size());
+        assertEquals(8, events.size());
 
         // send in another message
         resetMocks();
@@ -280,10 +284,10 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
 
         assertMockEndpointsSatisfied();
 
-        // and now we should have 3 more messages
+        // and now we should have 4 more messages
         events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, 
"dumpAllTracedMessages", null, null);
         assertNotNull(events);
-        assertEquals(9, events.size());
+        assertEquals(12, events.size());
     }
 
     @SuppressWarnings("unchecked")
@@ -380,12 +384,12 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
 
         List<BacklogTracerEventMessage> events = 
(List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpTracedMessages",
                 new Object[] { "foo" }, new String[] { "java.lang.String" });
-        assertEquals(10, events.size());
+        assertEquals(7, events.size());
 
-        // the first should be 0 and the last 9
+        // the first should be 3 and the last 9
         String xml = events.get(0).getMessageAsXml();
-        assertTrue(xml.contains("###0###"));
-        xml = events.get(9).getMessageAsXml();
+        assertTrue(xml.contains("###3###"));
+        xml = events.get(6).getMessageAsXml();
         assertTrue(xml.contains("###9###"));
 
         // send in another message
@@ -393,12 +397,12 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
 
         events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, 
"dumpTracedMessages",
                 new Object[] { "foo" }, new String[] { "java.lang.String" });
-        assertEquals(10, events.size());
+        assertEquals(7, events.size());
 
         // and we are shifted one now
         xml = events.get(0).getMessageAsXml();
-        assertTrue(xml.contains("###1###"));
-        xml = events.get(9).getMessageAsXml();
+        assertTrue(xml.contains("###4###"));
+        xml = events.get(6).getMessageAsXml();
         assertTrue(xml.contains("###10###"));
 
         // send in 4 messages
@@ -409,12 +413,12 @@ public class BacklogTracerTest extends 
ManagementTestSupport {
 
         events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, 
"dumpTracedMessages",
                 new Object[] { "foo" }, new String[] { "java.lang.String" });
-        assertEquals(10, events.size());
+        assertEquals(7, events.size());
 
         // and we are shifted +4 now
         xml = events.get(0).getMessageAsXml();
-        assertTrue(xml.contains("###5###"));
-        xml = events.get(9).getMessageAsXml();
+        assertTrue(xml.contains("###8###"));
+        xml = events.get(6).getMessageAsXml();
         assertTrue(xml.contains("###14###"));
     }
 
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java
index 842ee64ab8b..086719e7c02 100644
--- 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java
@@ -65,9 +65,9 @@ public class ManagedFromRestGetTest extends 
ManagementTestSupport {
         assertTrue(xml.contains("</rests>"));
 
         assertTrue(xml.contains("<param defaultValue=\"1\" 
dataType=\"integer\" name=\"header_count\""
-                + " description=\"header param description1\" type=\"header\" 
required=\"true\">"));
+                                + " description=\"header param description1\" 
type=\"header\" required=\"true\">"));
         assertTrue(xml.contains("<param defaultValue=\"b\" dataType=\"string\" 
name=\"header_letter\""
-                + " description=\"header param description2\" type=\"query\" 
collectionFormat=\"multi\" required=\"false\">"));
+                                + " description=\"header param description2\" 
type=\"query\" collectionFormat=\"multi\" required=\"false\">"));
         assertTrue(xml.contains("<value>1</value>"));
         assertTrue(xml.contains("<value>a</value>"));
 
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java
index 2f979809d0a..7984b258693 100644
--- 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java
@@ -66,9 +66,9 @@ public class ManagedFromRestPlaceholderTest extends 
ManagementTestSupport {
         assertTrue(xml.contains("</rests>"));
 
         assertTrue(xml.contains("<param defaultValue=\"1\" 
dataType=\"integer\" name=\"header_count\""
-                + " description=\"header param description1\" type=\"header\" 
required=\"true\">"));
+                                + " description=\"header param description1\" 
type=\"header\" required=\"true\">"));
         assertTrue(xml.contains("<param defaultValue=\"b\" dataType=\"string\" 
name=\"header_letter\""
-                + " description=\"header param description2\" type=\"query\" 
collectionFormat=\"multi\" required=\"false\">"));
+                                + " description=\"header param description2\" 
type=\"query\" collectionFormat=\"multi\" required=\"false\">"));
         assertTrue(xml.contains("<value>1</value>"));
         assertTrue(xml.contains("<value>a</value>"));
 
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java 
b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java
index 7f69612a3af..ee85d1603b4 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java
@@ -20,7 +20,9 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.io.Reader;
+import java.io.StringWriter;
 import java.io.Writer;
 import java.util.List;
 import java.util.Map;
@@ -854,6 +856,7 @@ public final class MessageHelper {
      * @param  allowFiles                whether to include message body if 
they are file based
      * @param  maxChars                  clip body after maximum chars (to 
avoid very big messages). Use 0 or negative
      *                                   value to not limit at all.
+     * @param  pretty                    whether to pretty print JSon
      * @return                           the JSon
      */
     public static String dumpAsJSon(
@@ -952,4 +955,83 @@ public final class MessageHelper {
         return answer;
     }
 
+    /**
+     * Dumps the exception as a generic XML structure.
+     *
+     * @param  indent number of spaces to indent
+     * @return        the XML
+     */
+    public static String dumpExceptionAsXML(Throwable exception, int indent) {
+        StringBuilder prefix = new StringBuilder();
+        for (int i = 0; i < indent; i++) {
+            prefix.append(" ");
+        }
+
+        StringBuilder sb = new StringBuilder();
+        try {
+            sb.append(prefix).append("<exception");
+            String type = ObjectHelper.classCanonicalName(exception);
+            if (type != null) {
+                sb.append(" type=\"").append(type).append("\"");
+            }
+            String msg = exception.getMessage();
+            if (msg != null) {
+                msg = StringHelper.xmlEncode(msg);
+                sb.append(" message=\"").append(msg).append("\"");
+            }
+            sb.append(">\n");
+            StringWriter sw = new StringWriter();
+            exception.printStackTrace(new PrintWriter(sw));
+            String trace = sw.toString();
+            // must always xml encode
+            sb.append(StringHelper.xmlEncode(trace));
+            sb.append(prefix).append("</exception>");
+        } catch (Throwable e) {
+            // ignore
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Dumps the exception as a generic JSon structure.
+     *
+     * @param  indent number of spaces to indent
+     * @param  pretty whether to pretty print JSon
+     * @return        the JSon
+     */
+    public static String dumpExceptionAsJSon(Throwable exception, int indent, 
boolean pretty) {
+        JsonObject root = new JsonObject();
+        JsonObject jo = new JsonObject();
+        root.put("exception", jo);
+
+        String type = ObjectHelper.classCanonicalName(exception);
+        if (type != null) {
+            jo.put("type", type);
+        }
+        String msg = exception.getMessage();
+        if (msg != null) {
+            jo.put("message", type);
+        } else {
+            jo.put("message", "[null]");
+        }
+        StringWriter sw = new StringWriter();
+        exception.printStackTrace(new PrintWriter(sw));
+        String trace = sw.toString();
+        try {
+            jo.put("stackTrace", Jsoner.unescape(trace));
+        } catch (Throwable e) {
+            // ignore as the body is for logging purpose
+        }
+        String answer = root.toJson();
+        if (pretty) {
+            if (indent > 0) {
+                answer = Jsoner.prettyPrint(answer, indent);
+            } else {
+                answer = Jsoner.prettyPrint(answer);
+            }
+        }
+        return answer;
+    }
+
 }

Reply via email to