diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java index 6466202b8a0e..0f3230a5bf8a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapter.java @@ -55,19 +55,14 @@ public boolean supportsContext(Observation.Context context) { @Override public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { - mutateClientRequest(context); - Iterable tags = this.tagsProvider.tags(context.getRequest(), context.getResponse(), context.getError()); + ClientRequest request = context.getRequest(); + if (request == null) { + request = context.getCarrier().attribute(URI_TEMPLATE_ATTRIBUTE, context.getUriTemplate()).build(); + } + Iterable tags = this.tagsProvider.tags(request, context.getResponse(), context.getError()); return KeyValues.of(tags, Tag::getKey, Tag::getValue); } - private void mutateClientRequest(ClientRequestObservationContext context) { - // WebClientExchangeTagsProvider relies on a request attribute to get the URI - // template, we need to adapt to that. - ClientRequest clientRequest = ClientRequest.from(context.getRequest()) - .attribute(URI_TEMPLATE_ATTRIBUTE, context.getUriTemplate()).build(); - context.setRequest(clientRequest); - } - @Override public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) { return KeyValues.empty(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java index 6740fd4154ae..6df42c00939f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/web/client/ClientObservationConventionAdapterTests.java @@ -29,6 +29,7 @@ import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientRequestObservationContext; import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +46,8 @@ class ClientObservationConventionAdapterTests { private ClientObservationConventionAdapter convention = new ClientObservationConventionAdapter(TEST_METRIC_NAME, new DefaultWebClientExchangeTagsProvider()); - private ClientRequest.Builder requestBuilder = ClientRequest.create(HttpMethod.GET, URI.create("/resource/test")); + private ClientRequest.Builder requestBuilder = ClientRequest.create(HttpMethod.GET, URI.create("/resource/test")) + .attribute(WebClient.class.getName() + ".uriTemplate", "/resource/{name}"); private ClientResponse response = ClientResponse.create(HttpStatus.OK).body("foo").build(); @@ -55,7 +57,6 @@ class ClientObservationConventionAdapterTests { void setup() { this.context = new ClientRequestObservationContext(); this.context.setCarrier(this.requestBuilder); - this.context.setRequest(this.requestBuilder.build()); this.context.setResponse(this.response); this.context.setUriTemplate("/resource/{name}"); } @@ -73,6 +74,14 @@ void shouldOnlySupportClientObservationContext() { @Test void shouldPushTagsAsLowCardinalityKeyValues() { + this.context.setRequest(this.requestBuilder.build()); + assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"), + KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), + KeyValue.of("method", "GET")); + } + + @Test + void doesNotFailWithEmptyRequest() { assertThat(this.convention.getLowCardinalityKeyValues(this.context)).contains(KeyValue.of("status", "200"), KeyValue.of("outcome", "SUCCESS"), KeyValue.of("uri", "/resource/{name}"), KeyValue.of("method", "GET"));