From 0313117900abb52d4fe2b868593fe1c188f63679 Mon Sep 17 00:00:00 2001 From: dlsrb6342 Date: Sat, 15 Jun 2019 00:36:49 +0900 Subject: [PATCH] Fixes gh-1110 Add GatewayTagsProvider interface --- .../GatewayMetricsAutoConfiguration.java | 14 ++- .../gateway/filter/GatewayMetricsFilter.java | 50 ++------ .../DefaultGatewayTagsProvider.java | 33 ++++++ .../support/tagsprovider/GatewayTags.java | 85 ++++++++++++++ .../tagsprovider/GatewayTagsProvider.java | 30 +++++ .../GatewayMetricsAutoConfigurationTests.java | 111 ++++++++++++++++++ .../DefaultGatewayTagsProviderTests.java | 99 ++++++++++++++++ 7 files changed, 378 insertions(+), 44 deletions(-) create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProvider.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTags.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java create mode 100644 spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java create mode 100644 spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProviderTests.java diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java index af6e9b71..2d67a8cd 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java @@ -24,9 +24,12 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.cloud.gateway.filter.GatewayMetricsFilter; +import org.springframework.cloud.gateway.support.tagsprovider.DefaultGatewayTagsProvider; +import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.DispatcherHandler; @@ -40,11 +43,18 @@ import org.springframework.web.reactive.DispatcherHandler; MetricsAutoConfiguration.class }) public class GatewayMetricsAutoConfiguration { + @Bean + @ConditionalOnMissingBean(GatewayTagsProvider.class) + public GatewayTagsProvider gatewayTagsProvider() { + return new DefaultGatewayTagsProvider(); + } + @Bean @ConditionalOnBean(MeterRegistry.class) @ConditionalOnProperty(name = "spring.cloud.gateway.metrics.enabled", matchIfMissing = true) - public GatewayMetricsFilter gatewayMetricFilter(MeterRegistry meterRegistry) { - return new GatewayMetricsFilter(meterRegistry); + public GatewayMetricsFilter gatewayMetricFilter(MeterRegistry meterRegistry, + GatewayTagsProvider tagsProvider) { + return new GatewayMetricsFilter(meterRegistry, tagsProvider); } } 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 a7ca7215..b064615a 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 @@ -24,17 +24,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; -import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider; import org.springframework.core.Ordered; -import org.springframework.http.HttpStatus; -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; - /** * @author Tony Clarke + * @author Ingyu Hwang */ public class GatewayMetricsFilter implements GlobalFilter, Ordered { @@ -42,8 +39,12 @@ public class GatewayMetricsFilter implements GlobalFilter, Ordered { private MeterRegistry meterRegistry; - public GatewayMetricsFilter(MeterRegistry meterRegistry) { + private GatewayTagsProvider tagsProvider; + + public GatewayMetricsFilter(MeterRegistry meterRegistry, + GatewayTagsProvider tagsProvider) { this.meterRegistry = meterRegistry; + this.tagsProvider = tagsProvider; } @Override @@ -77,43 +78,8 @@ 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(); - - // a non standard HTTPS status could be used. Let's be defensive here - // it needs to be checked for first, otherwise the delegate response - // who's status DIDN"T change, will be used - if (exchange.getResponse() instanceof AbstractServerHttpResponse) { - Integer statusInt = ((AbstractServerHttpResponse) exchange.getResponse()) - .getStatusCodeValue(); - if (statusInt != null) { - status = String.valueOf(statusInt); - httpStatusCodeStr = status; - HttpStatus resolved = HttpStatus.resolve(statusInt); - if (resolved != null) { - // this is not a CUSTOM status, so use series here. - outcome = resolved.series().name(); - status = resolved.name(); - } - } - } - else { - HttpStatus statusCode = exchange.getResponse().getStatusCode(); - if (statusCode != null) { - httpStatusCodeStr = String.valueOf(statusCode.value()); - outcome = statusCode.series().name(); - status = statusCode.name(); - } - } - - // TODO refactor to allow Tags provider like in MetricsWebFilter - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - Tags tags = Tags.of("outcome", outcome, "status", status, "httpStatusCode", - httpStatusCodeStr, "routeId", route.getId(), "routeUri", - route.getUri().toString(), "httpMethod", httpMethod); + Tags tags = tagsProvider.tags(exchange); if (log.isTraceEnabled()) { log.trace("gateway.requests tags: " + tags); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProvider.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProvider.java new file mode 100644 index 00000000..8c347f3c --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2019 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 + * + * https://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.cloud.gateway.support.tagsprovider; + +import io.micrometer.core.instrument.Tags; + +import org.springframework.web.server.ServerWebExchange; + +/** + * @author Ingyu Hwang + */ +public class DefaultGatewayTagsProvider implements GatewayTagsProvider { + + @Override + public Tags tags(ServerWebExchange exchange) { + return Tags.concat(GatewayTags.http(exchange), GatewayTags.route(exchange)); + } + +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTags.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTags.java new file mode 100644 index 00000000..516647e7 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTags.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-2019 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 + * + * https://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.cloud.gateway.support.tagsprovider; + +import io.micrometer.core.instrument.Tags; + +import org.springframework.cloud.gateway.route.Route; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.AbstractServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; + +/** + * @author Ingyu Hwang + */ +public final class GatewayTags { + + private GatewayTags() { + throw new AssertionError("Must not instantiate utility class."); + } + + public static Tags http(ServerWebExchange exchange) { + String outcome = "CUSTOM"; + String status = "CUSTOM"; + String httpStatusCodeStr = "NA"; + + String httpMethod = exchange.getRequest().getMethodValue(); + + // a non standard HTTPS status could be used. Let's be defensive here + // it needs to be checked for first, otherwise the delegate response + // who's status DIDN'T change, will be used + if (exchange.getResponse() instanceof AbstractServerHttpResponse) { + Integer statusInt = ((AbstractServerHttpResponse) exchange.getResponse()) + .getStatusCodeValue(); + if (statusInt != null) { + status = String.valueOf(statusInt); + httpStatusCodeStr = status; + HttpStatus resolved = HttpStatus.resolve(statusInt); + if (resolved != null) { + // this is not a CUSTOM status, so use series here. + outcome = resolved.series().name(); + status = resolved.name(); + } + } + } + else { + HttpStatus statusCode = exchange.getResponse().getStatusCode(); + if (statusCode != null) { + httpStatusCodeStr = String.valueOf(statusCode.value()); + outcome = statusCode.series().name(); + status = statusCode.name(); + } + } + + return Tags.of("outcome", outcome, "status", status, + "httpStatusCode", httpStatusCodeStr, "httpMethod", httpMethod); + } + + public static Tags route(ServerWebExchange exchange) { + Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); + + if (route != null) { + return Tags.of("routeId", route.getId(), + "routeUri", route.getUri().toString()); + } + + return Tags.empty(); + } + +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java new file mode 100644 index 00000000..4afc0e10 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2019 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 + * + * https://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.cloud.gateway.support.tagsprovider; + +import io.micrometer.core.instrument.Tags; + +import org.springframework.web.server.ServerWebExchange; + +/** + * @author Ingyu Hwang + */ +public interface GatewayTagsProvider { + + Tags tags(ServerWebExchange exchange); + +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java new file mode 100644 index 00000000..5b7026c8 --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2013-2019 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 + * + * https://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.cloud.gateway.config; + +import io.micrometer.core.instrument.Tags; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.filter.GatewayMetricsFilter; +import org.springframework.cloud.gateway.support.tagsprovider.DefaultGatewayTagsProvider; +import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ingyu Hwang + */ +@RunWith(Enclosed.class) +public class GatewayMetricsAutoConfigurationTests { + + @RunWith(SpringRunner.class) + @SpringBootTest(classes = Config.class) + public static class EnabledByDefault { + + @Autowired(required = false) + private GatewayMetricsFilter filter; + + @Autowired(required = false) + private GatewayTagsProvider tagsProvider; + + @Test + public void gatewayMetricsBeansExists() { + assertThat(filter).isNotNull(); + assertThat(tagsProvider).isInstanceOf(DefaultGatewayTagsProvider.class); + } + + } + + @RunWith(SpringRunner.class) + @SpringBootTest(classes = Config.class, properties = "spring.cloud.gateway.metrics.enabled=false") + public static class DisabledByProperty { + + @Autowired(required = false) + private GatewayMetricsFilter filter; + + @Test + public void gatewayMetricsBeanMissing() { + assertThat(filter).isNull(); + } + + } + + @RunWith(SpringRunner.class) + @SpringBootTest(classes = CustomTagsProviderConfig.class) + public static class EnabledCustomTagsProvider { + + @Autowired(required = false) + private GatewayMetricsFilter filter; + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + @Autowired(required = false) + private GatewayTagsProvider tagsProvider; + + @Test + public void gatewayMetricsBeansExists() { + assertThat(filter).isNotNull(); + assertThat(tagsProvider).isNotInstanceOf(DefaultGatewayTagsProvider.class) + .isInstanceOf(GatewayTagsProvider.class); + } + + } + + @SpringBootConfiguration + @EnableAutoConfiguration + protected static class Config { + + } + + @SpringBootConfiguration + @EnableAutoConfiguration + protected static class CustomTagsProviderConfig { + + @Bean + public GatewayTagsProvider emptyTagsProvider() { + return (exchange) -> Tags.empty(); + } + + } + +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProviderTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProviderTests.java new file mode 100644 index 00000000..9db348e4 --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/DefaultGatewayTagsProviderTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013-2019 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 + * + * https://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.cloud.gateway.support.tagsprovider; + +import io.micrometer.core.instrument.Tags; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.cloud.gateway.route.Route; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpResponse; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; + +/** + * @author Ingyu Hwang + */ +public class DefaultGatewayTagsProviderTests { + + private Route route; + + private DefaultGatewayTagsProvider defaultTagsProvider; + + private Tags defaultTags; + + private static final String ROUTE_URI = "http://gatewaytagsprovider.org:80"; + + @Before + public void setup() { + String routeId = "test-route"; + HttpStatus ok = HttpStatus.OK; + + route = Route.async().id(routeId).uri(ROUTE_URI).predicate(swe -> true).build(); + + defaultTagsProvider = new DefaultGatewayTagsProvider(); + defaultTags = Tags.of("outcome", ok.series().name(), "status", ok.name(), + "httpStatusCode", String.valueOf(ok.value()), "httpMethod", "GET", + "routeId", routeId, "routeUri", ROUTE_URI); + } + + @Test + public void defaultProvider() { + ServerWebExchange exchange = MockServerWebExchange + .from(MockServerHttpRequest.get(ROUTE_URI).build()); + exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, route); + exchange.getResponse().setStatusCode(HttpStatus.OK); + + Tags tags = defaultTagsProvider.tags(exchange); + assertThat(tags).isEqualTo(defaultTags); + } + + @Test + public void statusNotChanged() { + ServerWebExchange exchange = MockServerWebExchange + .from(MockServerHttpRequest.get(ROUTE_URI).build()); + + Tags tags = defaultTagsProvider.tags(exchange); + assertThat(tags).isEqualTo(Tags.of("outcome", "CUSTOM", "status", "CUSTOM", + "httpStatusCode", "NA", "httpMethod", "GET")); + } + + @Test + public void notAbstractServerHttpResponse() { + ServerWebExchange mockExchange = mock(ServerWebExchange.class); + ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator( + new MockServerHttpResponse()); + responseDecorator.setStatusCode(HttpStatus.OK); + + when(mockExchange.getRequest()) + .thenReturn(MockServerHttpRequest.get(ROUTE_URI).build()); + when(mockExchange.getResponse()).thenReturn(responseDecorator); + when(mockExchange.getAttribute(GATEWAY_ROUTE_ATTR)).thenReturn(route); + + Tags tags = defaultTagsProvider.tags(mockExchange); + assertThat(tags).isEqualTo(defaultTags); + } + +}