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 9d5d01bbb81a CAMEL-24972: the error registry says where each step is 
in the source (#26802)
9d5d01bbb81a is described below

commit 9d5d01bbb81ac5deec202577590ba69976d2e800
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 23 21:38:35 2026 +0200

    CAMEL-24972: the error registry says where each step is in the source 
(#26802)
    
    The error registry recorded the body type per step but not where that step 
is, so the one place that said what the body was did not say which line 
produced it - a reader holding orders.camel.yaml was told about to3, a 
generated node id.
    
    Each step string now carries the node's source location, written the way 
every other caller writes it (LoggerHelper.getLineNumberLoggerName), so a step 
reads:
    
      foo[to1] orders.camel.yaml:18 (1 ms) bodyType=java.util.LinkedHashMap
    
    The dev console and the camel-jbang-mcp history tool both print these 
strings, so both get it from one change; the error registry console also shows 
the failure's own location, which the registry already captured and never 
printed. BacklogErrorEventMessage.getMessageHistory now documents the current 
format and which parts are conditional.
    
    Nothing is added when source location is off, so the step keeps its shape. 
No API change.
    
    Closes #26802
---
 .../apache/camel/spi/BacklogErrorEventMessage.java |  8 ++-
 .../camel/impl/engine/DefaultErrorRegistry.java    |  5 ++
 .../camel/impl/console/ErrorRegistryConsole.java   |  3 +
 .../impl/ErrorRegistryNoSourceLocationTest.java    | 78 ++++++++++++++++++++++
 .../impl/ErrorRegistrySourceLocationTest.java      | 78 ++++++++++++++++++++++
 5 files changed, 171 insertions(+), 1 deletion(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogErrorEventMessage.java
 
b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogErrorEventMessage.java
index c07dd75f40a8..4d67e5824d25 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogErrorEventMessage.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogErrorEventMessage.java
@@ -126,7 +126,13 @@ public interface BacklogErrorEventMessage extends 
BacklogEventMessage {
      * The message history trace captured at the time of the error, or {@code 
null} if message history is not enabled on
      * the CamelContext.
      * <p/>
-     * Each element represents one step in the routing history in the format 
"routeId[nodeId] (elapsed ms)".
+     * Each element represents one step in the routing history, in the format
+     * {@code routeId[nodeId] source (elapsed ms) bodyType=... bodySize=...} - 
for example
+     * {@code route1[to3] orders.camel.yaml:18 (12 ms) 
bodyType=java.util.LinkedHashMap bodySize=214}.
+     * <p/>
+     * Everything after {@code routeId[nodeId]} is conditional: the source is 
present when source location is enabled
+     * (the dev profile enables it) and the node has one, the elapsed time 
when it is greater than zero, the body type
+     * when message history recorded it, and the body size only when a 
MessageSizeStrategy is enabled.
      */
     String @Nullable [] getMessageHistory();
 }
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
index 9a41c9a57055..d55dce232801 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
@@ -300,6 +300,11 @@ public class DefaultErrorRegistry extends 
EventNotifierSupport implements ErrorR
             String nodeId = mh.getNode() != null ? mh.getNode().getId() : null;
             long elapsed = mh.getElapsed();
             String step = mh.getRouteId() + "[" + nodeId + "]";
+            // where the step is in the source, so the reader can go to the 
line (CAMEL-24972)
+            String loc = LoggerHelper.getLineNumberLoggerName(mh.getNode());
+            if (loc != null) {
+                step += " " + loc;
+            }
             if (elapsed > 0) {
                 step += " (" + elapsed + " ms)";
             }
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
index 2cebf02fe25c..583853dcc37c 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
@@ -83,6 +83,9 @@ public class ErrorRegistryConsole extends AbstractDevConsole {
             sb.append(String.format("%n    %s (route: %s, node: %s, endpoint: 
%s, handled: %s)",
                     entry.getExchangeId(), entry.getRouteId(), 
entry.getToNode(), entry.getEndpointUri(),
                     entry.isHandled()));
+            if (entry.getLocation() != null) {
+                sb.append(String.format("%n      Source: %s", 
entry.getLocation()));
+            }
             sb.append(String.format("%n      Exception: %s - %s",
                     entry.getExceptionType(), entry.getExceptionMessage()));
             sb.append(String.format("%n      Timestamp: %s, Thread: %s",
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryNoSourceLocationTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryNoSourceLocationTest.java
new file mode 100644
index 000000000000..11df1bee4bfe
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryNoSourceLocationTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.impl;
+
+import java.util.regex.Pattern;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.BacklogErrorEventMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * With source location off there is no line to name, so a step keeps its 
shape rather than inventing one. Source
+ * location is set on the route definitions when they are loaded, so this 
needs its own context (CAMEL-24972).
+ */
+public class ErrorRegistryNoSourceLocationTest extends ContextTestSupport {
+
+    /** Such as ErrorRegistryNoSourceLocationTest:57 */
+    private static final Pattern LOCATION = Pattern.compile("\\S+:\\d+");
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getErrorRegistry().setEnabled(true);
+        context.setMessageHistory(true);
+        context.setSourceLocationEnabled(false);
+        return context;
+    }
+
+    @Test
+    public void testStepsKeepTheirShapeWithoutASourceLocation() throws 
Exception {
+        getMockEndpoint("mock:dead").expectedMessageCount(1);
+        template.sendBody("direct:start", "Hello World");
+        assertMockEndpointsSatisfied();
+
+        BacklogErrorEventMessage entry = 
context.getErrorRegistry().browse().iterator().next();
+        String[] steps = entry.getMessageHistory();
+        assertNotNull(steps, "Message history should be captured when 
enabled");
+        assertTrue(steps.length > 0, "Message history should have at least one 
entry");
+        for (String step : steps) {
+            assertFalse(LOCATION.matcher(step).find(), "There is no line to 
name, so none is named: " + step);
+            assertTrue(step.contains("bodyType="), step);
+        }
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                errorHandler(deadLetterChannel("mock:dead"));
+
+                from("direct:start").routeId("foo")
+                        .to("log:before")
+                        .throwException(new IllegalArgumentException("Forced 
error"));
+            }
+        };
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistrySourceLocationTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistrySourceLocationTest.java
new file mode 100644
index 000000000000..15110a072296
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistrySourceLocationTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.impl;
+
+import java.util.regex.Pattern;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.BacklogErrorEventMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The step strings of the error registry say where each step is in the 
source, so a reader holding the file can go to
+ * the line instead of mapping a generated node id back to it by hand 
(CAMEL-24972).
+ */
+public class ErrorRegistrySourceLocationTest extends ContextTestSupport {
+
+    /** Such as ErrorRegistrySourceLocationTest:57 */
+    private static final Pattern LOCATION = Pattern.compile("\\S+:\\d+");
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getErrorRegistry().setEnabled(true);
+        context.setMessageHistory(true);
+        context.setSourceLocationEnabled(true);
+        return context;
+    }
+
+    @Test
+    public void testStepsCarryTheSourceLocation() throws Exception {
+        getMockEndpoint("mock:dead").expectedMessageCount(1);
+        template.sendBody("direct:start", "Hello World");
+        assertMockEndpointsSatisfied();
+
+        BacklogErrorEventMessage entry = 
context.getErrorRegistry().browse().iterator().next();
+        String[] steps = entry.getMessageHistory();
+        assertNotNull(steps, "Message history should be captured when 
enabled");
+        assertTrue(steps.length > 0, "Message history should have at least one 
entry");
+        for (String step : steps) {
+            assertTrue(LOCATION.matcher(step).find(), "Step should say where 
it is in the source: " + step);
+        }
+        // the location sits next to the body type, so both facts about a step 
arrive together
+        assertTrue(steps[0].contains("bodyType="), steps[0]);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                errorHandler(deadLetterChannel("mock:dead"));
+
+                from("direct:start").routeId("foo")
+                        .to("log:before")
+                        .throwException(new IllegalArgumentException("Forced 
error"));
+            }
+        };
+    }
+}

Reply via email to