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();
+ }
+
}