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/02/10 10:38:52 UTC

[camel] 03/05: CAMEL-19027: camel-console - Add dev console for backlog tracer

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

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

commit 54f533bd7216d9efcdbf77fe2d5f743f15c6d4d5
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Feb 10 10:12:07 2023 +0100

    CAMEL-19027: camel-console - Add dev console for backlog tracer
---
 .../camel/spi/BacklogTracerEventMessage.java       |   5 +
 .../camel/impl/debugger/BacklogDebugger.java       |  20 +++-
 .../debugger/DefaultBacklogTracerEventMessage.java |  11 ++-
 .../camel/impl/engine/CamelInternalProcessor.java  |   7 +-
 .../apache/camel/impl/console/TraceConsole.java    |  18 +++-
 .../org/apache/camel/util/MessageHelperTest.java   |  35 +++++++
 core/camel-support/pom.xml                         |   4 +
 .../org/apache/camel/support/MessageHelper.java    | 102 +++++++++++++++++++++
 8 files changed, 190 insertions(+), 12 deletions(-)

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 86da3ffb690..5be20df9ebd 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
@@ -54,6 +54,11 @@ public interface BacklogTracerEventMessage {
      */
     String getMessageAsXml();
 
+    /**
+     * The content of the message as JSon (body and headers)
+     */
+    String getMessageAsJSon();
+
     /**
      * 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 eb39e71d012..02e48176c34 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
@@ -610,7 +610,8 @@ public final class BacklogDebugger extends ServiceSupport {
                 (nId, message) -> new DefaultBacklogTracerEventMessage(
                         message.getUid(), message.getTimestamp(), message.getRouteId(), message.getToNode(),
                         message.getExchangeId(),
-                        dumpAsXml(suspendedExchange.getExchange())));
+                        dumpAsXml(suspendedExchange.getExchange()),
+                        dumpAsJSon(suspendedExchange.getExchange())));
     }
 
     /**
@@ -624,6 +625,17 @@ public final class BacklogDebugger extends ServiceSupport {
                 getBodyMaxChars());
     }
 
+    /**
+     * Dumps the message as a generic JSon structure.
+     *
+     * @param  exchange the exchange to dump as JSon
+     * @return          the JSon
+     */
+    private String dumpAsJSon(Exchange exchange) {
+        return MessageHelper.dumpAsJSon(exchange.getIn(), true, 2, isBodyIncludeStreams(), isBodyIncludeFiles(),
+                getBodyMaxChars());
+    }
+
     /**
      * Represents a {@link org.apache.camel.spi.Breakpoint} that has a {@link Condition} on a specific node id.
      */
@@ -653,10 +665,11 @@ public final class BacklogDebugger extends ServiceSupport {
             String routeId = CamelContextHelper.getRouteId(definition);
             String exchangeId = exchange.getExchangeId();
             String messageAsXml = dumpAsXml(exchange);
+            String messageAsJSon = dumpAsJSon(exchange);
             long uid = debugCounter.incrementAndGet();
 
             BacklogTracerEventMessage msg
-                    = new DefaultBacklogTracerEventMessage(uid, timestamp, routeId, toNode, exchangeId, messageAsXml);
+                    = new DefaultBacklogTracerEventMessage(uid, timestamp, routeId, toNode, exchangeId, messageAsXml, messageAsJSon);
             suspendedBreakpointMessages.put(nodeId, msg);
 
             // suspend at this breakpoint
@@ -719,10 +732,11 @@ public final class BacklogDebugger extends ServiceSupport {
             String routeId = CamelContextHelper.getRouteId(definition);
             String exchangeId = exchange.getExchangeId();
             String messageAsXml = dumpAsXml(exchange);
+            String messageAsJSon = dumpAsJSon(exchange);
             long uid = debugCounter.incrementAndGet();
 
             BacklogTracerEventMessage msg
-                    = new DefaultBacklogTracerEventMessage(uid, timestamp, routeId, toNode, exchangeId, messageAsXml);
+                    = new DefaultBacklogTracerEventMessage(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/DefaultBacklogTracerEventMessage.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java
index b5fbe127114..491f2b5a5fe 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
@@ -25,23 +25,23 @@ import org.apache.camel.spi.BacklogTracerEventMessage;
  */
 public final class DefaultBacklogTracerEventMessage implements BacklogTracerEventMessage {
 
-    private static final long serialVersionUID = 1L;
-
     private final long uid;
     private final long timestamp;
     private final String routeId;
     private final String toNode;
     private final String exchangeId;
     private final String messageAsXml;
+    private final String messageAsJSon;
 
     public DefaultBacklogTracerEventMessage(long uid, long timestamp, String routeId, String toNode, String exchangeId,
-                                            String messageAsXml) {
+                                            String messageAsXml, String messageAsJSon) {
         this.uid = uid;
         this.timestamp = timestamp;
         this.routeId = routeId;
         this.toNode = toNode;
         this.exchangeId = exchangeId;
         this.messageAsXml = messageAsXml;
+        this.messageAsJSon = messageAsJSon;
     }
 
     @Override
@@ -74,6 +74,11 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven
         return messageAsXml;
     }
 
+    @Override
+    public String getMessageAsJSon() {
+        return messageAsJSon;
+    }
+
     @Override
     public String toString() {
         return "DefaultBacklogTracerEventMessage[" + exchangeId + " at " + toNode + "]";
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 e05b6ab8d22..dd09610c827 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
@@ -587,17 +587,20 @@ public class CamelInternalProcessor extends DelegateAsyncProcessor implements In
                 String messageAsXml = MessageHelper.dumpAsXml(exchange.getIn(), true, 4,
                         backlogTracer.isBodyIncludeStreams(), backlogTracer.isBodyIncludeFiles(),
                         backlogTracer.getBodyMaxChars());
+                String messageAsJSon = MessageHelper.dumpAsJSon(exchange.getIn(), true, 4,
+                        backlogTracer.isBodyIncludeStreams(), backlogTracer.isBodyIncludeFiles(),
+                        backlogTracer.getBodyMaxChars());
 
                 // if first we should add a pseudo trace message as well, so we have a starting message (eg from the route)
                 String routeId = routeDefinition != null ? routeDefinition.getRouteId() : null;
                 if (first) {
                     long created = exchange.getCreated();
                     DefaultBacklogTracerEventMessage pseudo = new DefaultBacklogTracerEventMessage(
-                            backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId, messageAsXml);
+                            backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId, messageAsXml, messageAsJSon);
                     backlogTracer.traceEvent(pseudo);
                 }
                 DefaultBacklogTracerEventMessage event = new DefaultBacklogTracerEventMessage(
-                        backlogTracer.incrementTraceCounter(), timestamp, routeId, toNode, exchangeId, messageAsXml);
+                        backlogTracer.incrementTraceCounter(), timestamp, routeId, toNode, exchangeId, messageAsXml, messageAsJSon);
                 backlogTracer.traceEvent(event);
             }
 
diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceConsole.java
index f7d82ddf928..29c5c696d0f 100644
--- a/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceConsole.java
+++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceConsole.java
@@ -26,6 +26,7 @@ import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.annotations.DevConsole;
 import org.apache.camel.support.console.AbstractDevConsole;
 import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
 
 @DevConsole("trace")
 @Configurer(bootstrap = true)
@@ -59,13 +60,22 @@ public class TraceConsole extends AbstractDevConsole {
             for (BacklogTracerEventMessage t : tracer.dumpAllTracedMessages()) {
                 JsonObject jo = new JsonObject();
                 jo.put("uid", t.getUid());
-                jo.put("exchangeId", t.getExchangeId());
-                jo.put("routeId", t.getRouteId());
-                jo.put("nodeId", t.getToNode());
+                if (t.getRouteId() != null) {
+                    jo.put("routeId", t.getRouteId());
+                }
+                if (t.getToNode() != null) {
+                    jo.put("nodeId", t.getToNode());
+                }
                 if (t.getTimestamp() > 0) {
                     jo.put("timestamp", t.getTimestamp());
                 }
-                // TODO: message body/headers as json
+                try {
+                    // parse back to json object and avoid double message root
+                    JsonObject msg = (JsonObject) Jsoner.deserialize(t.getMessageAsJSon());
+                    jo.put("message", msg.get("message"));
+                } catch (Exception e) {
+                    // ignore
+                }
                 arr.add(jo);
             }
         }
diff --git a/core/camel-core/src/test/java/org/apache/camel/util/MessageHelperTest.java b/core/camel-core/src/test/java/org/apache/camel/util/MessageHelperTest.java
index f8cf1ea4c8f..39ea78b58f7 100644
--- a/core/camel-core/src/test/java/org/apache/camel/util/MessageHelperTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/util/MessageHelperTest.java
@@ -243,4 +243,39 @@ public class MessageHelperTest {
         assertTrue(out.contains("Hello World"));
     }
 
+    @Test
+    public void testMessageDumpBodyJSon() throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.start();
+
+        message = new DefaultExchange(context).getIn();
+
+        // xml message body
+        message.setBody("Hello World");
+        message.setHeader("foo", 123);
+
+        String out = MessageHelper.dumpAsJSon(message, true);
+        assertNotNull(out);
+        assertTrue(out.contains("Hello World"));
+    }
+
+    @Test
+    public void testDumpAsXmlBodyJSon() throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.start();
+
+        message = new DefaultExchange(context).getIn();
+
+        // xml message body
+        message.setBody("<?xml version=\"1.0\"?><hi>Hello World</hi>");
+        message.setHeader("foo", 123);
+
+        String out = MessageHelper.dumpAsJSon(message);
+        // xml is escaped in json output
+        assertTrue(out.contains("<?xml version=\\\"1.0\\\"?><hi>Hello World<\\/hi>"));
+        assertTrue(out.contains(message.getExchange().getExchangeId()), "Should contain exchangeId");
+
+        context.stop();
+    }
+
 }
