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 e339a2e9019f CAMEL-23688: camel-jbang - Tests for cmd route-diagram
and cmd span (#27969)
e339a2e9019f is described below
commit e339a2e9019fa1ce241b3bb1c2d855c5942d4794
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 17 07:51:20 2026 +0200
CAMEL-23688: camel-jbang - Tests for cmd route-diagram and cmd span (#27969)
Tests for camel cmd route-diagram of a running integration as text (25%
covered before) and the views of camel cmd span (33%): the traces, the
flat list of spans, the waterfall of a trace and the JSON. They found
these bugs, fixed here:
* camel cmd route-diagram --highlight never filtered the routes (the
helper keeps them all without the order of a message), so its check
for routes without the nodes was never reached, and node ids that no
route has drew a diagram with nothing highlighted; now not found (3).
Its errors went to stdout with exit 1; they go to stderr, and a
graphical --theme without terminal graphics exits with 2.
* camel cmd span --trace with an id that matches no trace printed a
message and exited with 0; now not found (3). An unknown --sort of the
traces sorted the newest first; now a usage error.
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 8 +
.../commands/action/CamelRouteDiagramAction.java | 24 ++-
.../core/commands/action/CamelSpanAction.java | 10 +-
.../action/CamelRouteDiagramActionRunningTest.java | 169 ++++++++++++++++++
.../commands/action/CamelSpanActionViewsTest.java | 195 +++++++++++++++++++++
5 files changed, 391 insertions(+), 15 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 0ecfc40763c7..81cf732576ea 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -458,6 +458,14 @@ Without an interactive terminal, such as in a script, a CI
job (the `CI` environ
it off (use `--follow=false` to turn it off). In a terminal, the end of the
input (Ctrl+D) stops a command that watches
or follows, like Enter does.
+=== camel-jbang - camel cmd route-diagram and camel cmd span
+
+* `camel cmd route-diagram --highlight` with node ids that no route has exits
with `3` (it drew the diagram with
+ nothing highlighted). Its errors are printed on stderr, and a graphical
`--theme` in a terminal without graphics exits
+ with `2`.
+* `camel cmd span --trace` with a trace id that matches no trace exits with
`3` (it printed a message and exited with
+ `0`), and an unknown `--sort` key of the traces exits with `2` (it sorted
the newest first).
+
=== camel-jbang - camel run: startup in the background, and input errors
* `camel run --background` waits up to 60 seconds for the integration to start
(it was 20 seconds), and
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
index 7065cfc43adc..7b4d499a3252 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
@@ -48,6 +48,7 @@ import
org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.dsl.jbang.core.common.ActionClient;
import org.apache.camel.dsl.jbang.core.common.ActionClient.ActionResult;
import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
import org.apache.camel.dsl.jbang.core.common.ProcessFinder;
import org.apache.camel.dsl.jbang.core.common.ProcessFinder.CamelProcess;
@@ -183,11 +184,11 @@ public class CamelRouteDiagramAction extends
ActionWatchCommand {
if (!textMode) {
terminalGraphics =
TerminalGraphicsManager.getBestProtocol(terminal).orElse(null);
if (terminalGraphics == null) {
- printer().println("Terminal does not support graphics
protocols (Kitty, iTerm2, or Sixel).");
- printer().println(
+ printer().printErr("Terminal does not support graphics
protocols (Kitty, iTerm2, or Sixel).");
+ printer().printErr(
"Try running in a supported terminal: Kitty,
iTerm2, WezTerm, Ghostty, or VS Code.");
- printer().println("Or use --theme=ascii or --theme=unicode
for plain text output.");
- return 1;
+ printer().printErr("Or use --theme=ascii or
--theme=unicode for plain text output.");
+ return CliExitCode.USAGE;
}
}
}
@@ -254,20 +255,17 @@ public class CamelRouteDiagramAction extends
ActionWatchCommand {
throw CliException.notFound("No route matching: " + filter);
}
- // parse highlight info
+ // parse highlight info: the routes are drawn with the nodes
highlighted
Set<String> highlightedNodeIds = null;
RouteDiagramHelper.HighlightStyle hlStyle = null;
- RouteDiagramHelper.HighlightInfo highlightInfo = null;
if (highlight != null && !highlight.isBlank()) {
highlightedNodeIds = new
LinkedHashSet<>(Arrays.asList(highlight.split(",")));
hlStyle = "fail".equalsIgnoreCase(highlightStyle)
? RouteDiagramHelper.HighlightStyle.FAIL
: RouteDiagramHelper.HighlightStyle.SUCCESS;
- highlightInfo = new
RouteDiagramHelper.HighlightInfo(highlightedNodeIds, List.of(), hlStyle);
- routes = RouteDiagramHelper.filterAndOrderRoutes(routes,
highlightInfo);
- if (routes.isEmpty()) {
- printer().println("No routes contain highlighted nodes");
- return 1;
+ Set<String> ids = highlightedNodeIds;
+ if (routes.stream().flatMap(r -> r.nodes.stream()).noneMatch(n ->
n.id != null && ids.contains(n.id))) {
+ throw CliException.notFound("No route has the nodes to
highlight: " + highlight);
}
}
@@ -318,8 +316,8 @@ public class CamelRouteDiagramAction extends
ActionWatchCommand {
try {
image = renderer.renderDiagram(layoutRoutes, currentY, colors,
highlightedNodeIds, hlStyle);
} catch (IllegalStateException e) {
- printer().println(e.getMessage());
- return 1;
+ printer().printErr(e.getMessage());
+ return CliExitCode.FAILURE;
}
if (output != null) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSpanAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSpanAction.java
index 05cc6957895e..c1b940d3ee69 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSpanAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSpanAction.java
@@ -65,6 +65,9 @@ public class CamelSpanAction extends ActionBaseCommand {
.numeric("duration", r -> r.durationMs)
.text("status", r -> r.status);
+ // the keys of the order of the traces (see sortTraceSummary)
+ private static final List<String> TRACE_ORDER = List.of("trace", "route",
"from", "spans", "routes", "status", "duration");
+
public static class SortCompletionCandidates implements Iterable<String> {
public SortCompletionCandidates() {
@@ -200,6 +203,10 @@ public class CamelSpanAction extends ActionBaseCommand {
}
tableSpans(rows);
} else {
+ if (sort != null && !TRACE_ORDER.contains(sort.startsWith("-") ?
sort.substring(1) : sort)) {
+ throw CliException.usage("--sort must be one of " +
String.join(", ", TRACE_ORDER)
+ + " (with - in front for the reverse
order), was: " + sort);
+ }
List<TraceSummary> summaries = buildTraceSummaries(rows);
if (filter != null) {
summaries.removeIf(ts ->
!ts.searchText.contains(filter.toLowerCase()));
@@ -341,8 +348,7 @@ public class CamelSpanAction extends ActionBaseCommand {
}
}
if (matchedTraceId == null) {
- printer().println("No trace found matching '" + traceIdMatch +
"'");
- return;
+ throw CliException.notFound("No trace found matching: " +
traceIdMatch);
}
final String tid = matchedTraceId;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionRunningTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionRunningTest.java
new file mode 100644
index 000000000000..3df2a593adc8
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionRunningTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.junit.jupiter.MockitoExtension;
+import picocli.CommandLine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * camel cmd route-diagram of a running integration, as text (the diagrams as
images need a terminal with graphics).
+ */
+@ExtendWith(MockitoExtension.class)
+class CamelRouteDiagramActionRunningTest extends ActionCommandTestSupport {
+
+ @TempDir
+ Path dir;
+
+ @Test
+ void asciiDiagramOfTheRoutes() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ CamelRouteDiagramAction command = command("--theme=ascii");
+
+ assertThat(callWithResponse(command::doWatchCall, structure("route1",
"route2"))).isZero();
+
+
assertThat(readActionFile(TEST_PID).getString("action")).isEqualTo("route-structure");
+ assertThat(printer.getOutput()).contains("route1", "route2",
"timer:tick", "log:out").doesNotContain("─");
+ }
+
+ @Test
+ void unicodeDiagramByDefault() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command()::doWatchCall,
structure("route1"))).isZero();
+
+ assertThat(printer.getOutput()).contains("route1", "─");
+ }
+
+ @Test
+ void filterDrawsTheMatchingRoutes() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command("--theme=ascii",
"--filter=route2")::doWatchCall,
+ structure("route1", "route2"))).isZero();
+
+
assertThat(printer.getOutput()).contains("route2").doesNotContain("route1");
+ }
+
+ @Test
+ void filterThatMatchesNoRouteIsNotFound() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ CamelRouteDiagramAction command = command("--theme=ascii",
"--filter=orders");
+
+ assertThatThrownBy(() -> callWithResponse(command::doWatchCall,
structure("route1")))
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.NOT_FOUND))
+ .hasMessage("No route matching: orders");
+ }
+
+ @Test
+ void highlightOfNodesThatNoRouteHasIsNotFound() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ CamelRouteDiagramAction command = command("--theme=ascii",
"--highlight=nope");
+
+ // the diagram was drawn without anything highlighted
+ assertThatThrownBy(() -> callWithResponse(command::doWatchCall,
structure("route1")))
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.NOT_FOUND))
+ .hasMessage("No route has the nodes to highlight: nope");
+ assertThat(printer.getStdout()).isEmpty();
+ }
+
+ @Test
+ void highlightDrawsTheRoutesWithTheNodesHighlighted() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command("--theme=ascii",
"--highlight=to2")::doWatchCall,
+ structure("route1", "route2"))).isZero();
+
+ assertThat(printer.getOutput()).contains("route1", "route2");
+ }
+
+ @Test
+ void textDiagramIntoAFile() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ Path png = dir.resolve("out").resolve("diagram.png");
+
+ assertThat(callWithResponse(command("--theme=ascii", "--output=" +
png)::doWatchCall, structure("route1")))
+ .isZero();
+
+ // a text theme writes text, next to where the image would go
+ Path txt = dir.resolve("out").resolve("diagram.txt");
+ assertThat(Files.readString(txt)).contains("route1", "timer:tick");
+ assertThat(printer.getOutput()).contains("Diagram saved to: " +
txt.toAbsolutePath());
+ }
+
+ @Test
+ void anIntegrationWithoutRoutesIsAFailure() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ CamelRouteDiagramAction command = command("--theme=ascii");
+
+ assertThatThrownBy(() -> callWithResponse(command::doWatchCall,
structure()))
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.FAILURE))
+ .hasMessage("No routes found");
+ }
+
+ private CamelRouteDiagramAction command(String... args) {
+ CamelRouteDiagramAction command = new CamelRouteDiagramAction(new
CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(command, args);
+ command.files = List.of("myApp");
+ return command;
+ }
+
+ // the answer of the route-structure action: each route from a timer to a
log
+ private static JsonObject structure(String... routeIds) {
+ JsonArray routes = new JsonArray();
+ int n = 0;
+ for (String routeId : routeIds) {
+ n++;
+ JsonArray code = new JsonArray();
+ code.add(line("from", "timer:tick", 0, "from" + n));
+ code.add(line("to", "log:out", 1, "to" + n));
+ JsonObject route = new JsonObject();
+ route.put("routeId", routeId);
+ route.put("code", code);
+ routes.add(route);
+ }
+ JsonObject answer = new JsonObject();
+ answer.put("routes", routes);
+ return answer;
+ }
+
+ private static JsonObject line(String type, String code, int level, String
id) {
+ JsonObject line = new JsonObject();
+ line.put("type", type);
+ line.put("code", code);
+ line.put("level", level);
+ line.put("id", id);
+ return line;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSpanActionViewsTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSpanActionViewsTest.java
new file mode 100644
index 000000000000..55c64190513d
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSpanActionViewsTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import picocli.CommandLine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * camel cmd span: the traces of the spans an integration captured, a flat
list of the spans, and the waterfall of a
+ * trace.
+ */
+@ExtendWith(MockitoExtension.class)
+class CamelSpanActionViewsTest extends ActionCommandTestSupport {
+
+ @Test
+ void tracesWithTheirSpans() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command(), spans())).isZero();
+
+ String out = printer.getOutput();
+ assertThat(out).contains("TRACE-ID", "orders", "payments", "ERROR",
"OK", "kafka");
+ }
+
+ @Test
+ void filterTheTraces() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command("--filter=payments"),
spans())).isZero();
+
+
assertThat(printer.getOutput()).contains("payments").doesNotContain("orders");
+ }
+
+ @Test
+ void sortTheTracesBySpans() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command("--sort=spans"),
spans())).isZero();
+
+ // the trace with the most spans first
+ String out = printer.getOutput();
+ assertThat(out.indexOf("orders")).isLessThan(out.indexOf("payments"));
+ }
+
+ @Test
+ void unknownSortOfTheTracesIsAUsageError() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ CamelSpanAction command = command("--sort=size");
+
+ assertThatThrownBy(() -> callWithResponse(command, spans()))
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.USAGE))
+ .hasMessageStartingWith("--sort must be one of trace, route,
from, spans, routes, status, duration");
+ }
+
+ @Test
+ void flatListOfTheSpansByDuration() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command("--flat", "--sort=-duration"),
spans())).isZero();
+
+ String out = printer.getOutput();
+ // the slowest span (the kafka call of 40ms) first
+ assertThat(out).contains("kafka:billing", "timer://orders");
+
assertThat(out.indexOf("kafka:billing")).isLessThan(out.indexOf("log:done"));
+ }
+
+ @Test
+ void waterfallOfATrace() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command("--trace=aaaa"),
spans())).isZero();
+
+ assertThat(printer.getOutput()).contains("timer://orders",
"kafka:billing", "log:done")
+ .doesNotContain("timer://payments");
+ }
+
+ @Test
+ void waterfallOfATraceThatDoesNotExistIsNotFound() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ CamelSpanAction command = command("--trace=ffff");
+
+ assertThatThrownBy(() -> callWithResponse(command, spans()))
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.NOT_FOUND))
+ .hasMessage("No trace found matching: ffff");
+ }
+
+ @Test
+ void jsonOfTheSpansOfATrace() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+
+ assertThat(callWithResponse(command("--json", "--trace=bbbb"),
spans())).isZero();
+
+ JsonArray spans = Jsoner.deserialize(printer.getOutput(), (JsonArray)
null);
+ assertThat(spans).hasSize(1);
+ JsonObject span = spans.getMap(0);
+ assertThat(span.getString("routeId")).isEqualTo("payments");
+ }
+
+ @Test
+ void noSpansYet() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ JsonObject answer = new JsonObject(Map.of("enabled", true, "spans",
new JsonArray()));
+
+ assertThat(callWithResponse(command(), answer)).isZero();
+
+ assertThat(printer.getStderr()).contains("No spans captured yet.");
+ }
+
+ @Test
+ void anIntegrationWithoutTheExporterIsAFailure() throws Exception {
+ writeStatusFile(TEST_PID, "myApp");
+ CamelSpanAction command = command();
+
+ assertThatThrownBy(() -> callWithResponse(command, new
JsonObject(Map.of("enabled", false))))
+ .isInstanceOfSatisfying(CliException.class,
+ e ->
assertThat(e.getExitCode()).isEqualTo(CliExitCode.FAILURE))
+ .hasMessageContaining("--observe");
+ }
+
+ private CamelSpanAction command(String... args) {
+ CamelSpanAction command = new CamelSpanAction(new
CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(command,
+ Stream.concat(Stream.of("myApp", "--logging-color=false"),
Arrays.stream(args)).toArray(String[]::new));
+ return command;
+ }
+
+ // two traces: orders (three spans, a kafka call that failed) and payments
(one span)
+ private static JsonObject spans() {
+ JsonArray spans = new JsonArray(
+ List.of(
+ span("aaaa1111", "s1", "", "timer://orders", "orders",
"OK", 0, 50, null),
+ span("aaaa1111", "s2", "s1", "kafka:billing",
"orders", "ERROR", 5, 45, "kafka"),
+ span("aaaa1111", "s3", "s1", "log:done", "orders",
"OK", 46, 48, null),
+ span("bbbb2222", "s4", "", "timer://payments",
"payments", "OK", 100, 101, null)));
+ JsonObject answer = new JsonObject();
+ answer.put("enabled", true);
+ answer.put("spans", spans);
+ return answer;
+ }
+
+ private static JsonObject span(
+ String traceId, String spanId, String parentSpanId, String name,
String routeId, String status, long startMs,
+ long endMs, String scheme) {
+ JsonObject attributes = new JsonObject();
+ attributes.put("exchangeId", "E-" + traceId);
+ if (scheme != null) {
+ attributes.put("url.scheme", scheme);
+ }
+ JsonObject span = new JsonObject();
+ span.put("traceId", traceId);
+ span.put("spanId", spanId);
+ span.put("parentSpanId", parentSpanId);
+ span.put("name", name);
+ span.put("kind", "INTERNAL");
+ span.put("status", status);
+ span.put("durationMs", endMs - startMs);
+ span.put("routeId", routeId);
+ span.put("startEpochNanos", startMs * 1_000_000L);
+ span.put("endEpochNanos", endMs * 1_000_000L);
+ span.put("attributes", attributes);
+ return span;
+ }
+}