Fixes gh-1110 Add GatewayTagsProvider interface
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user