diff --git a/components-starter/camel-micrometer-starter/pom.xml b/components-starter/camel-micrometer-starter/pom.xml
index 2f3880a9791..86f042fffdb 100644
--- a/components-starter/camel-micrometer-starter/pom.xml
+++ b/components-starter/camel-micrometer-starter/pom.xml
@@ -54,6 +54,17 @@
camel-http-common
${camel-version}
+
+
+ org.apache.camel.springboot
+ camel-servlet-starter
+ test
+
+
+ org.awaitility
+ awaitility
+ test
+
org.apache.camel.springboot
diff --git a/components-starter/camel-micrometer-starter/src/main/docs/micrometer.json b/components-starter/camel-micrometer-starter/src/main/docs/micrometer.json
index f2ffa87891d..f6073d988e5 100644
--- a/components-starter/camel-micrometer-starter/src/main/docs/micrometer.json
+++ b/components-starter/camel-micrometer-starter/src/main/docs/micrometer.json
@@ -133,7 +133,7 @@
{
"name": "camel.metrics.uri-tag-dynamic",
"type": "java.lang.Boolean",
- "description": "Whether to use static or dynamic values for HTTP uri tags in captured metrics. When using dynamic tags, then a REST service with base URL: \/users\/{id} will capture metrics with uri tag with the actual dynamic value such as: \/users\/123. However, this can lead to many tags as the URI is dynamic, so use this with care.",
+ "description": "Whether to use static or dynamic values for HTTP uri tags in captured metrics. When using dynamic tags, then a REST service with base URL: \/users\/{id} will capture metrics with uri tag with the actual dynamic value such as: \/users\/123. However, this can lead to many tags as the URI is dynamic, so use this with care. The dynamic value is only used for requests that are resolved to a Camel HTTP consumer, any other request is tagged by the default Spring convention.",
"sourceType": "org.apache.camel.component.micrometer.springboot.metrics.CamelMetricsConfiguration",
"defaultValue": false
},
diff --git a/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/MicrometerTagsAutoConfiguration.java b/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/MicrometerTagsAutoConfiguration.java
index ecb5f3ac529..8ffa746bdc7 100644
--- a/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/MicrometerTagsAutoConfiguration.java
+++ b/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/MicrometerTagsAutoConfiguration.java
@@ -36,9 +36,19 @@
@AutoConfiguration(after = CamelAutoConfiguration.class)
@Conditional({ ConditionalOnCamelContextAndAutoConfigurationBeans.class })
-@ConditionalOnProperty(prefix = "camel.metrics", name = "uriTagEnabled", havingValue = "true")
+@ConditionalOnProperty(prefix = "camel.metrics", name = "uri-tag-enabled", havingValue = "true")
public class MicrometerTagsAutoConfiguration {
+ /**
+ * Name of the low cardinality key holding the http uri.
+ */
+ private static final String URI = "uri";
+
+ /**
+ * Maximum length of the uri tag value when using dynamic uri tags, to keep the tag value bounded.
+ */
+ private static final int MAX_URI_LENGTH = 200;
+
/**
* To integrate with micrometer to include expanded uri in tags when for example using camel rest-dsl with servlet.
*/
@@ -50,39 +60,71 @@ ServerRequestObservationConvention serverRequestObservationConvention(Optional MAX_URI_LENGTH ? uri.substring(0, MAX_URI_LENGTH) : uri;
+ StringBuilder sb = new StringBuilder(answer.length());
+ for (int i = 0; i < answer.length(); i++) {
+ char ch = answer.charAt(i);
+ sb.append(Character.isISOControl(ch) ? '_' : ch);
+ }
+ return sb.toString();
+ }
}
diff --git a/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/metrics/CamelMetricsConfiguration.java b/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/metrics/CamelMetricsConfiguration.java
index 787adee4876..888edd5601e 100644
--- a/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/metrics/CamelMetricsConfiguration.java
+++ b/components-starter/camel-micrometer-starter/src/main/java/org/apache/camel/component/micrometer/springboot/metrics/CamelMetricsConfiguration.java
@@ -32,7 +32,8 @@ public class CamelMetricsConfiguration {
*
* When using dynamic tags, then a REST service with base URL: /users/{id} will capture metrics with uri tag with
* the actual dynamic value such as: /users/123. However, this can lead to many tags as the URI is dynamic, so use
- * this with care.
+ * this with care. The dynamic value is only used for requests that are resolved to a Camel HTTP consumer, any
+ * other request is tagged by the default Spring convention.
*/
private boolean uriTagDynamic;
diff --git a/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagDynamicTest.java b/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagDynamicTest.java
new file mode 100644
index 00000000000..2b346bfa449
--- /dev/null
+++ b/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagDynamicTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.micrometer.springboot;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * With dynamic uri tags, the requested path is used as uri tag, but only for requests that are resolved to a Camel
+ * consumer, and the tag value is kept bounded in length.
+ */
+@DirtiesContext
+@CamelSpringBootTest
+@EnableAutoConfiguration
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ classes = { CamelAutoConfiguration.class, MicrometerUriTagTestSupport.TestConfiguration.class },
+ // the legacy camelCase spelling of the properties must keep working as well
+ properties = { "camel.metrics.uriTagEnabled=true", "camel.metrics.uriTagDynamic=true" })
+public class MicrometerUriTagDynamicTest extends MicrometerUriTagTestSupport {
+
+ private static final int REQUESTS = 10;
+ private static final int MAX_URI_LENGTH = 200;
+
+ @Order(1)
+ @Test
+ void unmatchedRequestsShareASingleMeter() throws Exception {
+ for (int i = 0; i < REQUESTS; i++) {
+ assertEquals(404, get("/camel/no-such-path-" + i));
+ }
+
+ await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+ Map tags = uriTags();
+ assertEquals(1, tags.size(), "Expected a single uri tag value but got " + tags);
+ assertEquals(REQUESTS, tags.values().iterator().next());
+ assertFalse(tags.keySet().stream().anyMatch(uri -> uri.contains("no-such-path")),
+ "The requested path must not be used as uri tag but got " + tags);
+ });
+ }
+
+ @Order(2)
+ @Test
+ void matchedRequestsUseTheRequestedPath() throws Exception {
+ assertEquals(200, get("/camel/users/123"));
+
+ await().atMost(10, TimeUnit.SECONDS)
+ .untilAsserted(() -> assertEquals(1, count("/camel/users/123"), "Got uri tags " + uriTags()));
+ }
+
+ @Order(3)
+ @Test
+ void longRequestedPathIsCapped() throws Exception {
+ assertEquals(200, get("/camel/users/" + "a".repeat(300)));
+
+ await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+ Map tags = uriTags();
+ assertTrue(tags.keySet().stream().anyMatch(uri -> uri.length() == MAX_URI_LENGTH
+ && uri.startsWith("/camel/users/aaa")), "Expected a capped uri tag value but got " + tags);
+ assertFalse(tags.keySet().stream().anyMatch(uri -> uri.length() > MAX_URI_LENGTH),
+ "No uri tag value must be longer than " + MAX_URI_LENGTH + " but got " + tags);
+ });
+ }
+}
diff --git a/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagTest.java b/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagTest.java
new file mode 100644
index 00000000000..a87ad86dd5a
--- /dev/null
+++ b/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.micrometer.springboot;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+/**
+ * The uri tag must be the static path of the Camel consumer, and requests that are not for a Camel consumer must not
+ * add a meter per requested path.
+ */
+@DirtiesContext
+@CamelSpringBootTest
+@EnableAutoConfiguration
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ classes = { CamelAutoConfiguration.class, MicrometerUriTagTestSupport.TestConfiguration.class },
+ properties = { "camel.metrics.uri-tag-enabled=true" })
+public class MicrometerUriTagTest extends MicrometerUriTagTestSupport {
+
+ private static final int REQUESTS = 10;
+
+ @Order(1)
+ @Test
+ void unmatchedRequestsShareASingleMeter() throws Exception {
+ for (int i = 0; i < REQUESTS; i++) {
+ assertEquals(404, get("/camel/no-such-path-" + i));
+ }
+
+ await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+ Map tags = uriTags();
+ assertEquals(1, tags.size(), "Expected a single uri tag value but got " + tags);
+ assertEquals(REQUESTS, tags.values().iterator().next());
+ assertFalse(tags.keySet().stream().anyMatch(uri -> uri.contains("no-such-path")),
+ "The requested path must not be used as uri tag but got " + tags);
+ });
+ }
+
+ @Order(2)
+ @Test
+ void matchedRequestsUseTheConsumerPath() throws Exception {
+ assertEquals(200, get("/camel/users/123"));
+ assertEquals(200, get("/camel/users/456"));
+
+ await().atMost(10, TimeUnit.SECONDS)
+ .untilAsserted(() -> assertEquals(2, count("/users/{id}"), "Got uri tags " + uriTags()));
+ }
+}
diff --git a/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagTestSupport.java b/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagTestSupport.java
new file mode 100644
index 00000000000..7768916f6bc
--- /dev/null
+++ b/components-starter/camel-micrometer-starter/src/test/java/org/apache/camel/component/micrometer/springboot/MicrometerUriTagTestSupport.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.micrometer.springboot;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import org.apache.camel.builder.RouteBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+
+/**
+ * Base class for the tests capturing the uri tag of the {@code http.server.requests} meters.
+ */
+public abstract class MicrometerUriTagTestSupport {
+
+ protected static final String HTTP_SERVER_REQUESTS = "http.server.requests";
+ protected static final String URI_TAG = "uri";
+
+ @Autowired
+ protected Environment env;
+
+ @Autowired
+ protected MeterRegistry meterRegistry;
+
+ /**
+ * Performs a HTTP GET on the given path, and returns the http status code.
+ */
+ protected int get(String path) throws Exception {
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:" + env.getRequiredProperty("local.server.port") + path))
+ .GET()
+ .build();
+ return client.send(request, HttpResponse.BodyHandlers.ofString()).statusCode();
+ }
+
+ /**
+ * The uri tag values of the captured http server request meters, and how many requests each of them counted.
+ */
+ protected Map uriTags() {
+ Map answer = new LinkedHashMap<>();
+ for (Timer timer : meterRegistry.find(HTTP_SERVER_REQUESTS).timers()) {
+ answer.merge(timer.getId().getTag(URI_TAG), timer.count(), Long::sum);
+ }
+ return answer;
+ }
+
+ /**
+ * Number of requests counted for the given uri tag value.
+ */
+ protected long count(String uri) {
+ Timer timer = meterRegistry.find(HTTP_SERVER_REQUESTS).tag(URI_TAG, uri).timer();
+ return timer != null ? timer.count() : 0;
+ }
+
+ @Configuration
+ public static class TestConfiguration {
+
+ @Bean
+ public RouteBuilder routeBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("servlet:/users/{id}")
+ .setBody().constant("Hello");
+ }
+ };
+ }
+ }
+}
diff --git a/docs/spring-boot/modules/ROOT/pages/starters/micrometer.adoc b/docs/spring-boot/modules/ROOT/pages/starters/micrometer.adoc
index 2f252165459..54d74e42a2e 100644
--- a/docs/spring-boot/modules/ROOT/pages/starters/micrometer.adoc
+++ b/docs/spring-boot/modules/ROOT/pages/starters/micrometer.adoc
@@ -44,6 +44,6 @@ The starter supports 18 options, which are listed below.
| camel.metrics.naming-strategy | Controls the name style to use for metrics. Default = uses micrometer naming convention. Legacy = uses the classic naming style (camelCase) | default | String
| camel.metrics.route-policy-exclude-pattern | Pattern to exclude routes (by id) to capture. Multiple route ids can be separated by comma. | | String
| camel.metrics.route-policy-level | Sets the level of information to capture. Possible values are all,route,context. all = both context and routes. route = routes only. context = camel context only. | all | String
-| camel.metrics.uri-tag-dynamic | Whether to use static or dynamic values for HTTP uri tags in captured metrics. When using dynamic tags, then a REST service with base URL: /users/\{id} will capture metrics with uri tag with the actual dynamic value such as: /users/123. However, this can lead to many tags as the URI is dynamic, so use this with care. | false | Boolean
+| camel.metrics.uri-tag-dynamic | Whether to use static or dynamic values for HTTP uri tags in captured metrics. When using dynamic tags, then a REST service with base URL: /users/\{id} will capture metrics with uri tag with the actual dynamic value such as: /users/123. However, this can lead to many tags as the URI is dynamic, so use this with care. The dynamic value is only used for requests that are resolved to a Camel HTTP consumer, any other request is tagged by the default Spring convention. | false | Boolean
| camel.metrics.uri-tag-enabled | Whether HTTP uri tags should be enabled or not in captured metrics. If disabled then the uri tag, is likely not able to be resolved and will be marked as UNKNOWN. | true | Boolean
|===