Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an outcome tag for web client metrics similar to MVC and WebFlux server metrics #15594

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
* Default implementation of {@link RestTemplateExchangeTagsProvider}.
*
* @author Jon Schneider
* @author Nishant Raut
* @since 2.0.0
*/
public class DefaultRestTemplateExchangeTagsProvider
@@ -41,7 +42,8 @@ public Iterable<Tag> getTags(String urlTemplate, HttpRequest request,
: RestTemplateExchangeTags.uri(request));
return Arrays.asList(RestTemplateExchangeTags.method(request), uriTag,
RestTemplateExchangeTags.status(response),
RestTemplateExchangeTags.clientName(request));
RestTemplateExchangeTags.clientName(request),
RestTemplateExchangeTags.outcome(response));
}

}
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@
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 @@
*
* @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 static Tag clientName(HttpRequest request) {
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;
}

}

}
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
* Default implementation of {@link WebClientExchangeTagsProvider}.
*
* @author Brian Clozel
* @author Nishant Raut
* @since 2.1.0
*/
public class DefaultWebClientExchangeTagsProvider
@@ -40,7 +41,8 @@ public Iterable<Tag> tags(ClientRequest request, ClientResponse response,
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,
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@

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 @@
* 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 static Tag clientName(ClientRequest request) {
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;
}

}
Original file line number Diff line number Diff line change
@@ -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");
}

}
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
* Tests for {@link DefaultWebClientExchangeTagsProvider}
*
* @author Brian Clozel
* @author Nishant Raut
*/
public class DefaultWebClientExchangeTagsProviderTests {

@@ -66,7 +67,7 @@ public void tagsShouldBePopulated() {
Iterable<Tag> 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 void tagsWhenNoUriTemplateShouldProvideUriPath() {
Iterable<Tag> 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
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
* Tests for {@link WebClientExchangeTags}
*
* @author Brian Clozel
* @author Nishant Raut
*/
public class WebClientExchangeTagsTests {

@@ -113,4 +114,45 @@ public void statusWhenClientException() {
.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");
}

}