diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java index f13bd6dfe90..d0635fa1409 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/DefaultRestTemplateExchangeTagsProvider.java @@ -28,6 +28,7 @@ import org.springframework.util.StringUtils; * Default implementation of {@link RestTemplateExchangeTagsProvider}. * * @author Jon Schneider + * @author Nishant Raut * @since 2.0.0 */ public class DefaultRestTemplateExchangeTagsProvider @@ -41,7 +42,8 @@ public class DefaultRestTemplateExchangeTagsProvider : RestTemplateExchangeTags.uri(request)); return Arrays.asList(RestTemplateExchangeTags.method(request), uriTag, RestTemplateExchangeTags.status(response), - RestTemplateExchangeTags.clientName(request)); + RestTemplateExchangeTags.clientName(request), + RestTemplateExchangeTags.outcome(response)); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java index 2dd9bc8a81c..4c647a97d24 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTags.java @@ -23,6 +23,7 @@ import java.util.regex.Pattern; import io.micrometer.core.instrument.Tag; import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; @@ -33,12 +34,25 @@ import org.springframework.web.client.RestTemplate; * * @author Andy Wilkinson * @author Jon Schneider + * @author Nishant Raut * @since 2.0.0 */ public final class RestTemplateExchangeTags { private static final Pattern STRIP_URI_PATTERN = Pattern.compile("^https?://[^/]+/"); + private static final Tag OUTCOME_UNKNOWN = Tag.of("outcome", "UNKNOWN"); + + private static final Tag OUTCOME_INFORMATIONAL = Tag.of("outcome", "INFORMATIONAL"); + + private static final Tag OUTCOME_SUCCESS = Tag.of("outcome", "SUCCESS"); + + private static final Tag OUTCOME_REDIRECTION = Tag.of("outcome", "REDIRECTION"); + + private static final Tag OUTCOME_CLIENT_ERROR = Tag.of("outcome", "CLIENT_ERROR"); + + private static final Tag OUTCOME_SERVER_ERROR = Tag.of("outcome", "SERVER_ERROR"); + private RestTemplateExchangeTags() { } @@ -115,4 +129,44 @@ public final class RestTemplateExchangeTags { return Tag.of("clientName", host); } + /** + * Creates a {@code outcome} {@code Tag} derived from the + * {@link ClientHttpResponse#getStatusCode() status} of the given {@code response}. + * @param response the response + * @return the outcome tag + * @since 2.2.0 + */ + public static Tag outcome(ClientHttpResponse response) { + if (response != null) { + HttpStatus status = extractStatus(response); + if (status != null) { + if (status.is1xxInformational()) { + return OUTCOME_INFORMATIONAL; + } + if (status.is2xxSuccessful()) { + return OUTCOME_SUCCESS; + } + if (status.is3xxRedirection()) { + return OUTCOME_REDIRECTION; + } + if (status.is4xxClientError()) { + return OUTCOME_CLIENT_ERROR; + } + } + return OUTCOME_SERVER_ERROR; + } + return OUTCOME_UNKNOWN; + } + + private static HttpStatus extractStatus(ClientHttpResponse response) { + + try { + return response.getStatusCode(); + } + catch (IOException ex) { + return null; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java index d747703b33d..1c012caa49c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProvider.java @@ -27,6 +27,7 @@ import org.springframework.web.reactive.function.client.ClientResponse; * Default implementation of {@link WebClientExchangeTagsProvider}. * * @author Brian Clozel + * @author Nishant Raut * @since 2.1.0 */ public class DefaultWebClientExchangeTagsProvider @@ -40,7 +41,8 @@ public class DefaultWebClientExchangeTagsProvider Tag clientName = WebClientExchangeTags.clientName(request); if (response != null) { return Arrays.asList(method, uri, clientName, - WebClientExchangeTags.status(response)); + WebClientExchangeTags.status(response), + WebClientExchangeTags.outcome(response)); } else { return Arrays.asList(method, uri, clientName, diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java index c5577f4cb20..a28ef9a0c28 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTags.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; import io.micrometer.core.instrument.Tag; +import org.springframework.http.HttpStatus; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; @@ -31,6 +32,7 @@ import org.springframework.web.reactive.function.client.WebClient; * performed by a {@link WebClient}. * * @author Brian Clozel + * @author Nishant Raut * @since 2.1.0 */ public final class WebClientExchangeTags { @@ -47,6 +49,18 @@ public final class WebClientExchangeTags { private static final Tag CLIENT_NAME_NONE = Tag.of("clientName", "none"); + private static final Tag OUTCOME_UNKNOWN = Tag.of("outcome", "UNKNOWN"); + + private static final Tag OUTCOME_INFORMATIONAL = Tag.of("outcome", "INFORMATIONAL"); + + private static final Tag OUTCOME_SUCCESS = Tag.of("outcome", "SUCCESS"); + + private static final Tag OUTCOME_REDIRECTION = Tag.of("outcome", "REDIRECTION"); + + private static final Tag OUTCOME_CLIENT_ERROR = Tag.of("outcome", "CLIENT_ERROR"); + + private static final Tag OUTCOME_SERVER_ERROR = Tag.of("outcome", "SERVER_ERROR"); + private WebClientExchangeTags() { } @@ -111,4 +125,33 @@ public final class WebClientExchangeTags { return Tag.of("clientName", host); } + /** + * Creates a {@code outcome} {@code Tag} derived from the + * {@link ClientResponse#statusCode() status} of the given {@code response}. + * @param response the response + * @return the outcome tag + * @since 2.2.0 + */ + public static Tag outcome(ClientResponse response) { + if (response != null) { + HttpStatus status = response.statusCode(); + if (status != null) { + if (status.is1xxInformational()) { + return OUTCOME_INFORMATIONAL; + } + if (status.is2xxSuccessful()) { + return OUTCOME_SUCCESS; + } + if (status.is3xxRedirection()) { + return OUTCOME_REDIRECTION; + } + if (status.is4xxClientError()) { + return OUTCOME_CLIENT_ERROR; + } + } + return OUTCOME_SERVER_ERROR; + } + return OUTCOME_UNKNOWN; + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java new file mode 100644 index 00000000000..cfc7dba3069 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/RestTemplateExchangeTagsTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2018 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. + * 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.springframework.boot.actuate.metrics.web.client; + +import io.micrometer.core.instrument.Tag; +import org.junit.Test; + +import org.springframework.http.HttpStatus; +import org.springframework.mock.http.client.MockClientHttpResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RestTemplateExchangeTags}. + * + * @author Nishant Raut + */ +public class RestTemplateExchangeTagsTests { + + private MockClientHttpResponse response; + + @Test + public void outcomeTagIsUnknownWhenResponseStatusIsNull() { + Tag tag = RestTemplateExchangeTags.outcome(null); + assertThat(tag.getValue()).isEqualTo("UNKNOWN"); + } + + @Test + public void outcomeTagIsInformationalWhenResponseIs1xx() { + this.response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.CONTINUE); + Tag tag = RestTemplateExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("INFORMATIONAL"); + } + + @Test + public void outcomeTagIsSuccessWhenResponseIs2xx() { + this.response = new MockClientHttpResponse("foo".getBytes(), HttpStatus.OK); + Tag tag = RestTemplateExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("SUCCESS"); + } + + @Test + public void outcomeTagIsRedirectionWhenResponseIs3xx() { + this.response = new MockClientHttpResponse("foo".getBytes(), + HttpStatus.MOVED_PERMANENTLY); + Tag tag = RestTemplateExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("REDIRECTION"); + } + + @Test + public void outcomeTagIsClientErrorWhenResponseIs4xx() { + this.response = new MockClientHttpResponse("foo".getBytes(), + HttpStatus.BAD_REQUEST); + Tag tag = RestTemplateExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); + } + + @Test + public void outcomeTagIsServerErrorWhenResponseIs5xx() { + this.response = new MockClientHttpResponse("foo".getBytes(), + HttpStatus.BAD_GATEWAY); + Tag tag = RestTemplateExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("SERVER_ERROR"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java index 61edf696a7e..377808da66d 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/DefaultWebClientExchangeTagsProviderTests.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link DefaultWebClientExchangeTagsProvider} * * @author Brian Clozel + * @author Nishant Raut */ public class DefaultWebClientExchangeTagsProviderTests { @@ -66,7 +67,7 @@ public class DefaultWebClientExchangeTagsProviderTests { Iterable tags = this.tagsProvider.tags(this.request, this.response, null); assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/{project}"), Tag.of("clientName", "example.org"), - Tag.of("status", "200")); + Tag.of("status", "200"), Tag.of("outcome", "SUCCESS")); } @Test @@ -76,7 +77,8 @@ public class DefaultWebClientExchangeTagsProviderTests { Iterable tags = this.tagsProvider.tags(request, this.response, null); assertThat(tags).containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/projects/spring-boot"), - Tag.of("clientName", "example.org"), Tag.of("status", "200")); + Tag.of("clientName", "example.org"), Tag.of("status", "200"), + Tag.of("outcome", "SUCCESS")); } @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java index ac8d88520f6..24c5ee9d5ef 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/client/WebClientExchangeTagsTests.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link WebClientExchangeTags} * * @author Brian Clozel + * @author Nishant Raut */ public class WebClientExchangeTagsTests { @@ -113,4 +114,45 @@ public class WebClientExchangeTagsTests { .isEqualTo(Tag.of("status", "CLIENT_ERROR")); } + @Test + public void outcomeTagIsUnknownWhenResponseStatusIsNull() { + Tag tag = WebClientExchangeTags.outcome(null); + assertThat(tag.getValue()).isEqualTo("UNKNOWN"); + } + + @Test + public void outcomeTagIsInformationalWhenResponseIs1xx() { + given(this.response.statusCode()).willReturn(HttpStatus.CONTINUE); + Tag tag = WebClientExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("INFORMATIONAL"); + } + + @Test + public void outcomeTagIsSuccessWhenResponseIs2xx() { + given(this.response.statusCode()).willReturn(HttpStatus.OK); + Tag tag = WebClientExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("SUCCESS"); + } + + @Test + public void outcomeTagIsRedirectionWhenResponseIs3xx() { + given(this.response.statusCode()).willReturn(HttpStatus.MOVED_PERMANENTLY); + Tag tag = WebClientExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("REDIRECTION"); + } + + @Test + public void outcomeTagIsClientErrorWhenResponseIs4xx() { + given(this.response.statusCode()).willReturn(HttpStatus.BAD_REQUEST); + Tag tag = WebClientExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("CLIENT_ERROR"); + } + + @Test + public void outcomeTagIsServerErrorWhenResponseIs5xx() { + given(this.response.statusCode()).willReturn(HttpStatus.BAD_GATEWAY); + Tag tag = WebClientExchangeTags.outcome(this.response); + assertThat(tag.getValue()).isEqualTo("SERVER_ERROR"); + } + }