diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index c6de84a222..4dd88d7d89 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -107,6 +107,9 @@ final class DefaultRestClient implements RestClient { private final ObservationRegistry observationRegistry; + @Nullable + private final ClientRequestObservationConvention observationConvention; + DefaultRestClient(ClientHttpRequestFactory clientRequestFactory, @Nullable List interceptors, @@ -116,6 +119,7 @@ final class DefaultRestClient implements RestClient { @Nullable List statusHandlers, List> messageConverters, ObservationRegistry observationRegistry, + @Nullable ClientRequestObservationConvention observationConvention, DefaultRestClientBuilder builder) { this.clientRequestFactory = clientRequestFactory; @@ -126,6 +130,7 @@ final class DefaultRestClient implements RestClient { this.defaultStatusHandlers = (statusHandlers != null ? new ArrayList<>(statusHandlers) : new ArrayList<>()); this.messageConverters = messageConverters; this.observationRegistry = observationRegistry; + this.observationConvention = observationConvention; this.builder = builder; } @@ -393,7 +398,7 @@ final class DefaultRestClient implements RestClient { clientRequest.getHeaders().addAll(headers); ClientRequestObservationContext observationContext = new ClientRequestObservationContext(clientRequest); observationContext.setUriTemplate((String) this.attributes.get(URI_TEMPLATE_ATTRIBUTE)); - observation = ClientHttpObservationDocumentation.HTTP_CLIENT_EXCHANGES.observation(null, + observation = ClientHttpObservationDocumentation.HTTP_CLIENT_EXCHANGES.observation(observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry).start(); if (this.body != null) { this.body.writeTo(clientRequest); diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java index 1d2f2aa70a..4aadfdd7a6 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java @@ -35,6 +35,7 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.http.client.JettyClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.client.observation.ClientRequestObservationConvention; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; @@ -132,6 +133,9 @@ final class DefaultRestClientBuilder implements RestClient.Builder { private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + @Nullable + private ClientRequestObservationConvention observationConvention; + public DefaultRestClientBuilder() { } @@ -161,6 +165,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder { this.interceptors = (other.interceptors != null) ? new ArrayList<>(other.interceptors) : null; this.initializers = (other.initializers != null) ? new ArrayList<>(other.initializers) : null; this.observationRegistry = other.observationRegistry; + this.observationConvention = other.observationConvention; } public DefaultRestClientBuilder(RestTemplate restTemplate) { @@ -182,6 +187,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder { this.initializers = new ArrayList<>(restTemplate.getClientHttpRequestInitializers()); } this.observationRegistry = restTemplate.getObservationRegistry(); + this.observationConvention = restTemplate.getObservationConvention(); } @@ -307,6 +313,12 @@ final class DefaultRestClientBuilder implements RestClient.Builder { return this; } + @Override + public RestClient.Builder observationConvention(ClientRequestObservationConvention observationConvention) { + this.observationConvention = observationConvention; + return this; + } + @Override public RestClient.Builder apply(Consumer builderConsumer) { builderConsumer.accept(this); @@ -362,6 +374,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder { this.statusHandlers, messageConverters, this.observationRegistry, + this.observationConvention, new DefaultRestClientBuilder(this) ); } diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClient.java b/spring-web/src/main/java/org/springframework/web/client/RestClient.java index 2efe052fb6..30293c3c9c 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestClient.java @@ -42,6 +42,7 @@ import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInitializer; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.observation.ClientRequestObservationConvention; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.web.util.DefaultUriBuilderFactory; @@ -374,6 +375,16 @@ public interface RestClient { */ Builder observationRegistry(ObservationRegistry observationRegistry); + /** + * Configure the {@link io.micrometer.observation.ObservationConvention} to use + * for collecting metadata for the request observation. Will use + * {@link org.springframework.http.client.observation.DefaultClientRequestObservationConvention} + * if none provided. + * @param observationConvention the observation convention to use + * @return this builder + */ + Builder observationConvention(ClientRequestObservationConvention observationConvention); + /** * Apply the given {@code Consumer} to this builder instance. *

This can be useful for applying pre-packaged customizations. diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index 9377c9fcf6..aee19c5392 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -375,6 +375,15 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat this.observationConvention = observationConvention; } + /** + * Return the configured {@link ClientRequestObservationConvention}, or {@code null} if not set. + * @since 6.1 + */ + @Nullable + public ClientRequestObservationConvention getObservationConvention() { + return this.observationConvention; + } + // GET @Override diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java index 67b112bee1..3dd6c5f87a 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java @@ -37,6 +37,8 @@ import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.observation.ClientRequestObservationContext; +import org.springframework.http.client.observation.ClientRequestObservationConvention; +import org.springframework.http.client.observation.DefaultClientRequestObservationConvention; import org.springframework.http.converter.HttpMessageConverter; import static org.assertj.core.api.Assertions.assertThat; @@ -159,6 +161,20 @@ class RestClientObservationTests { assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "UNKNOWN"); } + @Test + void executeWithCustomConventionUsesCustomObservationName() throws Exception { + ClientRequestObservationConvention observationConvention = + new DefaultClientRequestObservationConvention("custom.requests"); + RestClient restClient = this.client.mutate().observationConvention(observationConvention).build(); + mockSentRequest(GET, "https://example.org"); + mockResponseStatus(HttpStatus.OK); + + restClient.get().uri("https://example.org").retrieve().toBodilessEntity(); + + TestObservationRegistryAssert.assertThat(this.observationRegistry) + .hasObservationWithNameEqualTo("custom.requests"); + } + private void mockSentRequest(HttpMethod method, String uri) throws Exception { mockSentRequest(method, uri, new HttpHeaders());