diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 31615a79..07d9829a 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -960,6 +960,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]. diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java index 11ac4981..65cbee4e 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java @@ -73,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(); } @@ -84,15 +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()); + 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)); } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricFilterTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricFilterTests.java index 46a957c9..78bbf3bf 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricFilterTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricFilterTests.java @@ -63,8 +63,10 @@ public class GatewayMetricFilterTests extends BaseWebClientTests { 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", "lb://testservice"); + assertMetricsContainsTag("routeUri", testUri); } @Test @@ -74,6 +76,8 @@ public class GatewayMetricFilterTests extends BaseWebClientTests { .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); } @@ -84,13 +88,15 @@ public class GatewayMetricFilterTests extends BaseWebClientTests { headers.set(HttpHeaders.HOST, "www.setcustomstatus.org"); // cannot use netty client since we cannot read custom http status ResponseEntity 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"); assertMetricsContainsTag("routeUri", testUri); + assertMetricsContainsTag("httpStatusCode", "432"); + assertMetricsContainsTag("httpMethod", HttpMethod.POST.toString()); } private void assertMetricsContainsTag(String tagKey, String tagValue) {