This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new a7c9e1ca2977 [backport camel-4.22.x] CAMEL-24513: camel-core - Error
registry for handled exception should point to origin of error
a7c9e1ca2977 is described below
commit a7c9e1ca297748a0908361594e4886b753a5ee27
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Aug 28 08:37:48 2026 +0200
[backport camel-4.22.x] CAMEL-24513: camel-core - Error registry for
handled exception should point to origin of error
Cherry-pick of #25786 onto camel-4.22.x.
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
.../camel/catalog/docs/dead-letter-channel.adoc | 20 ++++++++++
.../org/apache/camel/catalog/models/doCatch.json | 4 +-
.../org/apache/camel/catalog/models/doFinally.json | 4 +-
.../apache/camel/catalog/models/errorHandler.json | 4 +-
.../apache/camel/catalog/models/onException.json | 4 +-
.../org/apache/camel/spring/xml/errorHandler.json | 4 +-
.../org/apache/camel/ExchangeConstantProvider.java | 4 +-
.../src/main/java/org/apache/camel/Exchange.java | 8 ++++
.../java/org/apache/camel/ExchangePropertyKey.java | 6 +++
.../camel/impl/engine/DefaultErrorRegistry.java | 29 ++++++++------
.../modules/eips/pages/dead-letter-channel.adoc | 20 ++++++++++
.../META-INF/org/apache/camel/model/doCatch.json | 4 +-
.../META-INF/org/apache/camel/model/doFinally.json | 4 +-
.../org/apache/camel/model/errorHandler.json | 4 +-
.../org/apache/camel/model/onException.json | 4 +-
.../org/apache/camel/processor/CatchProcessor.java | 5 ---
.../apache/camel/processor/FinallyProcessor.java | 7 +---
.../org/apache/camel/processor/TryProcessor.java | 9 +++++
.../ShareUnitOfWorkAggregationStrategy.java | 8 ++++
.../errorhandler/RedeliveryErrorHandler.java | 16 +++-----
.../org/apache/camel/impl/ErrorRegistryTest.java | 44 ++++++++++++++++++++++
.../org/apache/camel/support/ExchangeHelper.java | 29 ++++++++++++++
22 files changed, 199 insertions(+), 42 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
index 132bceab0b0e..37d1a28e2fbc 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
@@ -633,6 +633,26 @@ The `Exchange.FAILURE_ROUTE_ID` have the constant value
`CamelFailureRouteId`.
This allows, for example, you to fetch this information in your dead letter
queue and use that for error reporting.
+=== Which node failed?
+
+Camel also stores the id of the node (EIP processor) where the message failed,
along with its source
+code location (requires source location to be enabled). This information can
be obtained in Java via:
+
+._Java-only: accessing the failure node id and location from the Exchange_
+[source,java]
+----
+String failedNodeId = exchange.getProperty(Exchange.FAILURE_NODE_ID,
String.class);
+String failedLocation = exchange.getProperty(Exchange.FAILURE_LOCATION,
String.class);
+----
+
+The `Exchange.FAILURE_NODE_ID` have the constant value `CamelFailureNodeId`,
and `Exchange.FAILURE_LOCATION`
+have the constant value `CamelFailureLocation`.
+
+This information is captured up-front, as soon as the exception is caught, so
it always points to the node
+that actually failed - even when the error handler (`onException`, `doCatch`,
or the dead letter channel
+itself) performs further processing of its own before the exchange is moved to
the dead letter queue.
+
+
=== Control if redelivery is allowed during stopping/shutdown
The option `allowRedeliveryWhileStopping` (default is `true`) controls whether
redelivery is allowed or not,
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
index fe1bcc91fd59..b1a537a05ede 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
@@ -23,6 +23,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
index d8b5516eb41e..0ece8a5dcdc7 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
@@ -21,6 +21,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
index bea634af9bb9..12e4b0912735 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
@@ -18,6 +18,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
index 64dfaee08fb5..56a3f034538e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
@@ -32,6 +32,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
b/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
index 5de738fc2e52..090a8ea0ccc1 100644
---
a/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
+++
b/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
@@ -34,6 +34,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
b/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
index 9b93e7bb3081..a9d27b5a2870 100644
---
a/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
+++
b/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
@@ -17,7 +17,7 @@ public class ExchangeConstantProvider {
private static final Map<String, String> MAP;
static {
- Map<String, String> map = new HashMap<>(156);
+ Map<String, String> map = new HashMap<>(158);
map.put("ACTIVITY_SPAN_TAGS", "CamelActivitySpanTags");
map.put("AGGREGATED_COLLECTION_GUARD",
"CamelAggregatedCollectionGuard");
map.put("AGGREGATED_COMPLETED_BY", "CamelAggregatedCompletedBy");
@@ -61,6 +61,8 @@ public class ExchangeConstantProvider {
map.put("EXCEPTION_HANDLED", "CamelExceptionHandled");
map.put("FAILURE_ENDPOINT", "CamelFailureEndpoint");
map.put("FAILURE_HANDLED", "CamelFailureHandled");
+ map.put("FAILURE_LOCATION", "CamelFailureLocation");
+ map.put("FAILURE_NODE_ID", "CamelFailureNodeId");
map.put("FAILURE_ROUTE_ID", "CamelFailureRouteId");
map.put("FATAL_FALLBACK_ERROR_HANDLER",
"CamelFatalFallbackErrorHandler");
map.put("FILE_CONTENT_TYPE", "CamelFileContentType");
diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java
b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
index 74457a08e0e8..b5ca93d302d1 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
@@ -142,6 +142,14 @@ public interface Exchange extends VariableAware {
description = "Route ID where the Exchange failed during
processing",
javaType = "String")
String FAILURE_ROUTE_ID = "CamelFailureRouteId";
+ @Metadata(label = "doCatch,doFinally,errorHandler,onException",
+ description = "Node ID where the Exchange failed during
processing",
+ javaType = "String")
+ String FAILURE_NODE_ID = "CamelFailureNodeId";
+ @Metadata(label = "doCatch,doFinally,errorHandler,onException",
+ description = "Source code location where the Exchange failed
during processing",
+ javaType = "String")
+ String FAILURE_LOCATION = "CamelFailureLocation";
String FATAL_FALLBACK_ERROR_HANDLER = "CamelFatalFallbackErrorHandler";
String FILE_CONTENT_TYPE = "CamelFileContentType";
String FILE_LOCAL_WORK_PATH = "CamelFileLocalWorkPath";
diff --git
a/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java
b/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java
index c3d9ed220f05..8a90ce54e5d7 100644
--- a/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java
+++ b/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java
@@ -62,6 +62,8 @@ public enum ExchangePropertyKey {
FAILURE_ENDPOINT(Exchange.FAILURE_ENDPOINT),
FAILURE_HANDLED(Exchange.FAILURE_HANDLED),
FAILURE_ROUTE_ID(Exchange.FAILURE_ROUTE_ID),
+ FAILURE_NODE_ID(Exchange.FAILURE_NODE_ID),
+ FAILURE_LOCATION(Exchange.FAILURE_LOCATION),
FATAL_FALLBACK_ERROR_HANDLER(Exchange.FATAL_FALLBACK_ERROR_HANDLER),
FILE_EXCHANGE_FILE(Exchange.FILE_EXCHANGE_FILE),
GROUPED_EXCHANGE(Exchange.GROUPED_EXCHANGE),
@@ -175,6 +177,10 @@ public enum ExchangePropertyKey {
return FAILURE_ENDPOINT;
case Exchange.FAILURE_ROUTE_ID:
return FAILURE_ROUTE_ID;
+ case Exchange.FAILURE_NODE_ID:
+ return FAILURE_NODE_ID;
+ case Exchange.FAILURE_LOCATION:
+ return FAILURE_LOCATION;
case Exchange.FATAL_FALLBACK_ERROR_HANDLER:
return FATAL_FALLBACK_ERROR_HANDLER;
case Exchange.FILE_EXCHANGE_FILE:
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 2f72fd4b661f..72ab35cefe6c 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
@@ -128,18 +128,23 @@ public class DefaultErrorRegistry extends
EventNotifierSupport implements ErrorR
}
String endpointUri =
exchange.getProperty(ExchangePropertyKey.FAILURE_ENDPOINT, String.class);
- // capture node id and location from the last message history entry
- // (the historyNodeId on the exchange extension is cleared after the
node finishes processing,
- // so by the time the error event fires it is always null)
- String toNode = null;
- String location = null;
- List<MessageHistory> history
- = exchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY,
List.class);
- if (history != null && !history.isEmpty()) {
- MessageHistory last = history.get(history.size() - 1);
- if (last.getNode() != null) {
- toNode = last.getNode().getId();
- location =
LoggerHelper.getLineNumberLoggerName(last.getNode());
+ // capture node id and location where the exchange actually failed
+ // (captured up-front by the error handler / doCatch, before any
failure processor such as
+ // onException or a dead letter channel ran its own processing steps -
otherwise those steps
+ // would also be recorded in the message history, and the last entry
would no longer point to
+ // the node that actually failed)
+ String toNode =
exchange.getProperty(ExchangePropertyKey.FAILURE_NODE_ID, String.class);
+ String location =
exchange.getProperty(ExchangePropertyKey.FAILURE_LOCATION, String.class);
+ if (toNode == null) {
+ // fallback for error handlers that do not capture the failure
origin up-front (such as
+ // noErrorHandler): derive it from the last message history entry
instead
+ List<MessageHistory> history =
exchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class);
+ if (history != null && !history.isEmpty()) {
+ MessageHistory last = history.get(history.size() - 1);
+ if (last.getNode() != null) {
+ toNode = last.getNode().getId();
+ location =
LoggerHelper.getLineNumberLoggerName(last.getNode());
+ }
}
}
diff --git
a/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
b/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
index 132bceab0b0e..37d1a28e2fbc 100644
---
a/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
+++
b/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
@@ -633,6 +633,26 @@ The `Exchange.FAILURE_ROUTE_ID` have the constant value
`CamelFailureRouteId`.
This allows, for example, you to fetch this information in your dead letter
queue and use that for error reporting.
+=== Which node failed?
+
+Camel also stores the id of the node (EIP processor) where the message failed,
along with its source
+code location (requires source location to be enabled). This information can
be obtained in Java via:
+
+._Java-only: accessing the failure node id and location from the Exchange_
+[source,java]
+----
+String failedNodeId = exchange.getProperty(Exchange.FAILURE_NODE_ID,
String.class);
+String failedLocation = exchange.getProperty(Exchange.FAILURE_LOCATION,
String.class);
+----
+
+The `Exchange.FAILURE_NODE_ID` have the constant value `CamelFailureNodeId`,
and `Exchange.FAILURE_LOCATION`
+have the constant value `CamelFailureLocation`.
+
+This information is captured up-front, as soon as the exception is caught, so
it always points to the node
+that actually failed - even when the error handler (`onException`, `doCatch`,
or the dead letter channel
+itself) performs further processing of its own before the exchange is moved to
the dead letter queue.
+
+
=== Control if redelivery is allowed during stopping/shutdown
The option `allowRedeliveryWhileStopping` (default is `true`) controls whether
redelivery is allowed or not,
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
index fe1bcc91fd59..b1a537a05ede 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
@@ -23,6 +23,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
index d8b5516eb41e..0ece8a5dcdc7 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
@@ -21,6 +21,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
index bea634af9bb9..12e4b0912735 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
@@ -18,6 +18,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
index 64dfaee08fb5..56a3f034538e 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
@@ -32,6 +32,8 @@
"exchangeProperties": {
"CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty",
"displayName": "Exception Caught", "label": "producer", "required": false,
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false,
"secret": false, "description": "Stores the caught exception due to a
processing error of the current Exchange" },
"CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty",
"displayName": "Failure Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where the Exchange failed during processing" },
- "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" }
+ "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty",
"displayName": "Failure Route Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Route ID where the Exchange failed during processing" },
+ "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty",
"displayName": "Failure Node Id", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Node ID where the Exchange failed during processing" },
+ "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty",
"displayName": "Failure Location", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Source code location where the Exchange failed during
processing" }
}
}
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
index 8a3bed1e1b0f..30bf2a219b1a 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
@@ -154,11 +154,6 @@ public class CatchProcessor extends
BaseDelegateProcessorSupport
if (exchange.getProperty(ExchangePropertyKey.FAILURE_ENDPOINT) ==
null) {
exchange.setProperty(ExchangePropertyKey.FAILURE_ENDPOINT,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
}
- // and store the route id so we know in which route we failed
- String routeId = ExchangeHelper.getAtRouteId(exchange);
- if (routeId != null) {
- exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID,
routeId);
- }
// give the rest of the pipeline another chance
exchange.setProperty(ExchangePropertyKey.EXCEPTION_HANDLED, true);
exchange.setProperty(ExchangePropertyKey.EXCEPTION_CAUGHT, e);
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
index 26c71d900105..60ace585b1c4 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
@@ -58,11 +58,6 @@ public class FinallyProcessor extends
BaseDelegateProcessorSupport
exchange.setProperty(ExchangePropertyKey.FAILURE_ENDPOINT,
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
}
- // and store the route id so we know in which route we failed
- String routeId = ExchangeHelper.getAtRouteId(exchange);
- if (routeId != null) {
- exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID,
routeId);
- }
}
// continue processing
@@ -132,6 +127,8 @@ public class FinallyProcessor extends
BaseDelegateProcessorSupport
if (exception == null) {
exchange.removeProperty(ExchangePropertyKey.FAILURE_ENDPOINT);
exchange.removeProperty(ExchangePropertyKey.FAILURE_ROUTE_ID);
+
exchange.removeProperty(ExchangePropertyKey.FAILURE_NODE_ID);
+
exchange.removeProperty(ExchangePropertyKey.FAILURE_LOCATION);
} else {
// set exception back on exchange
exchange.setException(exception);
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
index 26dfe7b90e4e..af2c5ad10026 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
@@ -92,6 +92,7 @@ public class TryProcessor extends BaseProcessorSupport
final AsyncCallback callback;
final Iterator<Processor> processors;
final Object lastHandled;
+ boolean failureOriginCaptured;
public TryState(Exchange exchange, AsyncCallback callback) {
this.exchange = exchange;
@@ -104,6 +105,14 @@ public class TryProcessor extends BaseProcessorSupport
@Override
public void run() {
if (continueRouting(processors, exchange)) {
+ // capture where the exchange failed as soon as the exception
appears, before handing off to a
+ // doCatch/doFinally clause which is itself a channeled node
and would otherwise add its own
+ // entry to the message history, hiding the node that actually
failed
+ if (!failureOriginCaptured && exchange.getException() != null)
{
+ failureOriginCaptured = true;
+ ExchangeHelper.captureFailureOrigin(exchange);
+ }
+
exchange.setProperty(ExchangePropertyKey.TRY_ROUTE_BLOCK,
true);
ExchangeHelper.prepareOutToIn(exchange);
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
index 48b64d7a8b78..42b38ac12aa1 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
@@ -126,6 +126,14 @@ public final class ShareUnitOfWorkAggregationStrategy
extends ServiceSupport imp
answer.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID,
newExchange.getProperty(ExchangePropertyKey.FAILURE_ROUTE_ID));
}
+ if (newExchange.getProperty(ExchangePropertyKey.FAILURE_NODE_ID)
!= null) {
+ answer.setProperty(ExchangePropertyKey.FAILURE_NODE_ID,
+
newExchange.getProperty(ExchangePropertyKey.FAILURE_NODE_ID));
+ }
+ if (newExchange.getProperty(ExchangePropertyKey.FAILURE_LOCATION)
!= null) {
+ answer.setProperty(ExchangePropertyKey.FAILURE_LOCATION,
+
newExchange.getProperty(ExchangePropertyKey.FAILURE_LOCATION));
+ }
if (newExchange.getExchangeExtension().getErrorHandlerHandled() !=
null) {
answer.getExchangeExtension()
.setErrorHandlerHandled(newExchange.getExchangeExtension().getErrorHandlerHandled());
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
index a4b1fba908b1..6e325518c49b 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
@@ -831,11 +831,9 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
exchange.setProperty(ExchangePropertyKey.EXCEPTION_CAUGHT, e);
}
- // store where the exception happened
- Route rc = ExchangeHelper.getRoute(exchange);
- if (rc != null) {
- exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID,
rc.getRouteId());
- }
+ // store the route, node and location where the exception
happened, before any failure processor
+ // (onException, dead letter channel, ...) runs and adds its own
entries to the message history
+ ExchangeHelper.captureFailureOrigin(exchange);
}
/**
@@ -1381,11 +1379,9 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
redeliveryCounter = incrementRedeliveryCounter(exchange);
- // store where the exception happened
- Route rc = ExchangeHelper.getRoute(exchange);
- if (rc != null) {
- exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID,
rc.getRouteId());
- }
+ // store the route, node and location where the exception
happened, before any failure processor
+ // (onException, dead letter channel, ...) runs and adds its own
entries to the message history
+ ExchangeHelper.captureFailureOrigin(exchange);
}
/**
diff --git
a/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
index 847ae8702a41..c83ffe2c1741 100644
--- a/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
@@ -27,6 +27,7 @@ import org.apache.camel.spi.ErrorRegistry;
import org.apache.camel.spi.ErrorRegistryView;
import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -169,6 +170,36 @@ public class ErrorRegistryTest extends ContextTestSupport {
assertNotNull(entry.getToNode(), "Node id should be captured for
unhandled errors");
}
+ @Test
+ public void testErrorRegistryHandledErrorReportsOriginNode() throws
Exception {
+ getMockEndpoint("mock:handlerStep").expectedMessageCount(1);
+
+ template.sendBody("direct:handledWithSteps", "Hello World");
+
+ assertMockEndpointsSatisfied();
+
+ BacklogErrorEventMessage entry =
context.getErrorRegistry().browse().iterator().next();
+ assertThat(entry.isHandled()).isTrue();
+ assertThat(entry.getToNode())
+ .as("toNode should point to the node that actually failed, not
a node touched by the onException handler")
+ .isEqualTo("throwOrigin");
+ }
+
+ @Test
+ public void testErrorRegistryDoCatchReportsOriginNode() throws Exception {
+ getMockEndpoint("mock:catchStep").expectedMessageCount(1);
+
+ template.sendBody("direct:doCatchWithSteps", "Hello World");
+
+ assertMockEndpointsSatisfied();
+
+ BacklogErrorEventMessage entry =
context.getErrorRegistry().browse().iterator().next();
+ assertThat(entry.isHandled()).isTrue();
+ assertThat(entry.getToNode())
+ .as("toNode should point to the node that actually failed, not
a node touched by the doCatch block")
+ .isEqualTo("throwInTry");
+ }
+
@Test
public void testErrorRegistryCapturesEndpointUri() throws Exception {
getMockEndpoint("mock:dead").expectedMessageCount(1);
@@ -298,6 +329,19 @@ public class ErrorRegistryTest extends ContextTestSupport {
.setProperty("myProp", constant("propValue"))
.setHeader("myHeader", constant("headerValue"))
.throwException(new IllegalArgumentException("Data
error"));
+
+ from("direct:handledWithSteps").routeId("handledWithSteps")
+
.onException(IllegalArgumentException.class).handled(true).to("mock:handlerStep").end()
+ .to("mock:before")
+ .throwException(new IllegalArgumentException("Handled
with steps")).id("throwOrigin");
+
+ from("direct:doCatchWithSteps").routeId("doCatchWithSteps")
+ .doTry()
+ .to("mock:before")
+ .throwException(new
IllegalArgumentException("Handled in doCatch")).id("throwInTry")
+ .doCatch(IllegalArgumentException.class)
+ .to("mock:catchStep")
+ .end();
}
};
}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
index 2eaab1737d4a..1f4e32d81067 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
@@ -1089,6 +1089,35 @@ public final class ExchangeHelper {
return uow != null ? uow.getRoute() : null;
}
+ /**
+ * Captures where the Exchange failed - the route id, node id, and source
location - as the
+ * {@link Exchange#FAILURE_ROUTE_ID}, {@link Exchange#FAILURE_NODE_ID} and
{@link Exchange#FAILURE_LOCATION}
+ * properties, based on the current route and the last entry in the
message history.
+ * <p/>
+ * This must be called as soon as the exception is caught (handled or
not), before any exception handler
+ * (onException, doCatch, dead letter channel, ...) has a chance to run
any of its own processing steps - otherwise
+ * those steps would also be recorded in the message history, and the last
entry would no longer point to the node
+ * that actually failed.
+ *
+ * @param exchange the exchange that failed
+ */
+ @SuppressWarnings("unchecked")
+ public static void captureFailureOrigin(Exchange exchange) {
+ Route rc = getRoute(exchange);
+ if (rc != null) {
+ exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID,
rc.getRouteId());
+ }
+ List<MessageHistory> history =
exchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class);
+ if (history != null && !history.isEmpty()) {
+ MessageHistory last = history.get(history.size() - 1);
+ if (last.getNode() != null) {
+ exchange.setProperty(ExchangePropertyKey.FAILURE_NODE_ID,
last.getNode().getId());
+ exchange.setProperty(ExchangePropertyKey.FAILURE_LOCATION,
+ LoggerHelper.getLineNumberLoggerName(last.getNode()));
+ }
+ }
+ }
+
/**
* Sets the body in message in the exchange taking the exchange pattern
into consideration. If the pattern is out
* capable, then the body is set outbound message. Otherwise it is set on
the inbound message.