This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-26611 in repository https://gitbox.apache.org/repos/asf/camel.git
commit efbaa74a151ad8975893067f9a34208491857dfc Author: Claus Ibsen <[email protected]> AuthorDate: Thu Nov 5 10:21:19 2026 +0100 CAMEL-26611: camel-micrometer, camel-opentelemetry-metrics - use tags and attributes from headers as given The producers resolved every tag and attribute with the Simple language, including those from the CamelMetricsTags and CamelMetricsAttributes headers. Only the tags and attributes URI options are documented as Simple expressions, and the CamelMetricsName and CamelMetricsDescription headers are already used as given. Now only the URI options are resolved per exchange. The header tags and attributes are added as they are, and still override a URI tag or attribute with the same key. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/catalog/components/micrometer.json | 2 +- .../catalog/components/opentelemetry-metrics.json | 2 +- .../camel/catalog/docs/micrometer-component.adoc | 5 +++ .../docs/opentelemetry-metrics-component.adoc | 5 +++ .../camel/component/micrometer/micrometer.json | 2 +- .../src/main/docs/micrometer-component.adoc | 5 +++ .../micrometer/AbstractMicrometerProducer.java | 4 ++- .../component/micrometer/MicrometerConstants.java | 4 ++- .../component/micrometer/CounterRouteTest.java | 38 ++++++++++++++++++++++ .../metrics/opentelemetry-metrics.json | 2 +- .../main/docs/opentelemetry-metrics-component.adoc | 5 +++ .../metrics/AbstractOpenTelemetryProducer.java | 17 ++++------ .../metrics/OpenTelemetryConstants.java | 3 +- .../opentelemetry/metrics/CounterRouteTest.java | 38 ++++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 11 +++++++ .../dsl/MicrometerEndpointBuilderFactory.java | 4 ++- .../dsl/OpenTelemetryEndpointBuilderFactory.java | 4 ++- 17 files changed, 131 insertions(+), 20 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/micrometer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/micrometer.json index 53e540c3a179..85b226b5cf3d 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/micrometer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/micrometer.json @@ -35,7 +35,7 @@ "CamelMetricsCounterIncrement": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override increment value in URI", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_COUNTER_INCREMENT" }, "CamelMetricsName": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override name value in URI", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_METRIC_NAME" }, "CamelMetricsDescription": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override description value in URI", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_METRIC_DESCRIPTION" }, - "CamelMetricsTags": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.lang.Iterable<Tag>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter tags defined as URI parameters", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_METRIC_TAGS" } + "CamelMetricsTags": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.lang.Iterable<Tag>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter tags defined as URI parameters. The tags from the header are used as given; use the tags URI parameters for Simple expressions.", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER [...] }, "bodyTypes": { "producerIn": { "carriers": [ "any" ], "description": "Not used, unless an option such as increment, decrement or value is a simple expression that reads it, which must then give a number. The CamelMetrics headers override the options" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json index 63b6574d4142..0dfaf8b6c9f4 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json @@ -35,7 +35,7 @@ "CamelMetricsCounterIncrement": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override increment value in URI", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_COUNTER_INCREMENT" }, "CamelMetricsName": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override name value in URI", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_METRIC_NAME" }, "CamelMetricsDescription": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override description value in URI", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_METRIC_DESCRIPTION" }, - "CamelMetricsAttributes": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "io.opentelemetry.api.common.Attributes", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter attributes defined as URI parameters", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_METRIC_ATTRIBUTES" } + "CamelMetricsAttributes": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "io.opentelemetry.api.common.Attributes", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter attributes defined as URI parameters. The attributes from the header are used as given; use the attributes URI parameters for Simple expressions.", "constantName": "org.apache.camel.opentele [...] }, "bodyTypes": { "producerIn": { "carriers": [ "any" ], "description": "Not used, unless an option such as increment, decrement or value is a simple expression that reads it, which must then give a number. The CamelMetrics headers override the options" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/micrometer-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/micrometer-component.adoc index 1149d43c4d2a..e699f1bdcd1e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/micrometer-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/micrometer-component.adoc @@ -148,6 +148,11 @@ from("direct:in") will update a counter with name `new.name` instead of `name.not.used` using the tag `dynamic-key` with value `dynamic-value` in addition to the tag `key` with value `value`. +The tags from the `CamelMetricsTags` header are used as given: unlike the `tags` URI parameters, +their keys and values are not evaluated as `Simple` expressions. A tag from the header overrides a tag +from the URI with the same key. To tag a meter with a value from the message, use a `tags` URI parameter +such as `tags.customer=${header.customerId}`, or compute the value in the route before setting the header. + All Metrics specific headers are removed from the message once the Micrometer endpoint finishes processing of exchange. If the Micrometer endpoint cannot update the meter, then the exchange fails with the exception, which the error handler of the route handles as any other failure. For example, a `value`, `increment` or `decrement` that is diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opentelemetry-metrics-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opentelemetry-metrics-component.adoc index 4c30d69c9a03..da424ad67647 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opentelemetry-metrics-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/opentelemetry-metrics-component.adoc @@ -90,6 +90,11 @@ from("direct:in") will update a counter with name `new.name` (instead of `name.not.used`) using the attribute `dynamic-key` with value `dynamic-value` in addition to the attribute `key` with value `value`. +The attributes from the `CamelMetricsAttributes` header are used as given: unlike the `attributes` URI parameters, +their keys and values are not evaluated as `Simple` expressions. An attribute from the header overrides an attribute +from the URI with the same key. To add an attribute with a value from the message, use an `attributes` URI parameter +such as `attributes.customer=${header.customerId}`, or compute the value in the route before setting the header. + All Metrics specific headers are removed from the message once the OpenTelemetry endpoint finishes processing the exchange. While processing the exchange the OpenTelemetry endpoint will catch all exceptions and write log entry using level `warn`. diff --git a/components/camel-micrometer/src/generated/resources/META-INF/org/apache/camel/component/micrometer/micrometer.json b/components/camel-micrometer/src/generated/resources/META-INF/org/apache/camel/component/micrometer/micrometer.json index 53e540c3a179..85b226b5cf3d 100644 --- a/components/camel-micrometer/src/generated/resources/META-INF/org/apache/camel/component/micrometer/micrometer.json +++ b/components/camel-micrometer/src/generated/resources/META-INF/org/apache/camel/component/micrometer/micrometer.json @@ -35,7 +35,7 @@ "CamelMetricsCounterIncrement": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override increment value in URI", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_COUNTER_INCREMENT" }, "CamelMetricsName": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override name value in URI", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_METRIC_NAME" }, "CamelMetricsDescription": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override description value in URI", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_METRIC_DESCRIPTION" }, - "CamelMetricsTags": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.lang.Iterable<Tag>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter tags defined as URI parameters", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER_METRIC_TAGS" } + "CamelMetricsTags": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.lang.Iterable<Tag>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter tags defined as URI parameters. The tags from the header are used as given; use the tags URI parameters for Simple expressions.", "constantName": "org.apache.camel.component.micrometer.MicrometerConstants#HEADER [...] }, "bodyTypes": { "producerIn": { "carriers": [ "any" ], "description": "Not used, unless an option such as increment, decrement or value is a simple expression that reads it, which must then give a number. The CamelMetrics headers override the options" }, diff --git a/components/camel-micrometer/src/main/docs/micrometer-component.adoc b/components/camel-micrometer/src/main/docs/micrometer-component.adoc index 1149d43c4d2a..e699f1bdcd1e 100644 --- a/components/camel-micrometer/src/main/docs/micrometer-component.adoc +++ b/components/camel-micrometer/src/main/docs/micrometer-component.adoc @@ -148,6 +148,11 @@ from("direct:in") will update a counter with name `new.name` instead of `name.not.used` using the tag `dynamic-key` with value `dynamic-value` in addition to the tag `key` with value `value`. +The tags from the `CamelMetricsTags` header are used as given: unlike the `tags` URI parameters, +their keys and values are not evaluated as `Simple` expressions. A tag from the header overrides a tag +from the URI with the same key. To tag a meter with a value from the message, use a `tags` URI parameter +such as `tags.customer=${header.customerId}`, or compute the value in the route before setting the header. + All Metrics specific headers are removed from the message once the Micrometer endpoint finishes processing of exchange. If the Micrometer endpoint cannot update the meter, then the exchange fails with the exception, which the error handler of the route handles as any other failure. For example, a `value`, `increment` or `decrement` that is diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/AbstractMicrometerProducer.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/AbstractMicrometerProducer.java index c89b6b7234d9..ba1175a296b8 100644 --- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/AbstractMicrometerProducer.java +++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/AbstractMicrometerProducer.java @@ -64,11 +64,13 @@ public abstract class AbstractMicrometerProducer<T extends Meter> extends Defaul String defaultMetricsDescription = simple(exchange, getEndpoint().getMetricsDescription(), String.class); String finalMetricsDescription = getStringHeader(in, HEADER_METRIC_DESCRIPTION, defaultMetricsDescription); Iterable<Tag> headerTags = getTagHeader(in, HEADER_METRIC_TAGS, Tags.empty()); - Iterable<Tag> finalTags = Tags.concat(tags, headerTags).stream() + // only the tags from the endpoint are simple expressions, the tags from the header are used as given + Iterable<Tag> finalTags = Tags.of(tags).stream() .map(tag -> Tag.of( simple(exchange, tag.getKey(), String.class), simple(exchange, tag.getValue(), String.class))) .reduce(Tags.empty(), Tags::and, Tags::and) + .and(headerTags) .and(Tags.of( CAMEL_CONTEXT_TAG, getEndpoint().getCamelContext().getName())); try { diff --git a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java index c2f2c4e1fcaf..ea8a7affc388 100644 --- a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java +++ b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/MicrometerConstants.java @@ -37,7 +37,9 @@ public final class MicrometerConstants { public static final String HEADER_METRIC_NAME = HEADER_PREFIX + "Name"; @Metadata(description = "Override description value in URI", javaType = "String") public static final String HEADER_METRIC_DESCRIPTION = HEADER_PREFIX + "Description"; - @Metadata(description = "To augment meter tags defined as URI parameters", javaType = "java.lang.Iterable<Tag>") + @Metadata(description = "To augment meter tags defined as URI parameters. The tags from the header are used as given;" + + " use the tags URI parameters for Simple expressions.", + javaType = "java.lang.Iterable<Tag>") public static final String HEADER_METRIC_TAGS = HEADER_PREFIX + "Tags"; public static final String DEFAULT_CAMEL_MESSAGE_HISTORY_METER_NAME = "camel.message.history"; diff --git a/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/CounterRouteTest.java b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/CounterRouteTest.java index 60ed279d235b..8af9842b9dbc 100644 --- a/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/CounterRouteTest.java +++ b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/CounterRouteTest.java @@ -21,6 +21,7 @@ import java.util.Map; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.apache.camel.BindToRegistry; import org.apache.camel.CamelExecutionException; @@ -39,9 +40,11 @@ import org.springframework.context.support.AbstractApplicationContext; import static org.apache.camel.component.micrometer.MicrometerConstants.HEADER_COUNTER_DECREMENT; import static org.apache.camel.component.micrometer.MicrometerConstants.HEADER_COUNTER_INCREMENT; import static org.apache.camel.component.micrometer.MicrometerConstants.HEADER_METRIC_NAME; +import static org.apache.camel.component.micrometer.MicrometerConstants.HEADER_METRIC_TAGS; import static org.apache.camel.component.micrometer.MicrometerConstants.METRICS_REGISTRY_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @CamelSpringTest @@ -74,6 +77,9 @@ public class CounterRouteTest extends CamelSpringTestSupport { @Produce("direct:in-8") private ProducerTemplate producer8; + @Produce("direct:in-9") + private ProducerTemplate producer9; + @BindToRegistry(METRICS_REGISTRY_NAME) private MeterRegistry registry = new SimpleMeterRegistry(); @@ -119,6 +125,10 @@ public class CounterRouteTest extends CamelSpringTestSupport { from("direct:in-8") .to("micrometer:counter:H?decrement=${body}") .to("mock:out"); + + from("direct:in-9") + .to("micrometer:counter:I?tags.a=${body.length}&tags.b=uri") + .to("mock:out"); } }; } @@ -239,6 +249,34 @@ public class CounterRouteTest extends CamelSpringTestSupport { endpoint.assertIsSatisfied(); } + @Test + public void testHeaderTagsAreUsedAsGiven() throws Exception { + endpoint.expectedMessageCount(1); + producer9.sendBodyAndHeaders("Hello", Map.of( + "k", "key", + HEADER_METRIC_TAGS, Tags.of("c", "${body}", "${header.k}", "d"))); + Counter counter = registry.find("I").counter(); + assertEquals(1.0D, counter.count(), 0.01D); + // the tags from the uri are resolved per exchange + assertEquals("5", counter.getId().getTag("a")); + assertEquals("uri", counter.getId().getTag("b")); + // the tags from the header are not + assertEquals("${body}", counter.getId().getTag("c")); + assertEquals("d", counter.getId().getTag("${header.k}")); + assertNull(counter.getId().getTag("key")); + endpoint.assertIsSatisfied(); + } + + @Test + public void testHeaderTagOverridesUriTagWithSameKey() throws Exception { + endpoint.expectedMessageCount(1); + producer9.sendBodyAndHeader("Hello", HEADER_METRIC_TAGS, Tags.of("b", "header")); + Counter counter = registry.find("I").counter(); + assertEquals("5", counter.getId().getTag("a")); + assertEquals("header", counter.getId().getTag("b")); + endpoint.assertIsSatisfied(); + } + private void assertIncrementFails(Object body) { CamelExecutionException e = assertThrows(CamelExecutionException.class, () -> producer5.sendBody(body)); diff --git a/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json b/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json index 63b6574d4142..0dfaf8b6c9f4 100644 --- a/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json +++ b/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json @@ -35,7 +35,7 @@ "CamelMetricsCounterIncrement": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override increment value in URI", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_COUNTER_INCREMENT" }, "CamelMetricsName": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override name value in URI", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_METRIC_NAME" }, "CamelMetricsDescription": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Override description value in URI", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_METRIC_DESCRIPTION" }, - "CamelMetricsAttributes": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "io.opentelemetry.api.common.Attributes", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter attributes defined as URI parameters", "constantName": "org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_METRIC_ATTRIBUTES" } + "CamelMetricsAttributes": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "io.opentelemetry.api.common.Attributes", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To augment meter attributes defined as URI parameters. The attributes from the header are used as given; use the attributes URI parameters for Simple expressions.", "constantName": "org.apache.camel.opentele [...] }, "bodyTypes": { "producerIn": { "carriers": [ "any" ], "description": "Not used, unless an option such as increment, decrement or value is a simple expression that reads it, which must then give a number. The CamelMetrics headers override the options" }, diff --git a/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc b/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc index 4c30d69c9a03..da424ad67647 100644 --- a/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc +++ b/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc @@ -90,6 +90,11 @@ from("direct:in") will update a counter with name `new.name` (instead of `name.not.used`) using the attribute `dynamic-key` with value `dynamic-value` in addition to the attribute `key` with value `value`. +The attributes from the `CamelMetricsAttributes` header are used as given: unlike the `attributes` URI parameters, +their keys and values are not evaluated as `Simple` expressions. An attribute from the header overrides an attribute +from the URI with the same key. To add an attribute with a value from the message, use an `attributes` URI parameter +such as `attributes.customer=${header.customerId}`, or compute the value in the route before setting the header. + All Metrics specific headers are removed from the message once the OpenTelemetry endpoint finishes processing the exchange. While processing the exchange the OpenTelemetry endpoint will catch all exceptions and write log entry using level `warn`. diff --git a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/AbstractOpenTelemetryProducer.java b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/AbstractOpenTelemetryProducer.java index f033094546e4..db469ee5a212 100644 --- a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/AbstractOpenTelemetryProducer.java +++ b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/AbstractOpenTelemetryProducer.java @@ -16,10 +16,6 @@ */ package org.apache.camel.opentelemetry.metrics; -import java.util.HashMap; -import java.util.Map; - -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import org.apache.camel.Exchange; @@ -63,14 +59,13 @@ abstract public class AbstractOpenTelemetryProducer<T> extends DefaultProducer { String defaultMetricsDescription = simple(exchange, getEndpoint().getMetricsDescription(), String.class); String finalMetricsDescription = getStringHeader(in, HEADER_METRIC_DESCRIPTION, defaultMetricsDescription); - Map<AttributeKey<?>, Object> map = new HashMap<>(attributes.asMap()); - map.putAll(getAttributesHeader(in, HEADER_METRIC_ATTRIBUTES, Attributes.empty()).asMap()); - + // only the attributes from the endpoint are simple expressions, the attributes from the header are used as given AttributesBuilder ab = Attributes.builder(); - for (Map.Entry<AttributeKey<?>, Object> entry : map.entrySet()) { - ab.put(simple(exchange, entry.getKey().toString(), String.class), - simple(exchange, entry.getValue().toString(), String.class)); - } + attributes.forEach((key, value) -> ab.put( + simple(exchange, key.getKey(), String.class), + simple(exchange, value.toString(), String.class))); + getAttributesHeader(in, HEADER_METRIC_ATTRIBUTES, Attributes.empty()) + .forEach((key, value) -> ab.put(key.getKey(), value.toString())); ab.put(CAMEL_CONTEXT_ATTRIBUTE, getEndpoint().getCamelContext().getName()); try { diff --git a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java index 8c21da2361b1..c5e34fe5ab57 100644 --- a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java +++ b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java @@ -34,7 +34,8 @@ public class OpenTelemetryConstants { public static final String HEADER_METRIC_NAME = HEADER_PREFIX + "Name"; @Metadata(description = "Override description value in URI", javaType = "String") public static final String HEADER_METRIC_DESCRIPTION = HEADER_PREFIX + "Description"; - @Metadata(description = "To augment meter attributes defined as URI parameters", + @Metadata(description = "To augment meter attributes defined as URI parameters. The attributes from the header are used" + + " as given; use the attributes URI parameters for Simple expressions.", javaType = "io.opentelemetry.api.common.Attributes") public static final String HEADER_METRIC_ATTRIBUTES = HEADER_PREFIX + "Attributes"; diff --git a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/CounterRouteTest.java b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/CounterRouteTest.java index f6c0522151db..e18b1cc8cae8 100644 --- a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/CounterRouteTest.java +++ b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/CounterRouteTest.java @@ -93,6 +93,10 @@ public class CounterRouteTest extends CamelTestSupport { from("direct:in9") .to("opentelemetry-metrics:counter:I?decrement=${body}") .to("mock:result"); + + from("direct:in10") + .to("opentelemetry-metrics:counter:J?attributes.a=${body.length}&attributes.b=uri") + .to("mock:result"); } }; } @@ -307,6 +311,40 @@ public class CounterRouteTest extends CamelTestSupport { MockEndpoint.assertIsSatisfied(context); } + @Test + public void testHeaderAttributesAreUsedAsGiven() throws Exception { + MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); + mockEndpoint.expectedMessageCount(1); + template.sendBodyAndHeaders("direct:in10", "Hello", Map.of( + "k", "key", + HEADER_METRIC_ATTRIBUTES, Attributes.of( + AttributeKey.stringKey("c"), "${body}", + AttributeKey.stringKey("${header.k}"), "d"))); + + Attributes attributes = getMetric("J").getAttributes(); + // the attributes from the uri are resolved per exchange + assertEquals("5", attributes.get(AttributeKey.stringKey("a"))); + assertEquals("uri", attributes.get(AttributeKey.stringKey("b"))); + // the attributes from the header are not + assertEquals("${body}", attributes.get(AttributeKey.stringKey("c"))); + assertEquals("d", attributes.get(AttributeKey.stringKey("${header.k}"))); + assertNull(attributes.get(AttributeKey.stringKey("key"))); + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testHeaderAttributeOverridesUriAttributeWithSameKey() throws Exception { + MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); + mockEndpoint.expectedMessageCount(1); + template.sendBodyAndHeader("direct:in10", "Hello", HEADER_METRIC_ATTRIBUTES, + Attributes.of(AttributeKey.stringKey("b"), "header")); + + Attributes attributes = getMetric("J").getAttributes(); + assertEquals("5", attributes.get(AttributeKey.stringKey("a"))); + assertEquals("header", attributes.get(AttributeKey.stringKey("b"))); + MockEndpoint.assertIsSatisfied(context); + } + private void assertIncrementFails(Object body) { CamelExecutionException e = assertThrows(CamelExecutionException.class, () -> template.sendBody("direct:in7", body)); diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 9162438e1c97..cea5c9072764 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -61,6 +61,17 @@ Starting in 4.24, parameter values provided via the `CamelJpaParameters` header Routes that previously relied on evaluating Simple expressions within the `CamelJpaParameters` header should evaluate those expressions in the route before setting the header value. +=== camel-micrometer, camel-opentelemetry-metrics - tags and attributes from headers are used as given + +The `micrometer` and `opentelemetry-metrics` producers now use the tags of the `CamelMetricsTags` header and the +attributes of the `CamelMetricsAttributes` header as given. Before, their keys and values were evaluated as Simple +expressions, in the same way as the `tags` and `attributes` URI options. The URI options are still evaluated per +exchange, and a tag or attribute from the header still overrides one from the URI with the same key. + +A route that relied on a Simple expression in a header tag or attribute should use the `tags` / `attributes` URI +option instead (for example `tags.customer=${header.customerId}`), or compute the value in the route before +setting the header. + === camel-a2a - consumer context is separate from producer headers An A2A consumer no longer populates producer control headers with the incoming operation and diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MicrometerEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MicrometerEndpointBuilderFactory.java index 4b88b09c8454..c91e10c20b68 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MicrometerEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/MicrometerEndpointBuilderFactory.java @@ -367,7 +367,9 @@ public interface MicrometerEndpointBuilderFactory { return "CamelMetricsDescription"; } /** - * To augment meter tags defined as URI parameters. + * To augment meter tags defined as URI parameters. The tags from the + * header are used as given; use the tags URI parameters for Simple + * expressions. * * The option is a: {@code java.lang.Iterable<Tag>} type. * diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java index 557f759ecbda..34f28914140f 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java @@ -395,7 +395,9 @@ public interface OpenTelemetryEndpointBuilderFactory { return "CamelMetricsDescription"; } /** - * To augment meter attributes defined as URI parameters. + * To augment meter attributes defined as URI parameters. The attributes + * from the header are used as given; use the attributes URI parameters + * for Simple expressions. * * The option is a: {@code io.opentelemetry.api.common.Attributes} type. *
