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 81def551326e CAMEL-24844: the message dump carries the body's size,
and the camel-jbang history views and tool show type and size per step
81def551326e is described below
commit 81def551326ee77dd96ca4df79d8d9d6c479108a
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 20 15:13:26 2026 +0200
CAMEL-24844: the message dump carries the body's size, and the camel-jbang
history views and tool show type and size per step
The runtime side of CAMEL-24844 on a live app: what the body is at every
step, answered where people and agents look.
The message dump (MessageHelper.dumpAsJSon, carried by every backlog tracer
event) says "type": "null" when the body is null, where it said nothing
before, and carries a size for text and byte bodies from the
MessageSizeStrategy when it is enabled (the dev profile does): lengths only,
nothing read or converted.
The camel-jbang history and error views parse the size and show it next to
the type of each step's body. The CLI's message table says what a size
counts, "size: 3 elements" for a list and "size: 11 bytes" for text, and
shows the dumped value's length only when it differs; camel history gets
--json, named as the other commands name it, printing the recorded history.
The camel-jbang-mcp history tool (get_history) gets summary=true: one entry
per step with route, node, elapsed, and the body's type and size as it
reached the step, without bodies, headers and properties, so a small model
sees where the body turned from text into a Map or bytes without paying for
the full dump.
Closes #26627
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
.../org/apache/camel/util/MessageHelperTest.java | 20 ++++++++
.../org/apache/camel/support/MessageHelper.java | 15 +++++-
.../jbang-commands/camel-jbang-get-history.adoc | 1 +
.../META-INF/camel-jbang-commands-metadata.json | 2 +-
.../core/commands/action/CamelHistoryAction.java | 18 ++++++++
.../core/commands/action/MessageTableHelper.java | 20 ++++++--
.../dsl/jbang/core/commands/ai/ToolRegistry.java | 53 +++++++++++++++++++++-
.../commands/action/CamelHistoryActionTest.java | 17 +++++++
.../jbang/core/commands/ai/ToolRegistryTest.java | 35 ++++++++++++++
.../dsl/jbang/core/commands/tui/ErrorInfo.java | 1 +
.../dsl/jbang/core/commands/tui/ErrorsTab.java | 2 +-
.../dsl/jbang/core/commands/tui/HistoryEntry.java | 1 +
.../dsl/jbang/core/commands/tui/HistoryTab.java | 19 ++++++--
.../dsl/jbang/core/commands/tui/StatusParser.java | 12 ++++-
.../dsl/jbang/core/commands/tui/TraceEntry.java | 1 +
.../jbang/core/commands/tui/StatusParserTest.java | 19 ++++++++
16 files changed, 223 insertions(+), 13 deletions(-)
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 2f047a915594..83e1d0f4286d 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
@@ -300,4 +300,24 @@ public class MessageHelperTest {
assertTrue(out.contains("Hello World"));
}
+ @Test
+ public void testDumpAsJSonBodySizeAndNull() {
+ // CAMEL-24844: the size from the message size strategy when it is
enabled, and a null body says so
+ camelContext.getMessageSizeStrategy().setEnabled(true);
+ Exchange exchange = new DefaultExchange(camelContext);
+ Message message = exchange.getIn();
+ message.setBody("Hello World");
+ String out = MessageHelper.dumpAsJSon(message, true);
+ assertTrue(out.contains("\"type\": \"java.lang.String\""), out);
+ assertTrue(out.contains("\"size\": 11"), out);
+
+ message.setBody(null);
+ out = MessageHelper.dumpAsJSon(message, true);
+ assertTrue(out.contains("\"type\": \"null\""), out);
+ assertTrue(!out.contains("\"size\""), "no body, no size: " + out);
+
+ message.setBody("");
+ out = MessageHelper.dumpAsJSon(message, true);
+ assertTrue(out.contains("\"size\": 0"), "an empty text has a size of
0: " + out);
+ }
}
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 3371aa18fb2a..1e86d51d244e 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
@@ -39,6 +39,7 @@ import org.apache.camel.WrappedFile;
import org.apache.camel.spi.DataTypeAware;
import org.apache.camel.spi.ExchangeFormatter;
import org.apache.camel.spi.HeaderFilterStrategy;
+import org.apache.camel.spi.MessageSizeStrategy;
import org.apache.camel.trait.message.MessageTrait;
import org.apache.camel.util.ImportantHeaderUtils;
import org.apache.camel.util.ObjectHelper;
@@ -1090,7 +1091,7 @@ public final class MessageHelper {
JsonObject jb = new JsonObject();
jo.put("body", jb);
Object body = message.getBody();
- String type = ObjectHelper.classCanonicalName(body);
+ String type = body != null ? ObjectHelper.classCanonicalName(body)
: "null";
if (type != null) {
jb.put("type", type);
}
@@ -1121,6 +1122,18 @@ public final class MessageHelper {
jb.put("size", size);
}
}
+ if (body != null && !jb.containsKey("size") &&
message.getExchange() != null
+ && message.getExchange().getContext() != null) {
+ // the size of a text or byte body, from the message size
strategy when it is enabled (the dev
+ // profile does): lengths only, nothing is read or converted
(CAMEL-24844)
+ MessageSizeStrategy sizeStrategy =
message.getExchange().getContext().getMessageSizeStrategy();
+ if (sizeStrategy != null && sizeStrategy.isEnabled()) {
+ long size = sizeStrategy.computeBodySize(message);
+ if (size >= 0) {
+ jb.put("size", size);
+ }
+ }
+ }
String data = extractBodyForLogging(message, null,
allowCachedStreams, allowStreams, allowFiles, maxChars);
if (data != null) {
if ("[Body is null]".equals(data)) {
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-history.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-history.adoc
index a3114809ca3d..009697986176 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-history.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-history.adoc
@@ -23,6 +23,7 @@ camel get history [options]
| `--depth` | Depth of tracing. 0=Created Completed. 1=All events on 1st
route, 2=All events on 1st 2nd depth, and so on. 9 = all events on every depth.
| 9 | int
| `--diagram` | Display a route diagram with the message path highlighted | |
boolean
| `--it` | Interactive mode for enhanced history information | | boolean
+| `--json` | Output in JSON Format | | boolean
| `--limit-split` | Limit Split to a maximum number of entries to be displayed
| | int
| `--logging-color` | Use colored logging | true | boolean
| `--mask` | Whether to mask endpoint URIs to avoid printing sensitive
information such as password or access keys | | boolean
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 3118cacfdb31..9bc284eca34b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -15,7 +15,7 @@
{ "name": "eval", "fullName": "eval", "description": "Evaluate Camel
expressions and scripts", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression",
"fullName": "eval expression", "description": "Evaluates Camel expression",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
{ "name": "explain", "fullName": "explain", "description": "Explain what a
Camel route does using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
ANTHROPIC_API_KEY, OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars",
"javaType": "java.lang.String", "type": "string" }, { "names": "--api-type",
"description": "API type: 'ollama', 'openai' (OpenAI-compatible), ' [...]
{ "name": "export", "fullName": "export", "description": "Export to other
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names":
"--build-property", "description": "Maven build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--camel-spring-boot-version", "description": "Camel version to use
with Spring Boot", "javaType": "java.lang.String", "ty [...]
- { "name": "get", "fullName": "get", "description": "Get status of Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ {
"names": "--watch", "description": "Execute periodically and showing output
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity",
"fullName": " [...]
+ { "name": "get", "fullName": "get", "description": "Get status of Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ {
"names": "--watch", "description": "Execute periodically and showing output
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity",
"fullName": " [...]
{ "name": "harden", "fullName": "harden", "description": "Suggest security
hardening for Camel routes using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String",
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama'
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
{ "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio
web console", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ {
"names": "--host", "description": "Hostname to bind the Hawtio web console to",
"defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string"
}, { "names": "--openUrl", "description": "To automatic open Hawtio web console
in the web browser", "defaultValue": "true", "javaType": "boolean", "type":
[...]
{ "name": "infra", "fullName": "infra", "description": "List and Run
external services for testing and prototyping", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ {
"names": "--json", "description": "Output in JSON Format", "javaType":
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ],
"subcommands": [ { "name": "get", "fullName": "infra [...]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
index a60fa2444734..2f30984e858e 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
@@ -134,6 +134,10 @@ public class CamelHistoryAction extends ActionWatchCommand
{
description = "Pretty print message body when using
JSon or XML format")
boolean pretty;
+ @CommandLine.Option(names = { "--json" },
+ description = "Output in JSON Format")
+ boolean jsonOutput;
+
@CommandLine.Option(names = { "--logging-color" }, defaultValue = "true",
description = "Use colored logging")
boolean loggingColor = true;
@@ -167,6 +171,20 @@ public class CamelHistoryAction extends ActionWatchCommand
{
name = "*";
}
+ if (jsonOutput) {
+ for (long pid : findPids(name)) {
+ Path p = getMessageHistoryFile(Long.toString(pid));
+ if (Files.exists(p)) {
+ for (String line : Files.readAllLines(p)) {
+ if (!line.isBlank()) {
+ printer().println(line);
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
List<List<Row>> pids = loadRows();
if (!pids.isEmpty()) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
index 1e9ae066031a..315c793fa6c2 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
@@ -467,6 +467,18 @@ public class MessageTableHelper {
return s;
}
+ /** Whether the dump's size counts elements (a collection, a map or an
array other than bytes). */
+ static boolean sizeIsCount(String type) {
+ if (type == null) {
+ return false;
+ }
+ if (type.endsWith("[]")) {
+ return !type.equals("byte[]");
+ }
+ return type.startsWith("java.util.") && (type.contains("List") ||
type.contains("Set") || type.contains("Map")
+ || type.contains("Collection") || type.contains("Queue")
|| type.contains("Deque"));
+ }
+
String typeAndLengthAsString() {
String s;
if (type == null) {
@@ -493,13 +505,15 @@ public class MessageTableHelper {
long p = position != null ? position : -1;
StringBuilder sb = new StringBuilder();
if (sz != -1) {
- sb.append(" size: ").append(sz);
+ // the dump's size is a count for a collection or an array,
bytes for text, bytes, a stream or a file
+ sb.append(" size: ").append(sz).append(sizeIsCount(type) ? "
elements" : " bytes");
}
if (p != -1) {
sb.append(" pos: ").append(p);
}
- if (l != -1) {
- sb.append(" bytes: ").append(l);
+ if (l != -1 && l != sz) {
+ // the length of the value as dumped (it may be cut), when it
is not the size already shown
+ sb.append(" shown: ").append(l);
}
if (!sb.isEmpty()) {
s = s + " (" + sb.toString().trim() + ")";
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index 6dba0839df9b..b4674d69cdae 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -213,10 +213,20 @@ public final class ToolRegistry {
}));
register(tool("get_history",
- "Get the message history trace of the last completed
exchange.")
+ "Get the message history trace of the last completed exchange:
every step with the message as it was"
+ + " there. With summary=true only the
steps, each with route, node, elapsed, and the"
+ + " body's type and size as it reached
the step (what to read to see where the body"
+ + " changed from text into a Map or
bytes).")
+ .param("summary", "boolean", "Only the steps with route, node,
elapsed, body type and size", false)
.executor((ctx, args) -> {
JsonObject history = ctx.readHistoryFile();
- return history != null ? history.toJson() : "No message
history available.";
+ if (history == null) {
+ return "No message history available.";
+ }
+ if ("true".equalsIgnoreCase(args.get("summary"))) {
+ return historySummary(history).toJson();
+ }
+ return history.toJson();
}));
register(tool("get_route_source",
@@ -1176,4 +1186,43 @@ public final class ToolRegistry {
|| (title != null && title.toLowerCase().contains(lf))
|| (description != null &&
description.toLowerCase().contains(lf));
}
+
+ /**
+ * The steps of the last completed exchange, one line each: route, node,
elapsed, and the body's type and size as it
+ * reached the step (from the message dump of the trace event), without
the bodies, headers and properties.
+ */
+ static JsonObject historySummary(JsonObject history) {
+ JsonObject answer = new JsonObject();
+ if (history.get("name") != null) {
+ answer.put("name", history.get("name"));
+ }
+ JsonArray steps = new JsonArray();
+ Object traces = history.get("traces");
+ if (traces instanceof java.util.List<?> list) {
+ for (Object o : list) {
+ if (!(o instanceof JsonObject t)) {
+ continue;
+ }
+ JsonObject step = new JsonObject();
+ for (String key : new String[] {
+ "routeId", "nodeId", "nodeShortName", "nodeLabel",
"location", "elapsed",
+ "first", "last", "failed" }) {
+ if (t.get(key) != null) {
+ step.put(key, t.get(key));
+ }
+ }
+ if (t.get("message") instanceof JsonObject m && m.get("body")
instanceof JsonObject b) {
+ if (b.get("type") != null) {
+ step.put("bodyType", b.get("type"));
+ }
+ if (b.get("size") != null) {
+ step.put("bodySize", b.get("size"));
+ }
+ }
+ steps.add(step);
+ }
+ }
+ answer.put("steps", steps);
+ return answer;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryActionTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryActionTest.java
index a137f72ff99d..7ef13aff324e 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryActionTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryActionTest.java
@@ -55,6 +55,23 @@ class CamelHistoryActionTest extends
ActionCommandTestSupport {
assertTrue(out.contains("ABCDEFGH-0001"), "should print the exchange
id, was: " + out);
}
+ @Test
+ void testJsonOutputIsTheRecordedHistory() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ writeMessageHistoryFile(TEST_PID, singleTraceLine());
+ CamelHistoryAction command = new CamelHistoryAction(new
CamelJBangMain().withPrinter(printer));
+ command.name = "myApp";
+ command.depth = 9;
+ command.loggingColor = false;
+ command.jsonOutput = true;
+ int exit = callWithSingleProcess(command);
+ assertEquals(0, exit);
+ String out = printer.getOutput();
+ assertTrue(out.contains("\"traces\""), "the recorded history as JSON,
was: " + out);
+ assertTrue(out.contains("ABCDEFGH-0001"), "the exchange id in the
JSON, was: " + out);
+ assertTrue(!out.contains("Message History of last completed"), "no
table in JSON mode, was: " + out);
+ }
+
@Test
void testRendersNothingWhenNameDoesNotMatch() throws Exception {
writeStatusFile(TEST_PID, "myApp");
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
index f08d209c07d3..671a88db8d40 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
@@ -21,6 +21,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -190,4 +192,37 @@ class ToolRegistryTest {
assertThrows(ToolExecutionException.class,
() -> ToolRegistry.execute("detect_config_drift", ctx,
Map.of()));
}
+
+ @Test
+ void historySummaryKeepsTheStepsAndTheBodyTypeAndSize() {
+ // CAMEL-24844: the compact form of get_history a small model can read
+ JsonObject body = new JsonObject();
+ body.put("type", "java.util.LinkedHashMap");
+ body.put("size", 3);
+ body.put("value", "{orderId=ORD-1001}");
+ JsonObject message = new JsonObject();
+ message.put("body", body);
+ message.put("headers", new JsonArray());
+ JsonObject trace = new JsonObject();
+ trace.put("routeId", "route1");
+ trace.put("nodeId", "unmarshal1");
+ trace.put("nodeShortName", "unmarshal");
+ trace.put("elapsed", 2);
+ trace.put("message", message);
+ JsonArray traces = new JsonArray();
+ traces.add(trace);
+ JsonObject history = new JsonObject();
+ history.put("name", "shop");
+ history.put("traces", traces);
+
+ JsonObject summary = ToolRegistry.historySummary(history);
+ assertEquals("shop", summary.get("name"));
+ JsonArray steps = summary.getCollection("steps");
+ assertEquals(1, steps.size());
+ JsonObject step = (JsonObject) steps.get(0);
+ assertEquals("unmarshal1", step.get("nodeId"));
+ assertEquals("java.util.LinkedHashMap", step.get("bodyType"));
+ assertEquals(3, step.get("bodySize"));
+ assertNull(step.get("message"), "no bodies, headers or properties in
the summary");
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorInfo.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorInfo.java
index d01b5026412e..ad94f33fe897 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorInfo.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorInfo.java
@@ -36,6 +36,7 @@ class ErrorInfo {
String[] messageHistory;
String body;
String bodyType;
+ long bodySize = -1;
final Map<String, Object> headers = new LinkedHashMap<>();
final Map<String, String> headerTypes = new LinkedHashMap<>();
final Map<String, Object> properties = new LinkedHashMap<>();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index 129c4ac47d5d..bc39693b6517 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -532,7 +532,7 @@ class ErrorsTab extends AbstractTableTab {
HistoryTab.addKvLines(lines, " Headers:", ei.headers,
ei.headerTypes, false, null);
}
if (showBody) {
- HistoryTab.addBodyLines(lines, ei.body, ei.bodyType, false);
+ HistoryTab.addBodyLines(lines, ei.body, ei.bodyType, ei.bodySize,
false);
}
int[] scroll = { detailScroll };
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryEntry.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryEntry.java
index 06339e4a7d1a..07f2394ccaad 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryEntry.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryEntry.java
@@ -41,6 +41,7 @@ class HistoryEntry {
long epochMs;
String body;
String bodyType;
+ long bodySize = -1;
String exception;
Map<String, Object> headers;
Map<String, String> headerTypes;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
index 8a3b5d0fcbf3..1d36ec4b600c 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
@@ -906,6 +906,7 @@ class HistoryTab extends AbstractTab {
boolean failed = false;
String body = null;
String bodyType = null;
+ long bodySize = -1;
String exception = null;
Map<String, Object> headers = null;
Map<String, Object> properties = null;
@@ -927,6 +928,7 @@ class HistoryTab extends AbstractTab {
failed = e.failed;
body = e.body;
bodyType = e.bodyType;
+ bodySize = e.bodySize;
exception = e.exception;
headers = e.headers;
properties = e.exchangeProperties;
@@ -950,6 +952,7 @@ class HistoryTab extends AbstractTab {
failed = e.failed;
body = e.body;
bodyType = e.bodyType;
+ bodySize = e.bodySize;
exception = e.exception;
headers = e.headers;
properties = e.exchangeProperties;
@@ -1050,9 +1053,11 @@ class HistoryTab extends AbstractTab {
if (showBody && body != null) {
Style headerStyle = bodyChanged ? Theme.change().bold() :
Theme.muted();
lines.add(Line.from(Span.raw("")));
+ String detail = bodyType != null && bodySize >= 0
+ ? bodyType + ", " + HeapHistogramTab.formatBytes(bodySize)
: bodyType;
lines.add(Line.from(
Span.styled(" Body", headerStyle),
- bodyType != null ? Span.styled(" (" + bodyType + ")",
Style.EMPTY.dim()) : Span.raw("")));
+ detail != null ? Span.styled(" (" + detail + ")",
Style.EMPTY.dim()) : Span.raw("")));
for (String line : body.split("\n")) {
lines.add(Line.from(Span.raw(" " + line)));
}
@@ -1388,7 +1393,7 @@ class HistoryTab extends AbstractTab {
headersChanged, prev != null ? prev.headers : null);
}
if (showTraceBody) {
- addBodyLines(lines, entry.body, entry.bodyType, bodyChanged);
+ addBodyLines(lines, entry.body, entry.bodyType, entry.bodySize,
bodyChanged);
}
addExceptionLines(lines, entry.exception);
@@ -1673,7 +1678,7 @@ class HistoryTab extends AbstractTab {
headersChanged, prev != null ? prev.headers : null);
}
if (showHistoryBody) {
- addBodyLines(lines, entry.body, entry.bodyType, bodyChanged);
+ addBodyLines(lines, entry.body, entry.bodyType, entry.bodySize,
bodyChanged);
}
addExceptionLines(lines, entry.exception);
@@ -2196,12 +2201,18 @@ class HistoryTab extends AbstractTab {
}
static void addBodyLines(List<Line> lines, String body, String bodyType,
boolean changed) {
+ addBodyLines(lines, body, bodyType, -1, changed);
+ }
+
+ /** The body's type and, when the running app measured it, its size next
to the body lines (CAMEL-24844). */
+ static void addBodyLines(List<Line> lines, String body, String bodyType,
long bodySize, boolean changed) {
Style headerStyle = changed ? Theme.change().bold() : Theme.muted();
if (body != null) {
if (bodyType != null) {
+ String detail = bodySize >= 0 ? bodyType + ", " +
HeapHistogramTab.formatBytes(bodySize) : bodyType;
lines.add(Line.from(
Span.styled(" Body: ", headerStyle),
- Span.styled("(" + bodyType + ")", Style.EMPTY.dim())));
+ Span.styled("(" + detail + ")", Style.EMPTY.dim())));
} else {
lines.add(Line.from(Span.styled(" Body:", headerStyle)));
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index da3e27a8eab8..38a6a42692c0 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -44,6 +44,7 @@ final class StatusParser {
Map<String, String> headerTypes,
String body,
String bodyType,
+ long bodySize,
Map<String, Object> exchangeProperties,
Map<String, String> exchangePropertyTypes,
Map<String, Object> exchangeVariables,
@@ -968,6 +969,7 @@ final class StatusParser {
entry.headerTypes = md.headerTypes();
entry.body = md.body();
entry.bodyType = md.bodyType();
+ entry.bodySize = md.bodySize();
if (entry.body != null) {
entry.bodyPreview = entry.body.replace("\n", "
").replace("\r", "");
}
@@ -1076,6 +1078,7 @@ final class StatusParser {
entry.headerTypes = md.headerTypes();
entry.body = md.body();
entry.bodyType = md.bodyType();
+ entry.bodySize = md.bodySize();
entry.exchangeProperties = md.exchangeProperties();
entry.exchangePropertyTypes = md.exchangePropertyTypes();
entry.exchangeVariables = md.exchangeVariables();
@@ -1102,6 +1105,7 @@ final class StatusParser {
Map<String, String> headerTypes = null;
String body = null;
String bodyType = null;
+ long bodySize = -1;
Map<String, Object> exchangeProperties = null;
Map<String, String> exchangePropertyTypes = null;
Map<String, Object> exchangeVariables = null;
@@ -1130,6 +1134,9 @@ final class StatusParser {
Object val = bodyJson.get("value");
body = val != null ? val.toString() : null;
bodyType = TuiHelper.shortTypeName(bodyJson.getString("type"));
+ if (bodyJson.get("size") instanceof Number n) {
+ bodySize = n.longValue();
+ }
} else if (bodyObj != null) {
body = bodyObj.toString();
}
@@ -1171,7 +1178,7 @@ final class StatusParser {
}
return new MessageData(
- headers, headerTypes, body, bodyType,
+ headers, headerTypes, body, bodyType, bodySize,
exchangeProperties, exchangePropertyTypes, exchangeVariables,
exchangeVariableTypes);
}
@@ -1439,6 +1446,9 @@ final class StatusParser {
if (bodyObj instanceof JsonObject bodyJson) {
ei.body = bodyJson.getString("value");
ei.bodyType =
TuiHelper.shortTypeName(bodyJson.getString("type"));
+ if (bodyJson.get("size") instanceof Number n) {
+ ei.bodySize = n.longValue();
+ }
} else if (bodyObj != null) {
ei.body = bodyObj.toString();
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TraceEntry.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TraceEntry.java
index a566178d6b44..a23466c745dc 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TraceEntry.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TraceEntry.java
@@ -42,6 +42,7 @@ class TraceEntry {
long epochMs;
String body;
String bodyType;
+ long bodySize = -1;
String bodyPreview;
Map<String, Object> headers;
Map<String, String> headerTypes;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
index ad690a17c6e0..4d4195b58e66 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
@@ -403,6 +403,25 @@ class StatusParserTest {
assertEquals("String", md.headerTypes().get("Content-Type"));
}
+ @Test
+ void parseMessageBodyTypeAndSize() {
+ // CAMEL-24844: the body's type and the size the running app measured
+ JsonObject body = new JsonObject();
+ body.put("type", "java.lang.String");
+ body.put("size", 11);
+ body.put("value", "Hello World");
+ JsonObject message = new JsonObject();
+ message.put("body", body);
+
+ StatusParser.MessageData md = StatusParser.parseMessage(message);
+ assertEquals("Hello World", md.body());
+ assertEquals("String", md.bodyType());
+ assertEquals(11, md.bodySize());
+
+ body.remove("size");
+ assertEquals(-1, StatusParser.parseMessage(message).bodySize());
+ }
+
@Test
void parseMessageWithHeadersAsMap() {
JsonObject message = new JsonObject();