Merge remote-tracking branch 'origin/2.1.x'

This commit is contained in:
Ryan Baxter
2019-04-01 12:21:52 -04:00
3 changed files with 54 additions and 94 deletions

View File

@@ -1155,6 +1155,8 @@ To enable Gateway Metrics add spring-boot-starter-actuator as a project dependen
* `routeUri`: The URI that the API will be routed to
* `outcome`: Outcome as classified by link:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpStatus.Series.html[HttpStatus.Series]
* `status`: Http Status of the request returned to the client
* `httpStatusCode`: Http Status of the request returned to the client
* `httpMethod`: The Http method used for the request
These metrics are then available to be scraped from ``/actuator/metrics/gateway.requests`` and can be easily integated with Prometheus to create a link:images/gateway-grafana-dashboard.jpeg[Grafana] link:gateway-grafana-dashboard.json[dashboard].

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2017 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.
@@ -12,17 +12,12 @@
* 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.cloud.gateway.filter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Timer.Sample;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
@@ -31,13 +26,15 @@ import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Timer.Sample;
import reactor.core.publisher.Mono;
public class GatewayMetricsFilter implements GlobalFilter, Ordered {
private final Log log = LogFactory.getLog(getClass());
private final MeterRegistry meterRegistry;
private MeterRegistry meterRegistry;
public GatewayMetricsFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
@@ -47,7 +44,7 @@ public class GatewayMetricsFilter implements GlobalFilter, Ordered {
public int getOrder() {
// start the timer as soon as possible and report the metric event before we write
// response to client
return Ordered.HIGHEST_PRECEDENCE + 10000;
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
@@ -76,8 +73,12 @@ public class GatewayMetricsFilter implements GlobalFilter, Ordered {
private void endTimerInner(ServerWebExchange exchange, Sample sample) {
String outcome = "CUSTOM";
String status = "CUSTOM";
String httpStatusCodeStr = "NA";
String httpMethod = exchange.getRequest().getMethodValue();
HttpStatus statusCode = exchange.getResponse().getStatusCode();
if (statusCode != null) {
httpStatusCodeStr = String.valueOf(statusCode.value());
outcome = statusCode.series().name();
status = statusCode.name();
}
@@ -87,19 +88,18 @@ public class GatewayMetricsFilter implements GlobalFilter, Ordered {
.getStatusCodeValue();
if (statusInt != null) {
status = String.valueOf(statusInt);
httpStatusCodeStr = status;
}
else {
status = "NA";
}
}
}
// TODO refactor to allow Tags provider like in MetricsWebFilter
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
Tags tags = Tags.of("outcome", outcome, "status", status, "routeId",
route.getId(), "routeUri", route.getUri().toString());
if (log.isTraceEnabled()) {
log.trace("Stopping timer 'gateway.requests' with tags " + tags);
}
Tags tags = Tags.of("outcome", outcome, "status", status, "httpStatusCode",
httpStatusCodeStr, "routeId", route.getId(), "routeUri",
route.getUri().toString(), "httpMethod", httpMethod);
sample.stop(meterRegistry.timer("gateway.requests", tags));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2017 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.
@@ -12,21 +12,16 @@
* 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.cloud.gateway.filter;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.search.MeterNotFoundException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
@@ -48,8 +43,7 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import io.micrometer.core.instrument.MeterRegistry;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@@ -65,79 +59,49 @@ public class GatewayMetricFilterTests extends BaseWebClientTests {
private String testUri;
@Test
public void gatewayRequestsMeterFilterHasTags() {
testClient.get().uri("/headers")
.header(HttpHeaders.HOST, "www.metricshappypath.org").exchange()
.expectStatus().isOk().returnResult(String.class).consumeWith(result -> {
assertMetricsContainsTag("outcome",
HttpStatus.Series.SUCCESSFUL.name());
assertMetricsContainsTag("status", HttpStatus.OK.name());
assertMetricsContainsTag("routeId", "test_metrics_happy_path");
assertMetricsContainsTag("routeUri", "lb://testservice");
});
public void gatewayRequestsMeterFilterHasTags() throws InterruptedException {
testClient.get().uri("/headers").exchange().expectStatus().isOk();
assertMetricsContainsTag("outcome", HttpStatus.Series.SUCCESSFUL.name());
assertMetricsContainsTag("status", HttpStatus.OK.name());
assertMetricsContainsTag("httpStatusCode", String.valueOf(HttpStatus.OK.value()));
assertMetricsContainsTag("httpMethod", HttpMethod.GET.toString());
assertMetricsContainsTag("routeId", "default_path_to_httpbin");
assertMetricsContainsTag("routeUri", testUri);
}
@Test
public void gatewayRequestsMeterFilterHasTagsForBadTargetUri() {
testClient.get().uri("/badtargeturi").exchange().expectStatus().is5xxServerError()
.returnResult(String.class).consumeWith(result -> {
assertMetricsContainsTag("outcome",
HttpStatus.Series.SERVER_ERROR.name());
assertMetricsContainsTag("status",
HttpStatus.INTERNAL_SERVER_ERROR.name());
assertMetricsContainsTag("routeId", "default_path_to_httpbin");
assertMetricsContainsTag("routeUri", testUri);
});
public void gatewayRequestsMeterFilterHasTagsForBadTargetUri()
throws InterruptedException {
testClient.get().uri("/badtargeturi").exchange().expectStatus()
.is5xxServerError();
assertMetricsContainsTag("outcome", HttpStatus.Series.SERVER_ERROR.name());
assertMetricsContainsTag("status", HttpStatus.INTERNAL_SERVER_ERROR.name());
assertMetricsContainsTag("httpStatusCode", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
assertMetricsContainsTag("httpMethod", HttpMethod.GET.toString());
assertMetricsContainsTag("routeId", "default_path_to_httpbin");
assertMetricsContainsTag("routeUri", testUri);
}
@Test
public void hasMetricsForSetStatusFilter() {
public void hasMetricsForSetStatusFilter() throws InterruptedException {
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.HOST, "www.setcustomstatusmetrics.org");
headers.set(HttpHeaders.HOST, "www.setcustomstatus.org");
// cannot use netty client since we cannot read custom http status
ResponseEntity<String> response = new TestRestTemplate().exchange(
baseUri + "/headers", HttpMethod.GET, new HttpEntity<>(headers),
baseUri + "/headers", HttpMethod.POST, new HttpEntity<>(headers),
String.class);
assertThat(response.getStatusCodeValue()).isEqualTo(432);
assertMetricsContainsTag("outcome", "CUSTOM");
assertMetricsContainsTag("status", "432");
assertMetricsContainsTag("routeId", "test_custom_http_status_metrics");
assertMetricsContainsTag("routeId", "test_custom_http_status");
assertMetricsContainsTag("routeUri", testUri);
assertMetricsContainsTag("httpStatusCode", "432");
assertMetricsContainsTag("httpMethod", HttpMethod.POST.toString());
}
private void assertMetricsContainsTag(String tagKey, String tagValue) {
List<Meter.Id> meterIds = null;
try {
meterIds = this.meterRegistry.getMeters().stream().map(Meter::getId)
.collect(Collectors.toList());
Collection<Timer> timers = this.meterRegistry.get(REQUEST_METRICS_NAME)
.timers();
System.err.println("Looking for gateway.requests: tag: " + tagKey
+ ", value: " + tagValue);
timers.forEach(timer -> System.err
.println(timer.getId() + timer.getClass().getSimpleName()));
long count = getCount(tagKey, tagValue);
assertThat(count).isEqualTo(1);
}
catch (MeterNotFoundException e) {
System.err.println("\n\n\nError finding gatway.requests meter: tag: " + tagKey
+ ", value: " + tagValue);
System.err.println(
"\n\n\nMeter ids prior to search: " + meterIds + "\n\n\n and after:");
this.meterRegistry.forEachMeter(meter -> System.err
.println(meter.getId() + meter.getClass().getSimpleName()));
// try again?
long count = getCount(tagKey, tagValue);
if (count != 1) {
throw e;
}
}
}
private long getCount(String tagKey, String tagValue) {
return this.meterRegistry.get(REQUEST_METRICS_NAME).tag(tagKey, tagValue).timer()
.count();
assertThat(this.meterRegistry.get(REQUEST_METRICS_NAME).tag(tagKey, tagValue)
.timer().count()).isEqualTo(1);
}
@EnableAutoConfiguration
@@ -145,18 +109,14 @@ public class GatewayMetricFilterTests extends BaseWebClientTests {
@RestController
@Import(DefaultTestConfig.class)
public static class CustomConfig {
@Value("${test.uri}")
protected String testUri;
@Bean
public RouteLocator myRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("test_metrics_happy_path",
r -> r.host("*.metricshappypath.org").uri(testUri))
.route("test_custom_http_status_metrics",
r -> r.host("*.setcustomstatusmetrics.org")
.filters(f -> f.setStatus(432)).uri(testUri))
.route("test_custom_http_status", r -> r.host("*.setcustomstatus.org")
.filters(f -> f.setStatus(432)).uri(testUri))
.build();
}
@@ -164,7 +124,5 @@ public class GatewayMetricFilterTests extends BaseWebClientTests {
public String exception() {
throw new RuntimeException("an error");
}
}
}