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 7305f778948d CAMEL-24513: camel-core - Error registry for handled
exception should point to origin of error
7305f778948d is described below
commit 7305f778948d6a59429630340f4cb929183b3866
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Aug 27 14:55:10 2026 +0200
CAMEL-24513: camel-core - Error registry for handled exception should point
to origin of error
DefaultErrorRegistry derived the failing node id/location from the last
message
history entry, which is only correct if nothing runs after the failure
occurred.
For a handled exception, the exception handler itself (onException,
doCatch, dead
letter channel) can append its own message history entries before the
registry
captures the snapshot, so toNode/location ended up pointing at a node
touched by
the handler instead of the node that actually failed.
Adds Exchange.FAILURE_NODE_ID / Exchange.FAILURE_LOCATION, captured
up-front via
ExchangeHelper.captureFailureOrigin() as soon as the exception is caught,
before
any failure processor runs. DefaultErrorRegistry now reads this snapshot
uniformly
for both handled and unhandled errors, falling back to the previous
heuristic only
when no error handler captured it up-front.
Closes #25786
Co-Authored-By: Claude Code on behalf of Claus Ibsen <[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 1063517cde30..355a81d775dc 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 1063517cde30..355a81d775dc 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.