Fixes gh-1110 Add GatewayTagsProvider interface

This commit is contained in:
dlsrb6342
2019-06-15 00:36:49 +09:00
parent e632221d28
commit 0313117900
7 changed files with 378 additions and 44 deletions

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}