diff --git a/framework-docs/src/docs/asciidoc/integration/observability.adoc b/framework-docs/src/docs/asciidoc/integration/observability.adoc index dabca4a72e..20d0d94669 100644 --- a/framework-docs/src/docs/asciidoc/integration/observability.adoc +++ b/framework-docs/src/docs/asciidoc/integration/observability.adoc @@ -131,18 +131,18 @@ Instrumentation is using the `org.springframework.http.client.observation.Client [cols="a,a"] |=== |Name | Description -|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened. |`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created. -|`outcome` _(required)_|Outcome of the HTTP client exchange. -|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received. |`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. +|`client.name` _(required)_|Client name derived from the request URI host. +|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received. +|`outcome` _(required)_|Outcome of the HTTP client exchange. +|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened. |=== .High cardinality Keys [cols="a,a"] |=== |Name | Description -|`client.name` _(required)_|Client name derived from the request URI host. |`http.url` _(required)_|HTTP request URI. |=== @@ -157,18 +157,18 @@ Instrumentation is using the `org.springframework.web.reactive.function.client.C [cols="a,a"] |=== |Name | Description -|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened. |`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created. -|`outcome` _(required)_|Outcome of the HTTP client exchange. -|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received. |`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. +|`client.name` _(required)_|Client name derived from the request URI host. +|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received. +|`outcome` _(required)_|Outcome of the HTTP client exchange. +|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened. |=== .High cardinality Keys [cols="a,a"] |=== |Name | Description -|`client.name` _(required)_|Client name derived from the request URI host. |`http.url` _(required)_|HTTP request URI. |=== diff --git a/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java b/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java index 223de1369d..58bb2ed287 100644 --- a/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java +++ b/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati @Override public KeyName[] getHighCardinalityKeyNames() { - return HighCardinalityKeyNames.values(); + return new KeyName[] {HighCardinalityKeyNames.HTTP_URL}; } }; @@ -89,6 +89,17 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati } }, + + /** + * Client name derived from the request URI host. + */ + CLIENT_NAME { + @Override + public String asString() { + return "client.name"; + } + }, + /** * Name of the exception thrown during the exchange, or {@value KeyValue#NONE_VALUE} if no exception happened. */ @@ -126,7 +137,10 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati /** * Client name derived from the request URI host. + * @deprecated in favor of {@link LowCardinalityKeyNames#CLIENT_NAME}. + * This will be available both as a low and high cardinality key value. */ + @Deprecated(since = "6.0.5", forRemoval = true) CLIENT_NAME { @Override public String asString() { diff --git a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java index f3e26c60bd..442a7efdfe 100644 --- a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,12 +51,12 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO private static final KeyValue HTTP_OUTCOME_UNKNOWN = KeyValue.of(LowCardinalityKeyNames.OUTCOME, "UNKNOWN"); + private static final KeyValue CLIENT_NAME_NONE = KeyValue.of(LowCardinalityKeyNames.CLIENT_NAME, KeyValue.NONE_VALUE); + private static final KeyValue EXCEPTION_NONE = KeyValue.of(LowCardinalityKeyNames.EXCEPTION, KeyValue.NONE_VALUE); private static final KeyValue HTTP_URL_NONE = KeyValue.of(HighCardinalityKeyNames.HTTP_URL, KeyValue.NONE_VALUE); - private static final KeyValue CLIENT_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.CLIENT_NAME, KeyValue.NONE_VALUE); - private final String name; @@ -87,7 +87,7 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO @Override public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { - return KeyValues.of(uri(context), method(context), status(context), exception(context), outcome(context)); + return KeyValues.of(uri(context), method(context), status(context), clientName(context), exception(context), outcome(context)); } protected KeyValue uri(ClientRequestObservationContext context) { @@ -119,6 +119,13 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO } } + protected KeyValue clientName(ClientRequestObservationContext context) { + if (context.getCarrier() != null && context.getCarrier().getURI().getHost() != null) { + return KeyValue.of(LowCardinalityKeyNames.CLIENT_NAME, context.getCarrier().getURI().getHost()); + } + return CLIENT_NAME_NONE; + } + protected KeyValue exception(ClientRequestObservationContext context) { Throwable error = context.getError(); if (error != null) { @@ -129,7 +136,7 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO return EXCEPTION_NONE; } - protected static KeyValue outcome(ClientRequestObservationContext context) { + protected KeyValue outcome(ClientRequestObservationContext context) { if (context.getResponse() != null) { try { return HttpOutcome.forStatus(context.getResponse().getStatusCode()); @@ -143,7 +150,7 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO @Override public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) { - return KeyValues.of(requestUri(context), clientName(context)); + return KeyValues.of(requestUri(context)); } protected KeyValue requestUri(ClientRequestObservationContext context) { @@ -153,12 +160,6 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO return HTTP_URL_NONE; } - protected KeyValue clientName(ClientRequestObservationContext context) { - if (context.getCarrier() != null && context.getCarrier().getURI().getHost() != null) { - return KeyValue.of(HighCardinalityKeyNames.CLIENT_NAME, context.getCarrier().getURI().getHost()); - } - return CLIENT_NAME_NONE; - } static class HttpOutcome { diff --git a/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientRequestObservationConventionTests.java b/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientRequestObservationConventionTests.java index a5bb234337..14ee942883 100644 --- a/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientRequestObservationConventionTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientRequestObservationConventionTests.java @@ -69,9 +69,8 @@ class DefaultClientRequestObservationConventionTests { context.setUriTemplate("/resource/{id}"); assertThat(this.observationConvention.getLowCardinalityKeyValues(context)) .contains(KeyValue.of("exception", "none"), KeyValue.of("method", "GET"), KeyValue.of("uri", "/resource/{id}"), - KeyValue.of("status", "200"), KeyValue.of("outcome", "SUCCESS")); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(2) - .contains(KeyValue.of("client.name", "none"), KeyValue.of("http.url", "/resource/42")); + KeyValue.of("status", "200"), KeyValue.of("client.name", "none"), KeyValue.of("outcome", "SUCCESS")); + assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("http.url", "/resource/42")); } @Test @@ -79,9 +78,8 @@ class DefaultClientRequestObservationConventionTests { ClientRequestObservationContext context = createContext( new MockClientHttpRequest(HttpMethod.GET, "/resource/42"), new MockClientHttpResponse()); assertThat(this.observationConvention.getLowCardinalityKeyValues(context)) - .contains(KeyValue.of("method", "GET"), KeyValue.of("uri", "none")); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(2) - .contains(KeyValue.of("http.url", "/resource/42")); + .contains(KeyValue.of("method", "GET"), KeyValue.of("client.name", "none"), KeyValue.of("uri", "none")); + assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("http.url", "/resource/42")); } @Test @@ -89,7 +87,7 @@ class DefaultClientRequestObservationConventionTests { ClientRequestObservationContext context = createContext( new MockClientHttpRequest(HttpMethod.GET, "https://localhost:8080/resource/42"), new MockClientHttpResponse()); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("client.name", "localhost")); + assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("client.name", "localhost")); } @Test diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientHttpObservationDocumentation.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientHttpObservationDocumentation.java index 04759b14fa..aa895b4abc 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientHttpObservationDocumentation.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientHttpObservationDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati @Override public KeyName[] getHighCardinalityKeyNames() { - return HighCardinalityKeyNames.values(); + return new KeyName[] {HighCardinalityKeyNames.HTTP_URL}; } }; @@ -86,6 +86,16 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati } }, + /** + * Client name derived from the request URI host. + */ + CLIENT_NAME { + @Override + public String asString() { + return "client.name"; + } + }, + /** * Name of the exception thrown during the exchange, or {@value KeyValue#NONE_VALUE} if no exception happened. */ @@ -124,7 +134,10 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati /** * Client name derived from the request URI host. + * @deprecated in favor of {@link LowCardinalityKeyNames#CLIENT_NAME}. + * This will be available both as a low and high cardinality key value. */ + @Deprecated(since = "6.0.5", forRemoval = true) CLIENT_NAME { @Override public String asString() { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java index 294dfa039c..ca097805ec 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java @@ -50,11 +50,12 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO private static final KeyValue HTTP_OUTCOME_UNKNOWN = KeyValue.of(LowCardinalityKeyNames.OUTCOME, "UNKNOWN"); + private static final KeyValue CLIENT_NAME_NONE = KeyValue.of(LowCardinalityKeyNames.CLIENT_NAME, KeyValue.NONE_VALUE); + private static final KeyValue EXCEPTION_NONE = KeyValue.of(LowCardinalityKeyNames.EXCEPTION, KeyValue.NONE_VALUE); private static final KeyValue HTTP_URL_NONE = KeyValue.of(HighCardinalityKeyNames.HTTP_URL, KeyValue.NONE_VALUE); - private static final KeyValue CLIENT_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.CLIENT_NAME, KeyValue.NONE_VALUE); private final String name; @@ -86,7 +87,7 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO @Override public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { - return KeyValues.of(uri(context), method(context), status(context), exception(context), outcome(context)); + return KeyValues.of(uri(context), method(context), status(context), clientName(context), exception(context), outcome(context)); } protected KeyValue uri(ClientRequestObservationContext context) { @@ -119,6 +120,13 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO return STATUS_CLIENT_ERROR; } + protected KeyValue clientName(ClientRequestObservationContext context) { + if (context.getRequest() != null && context.getRequest().url().getHost() != null) { + return KeyValue.of(LowCardinalityKeyNames.CLIENT_NAME, context.getRequest().url().getHost()); + } + return CLIENT_NAME_NONE; + } + protected KeyValue exception(ClientRequestObservationContext context) { Throwable error = context.getError(); if (error != null) { @@ -141,7 +149,7 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO @Override public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) { - return KeyValues.of(httpUrl(context), clientName(context)); + return KeyValues.of(httpUrl(context)); } protected KeyValue httpUrl(ClientRequestObservationContext context) { @@ -151,13 +159,6 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO return HTTP_URL_NONE; } - protected KeyValue clientName(ClientRequestObservationContext context) { - if (context.getRequest() != null && context.getRequest().url().getHost() != null) { - return KeyValue.of(HighCardinalityKeyNames.CLIENT_NAME, context.getRequest().url().getHost()); - } - return CLIENT_NAME_NONE; - } - static class HttpOutcome { static KeyValue forStatus(HttpStatusCode statusCode) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConventionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConventionTests.java index a45c7aeb0f..e195a3506b 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConventionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConventionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,22 +58,23 @@ class DefaultClientRequestObservationConventionTests { @Test void shouldAddKeyValuesForNullExchange() { ClientRequestObservationContext context = new ClientRequestObservationContext(); - assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).hasSize(5) + assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).hasSize(6) .contains(KeyValue.of("method", "none"), KeyValue.of("uri", "none"), KeyValue.of("status", "CLIENT_ERROR"), + KeyValue.of("client.name", "none"), KeyValue.of("exception", "none"), KeyValue.of("outcome", "UNKNOWN")); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(2) - .contains(KeyValue.of("client.name", "none"), KeyValue.of("http.url", "none")); + assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(1) + .contains(KeyValue.of("http.url", "none")); } @Test void shouldAddKeyValuesForExchangeWithException() { ClientRequestObservationContext context = new ClientRequestObservationContext(); context.setError(new IllegalStateException("Could not create client request")); - assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).hasSize(5) + assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).hasSize(6) .contains(KeyValue.of("method", "none"), KeyValue.of("uri", "none"), KeyValue.of("status", "CLIENT_ERROR"), - KeyValue.of("exception", "IllegalStateException"), KeyValue.of("outcome", "UNKNOWN")); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(2) - .contains(KeyValue.of("client.name", "none"), KeyValue.of("http.url", "none")); + KeyValue.of("client.name", "none"), KeyValue.of("exception", "IllegalStateException"), KeyValue.of("outcome", "UNKNOWN")); + assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(1) + .contains(KeyValue.of("http.url", "none")); } @Test @@ -85,9 +86,9 @@ class DefaultClientRequestObservationConventionTests { context.setRequest(context.getCarrier().build()); assertThat(this.observationConvention.getLowCardinalityKeyValues(context)) .contains(KeyValue.of("exception", "none"), KeyValue.of("method", "GET"), KeyValue.of("uri", "/resource/{id}"), - KeyValue.of("status", "200"), KeyValue.of("outcome", "SUCCESS")); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(2) - .contains(KeyValue.of("client.name", "none"), KeyValue.of("http.url", "/resource/42")); + KeyValue.of("status", "200"), KeyValue.of("client.name", "none"), KeyValue.of("outcome", "SUCCESS")); + assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(1) + .contains(KeyValue.of("http.url", "/resource/42")); } @Test @@ -96,14 +97,14 @@ class DefaultClientRequestObservationConventionTests { context.setRequest(context.getCarrier().build()); assertThat(this.observationConvention.getLowCardinalityKeyValues(context)) .contains(KeyValue.of("method", "GET"), KeyValue.of("uri", "none")); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(2).contains(KeyValue.of("http.url", "/resource/42")); + assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(1).contains(KeyValue.of("http.url", "/resource/42")); } @Test void shouldAddClientNameKeyValueForRequestWithHost() { ClientRequestObservationContext context = createContext(ClientRequest.create(HttpMethod.GET, URI.create("https://localhost:8080/resource/42"))); context.setRequest(context.getCarrier().build()); - assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("client.name", "localhost")); + assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("client.name", "localhost")); } private ClientRequestObservationContext createContext(ClientRequest.Builder request) {