diff --git a/core/camel-support/pom.xml b/core/camel-support/pom.xml
index 53516dc7f3a..ab29756f251 100644
--- a/core/camel-support/pom.xml
+++ b/core/camel-support/pom.xml
@@ -51,6 +51,10 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-util</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-util-json</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
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 1cfcc77fe1f..137d48d4619 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
@@ -41,6 +41,9 @@ import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StopWatch;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.URISupport;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
 
 /**
  * Some helper methods when working with {@link org.apache.camel.Message}.
@@ -733,4 +736,103 @@ public final class MessageHelper {
         return sb.toString();
     }
 
+    /**
+     * Dumps the message as a generic JSon structure.
+     *
+     * @param  message the message
+     * @return         the JSon
+     */
+    public static String dumpAsJSon(Message message) {
+        return dumpAsJSon(message, true);
+    }
+
+    /**
+     * Dumps the message as a generic JSon structure.
+     *
+     * @param  message     the message
+     * @param  includeBody whether or not to include the message body
+     * @return             the JSon
+     */
+    public static String dumpAsJSon(Message message, boolean includeBody) {
+        return dumpAsJSon(message, includeBody, 0);
+    }
+
+    /**
+     * Dumps the message as a generic JSon structure.
+     *
+     * @param  message     the message
+     * @param  includeBody whether or not to include the message body
+     * @param  indent      number of spaces to indent
+     * @return             the JSon
+     */
+    public static String dumpAsJSon(Message message, boolean includeBody, int indent) {
+        return dumpAsJSon(message, includeBody, indent, false, true, 128 * 1024);
+    }
+
+    /**
+     * Dumps the message as a generic JSon structure.
+     *
+     * @param  message      the message
+     * @param  includeBody  whether or not to include the message body
+     * @param  indent       number of spaces to indent
+     * @param  allowStreams whether to include message body if they are stream based
+     * @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.
+     * @return              the JSon
+     */
+    public static String dumpAsJSon(
+            Message message, boolean includeBody, int indent, boolean allowStreams, boolean allowFiles, int maxChars) {
+
+        JsonObject root = new JsonObject();
+        JsonObject jo = new JsonObject();
+        root.put("message", jo);
+        jo.put("exchangeId", message.getExchange().getExchangeId());
+
+        // headers
+        if (message.hasHeaders()) {
+            JsonArray arr = new JsonArray();
+            jo.put("headers", arr);
+            // sort the headers so they are listed A..Z
+            Map<String, Object> headers = new TreeMap<>(message.getHeaders());
+            for (Map.Entry<String, Object> entry : headers.entrySet()) {
+                Object value = entry.getValue();
+                String type = ObjectHelper.classCanonicalName(value);
+                JsonObject jh = new JsonObject();
+                jh.put("key", entry.getKey());
+                if (type != null) {
+                    jh.put("type", type);
+                }
+                // dump header value as JSon, use Camel type converter to convert to String
+                if (value != null) {
+                    try {
+                        String data = message.getExchange().getContext().getTypeConverter().tryConvertTo(String.class,
+                                message.getExchange(), value);
+                        if (data != null) {
+                            jh.put("value", Jsoner.unescape(data));
+                        }
+                    } catch (Throwable e) {
+                        // ignore as the body is for logging purpose
+                    }
+                }
+            }
+        }
+
+        if (includeBody) {
+            JsonObject jb = new JsonObject();
+            jo.put("body", jb);
+            String type = ObjectHelper.classCanonicalName(message.getBody());
+            if (type != null) {
+                jb.put("type", type);
+            }
+
+            String data = extractBodyForLogging(message, null, allowStreams, allowFiles, maxChars);
+            if (data != null) {
+                jb.put("value", Jsoner.unescape(data));
+            }
+        }
+
+        return root.toJson();
+    }
+
 }