diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 58df8bf53..961d81742 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -1072,6 +1072,10 @@ You can configure which URIs you would like to skip by setting the `spring.sleut If you have `ManagementServerProperties` on classpath, its value of `contextPath` gets appended to the provided skip pattern. If you want to reuse the Sleuth's default skip patterns and just append your own, pass those patterns by using the `spring.sleuth.web.additionalSkipPattern`. +By default, all the spring boot actuator endpoints are automatically added to the skip pattern. +If you want to disable this behaviour set `spring.sleuth.web.ignore-auto-configured-skip-patterns` +to `true`. + To change the order of tracing filter registration, please set the `spring.sleuth.web.filter-order` property. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java index 9f7242e47..7ccc2df77 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthWebProperties.java @@ -31,7 +31,7 @@ public class SleuthWebProperties { /** * Default set of skip patterns. */ - public static final String DEFAULT_SKIP_PATTERN = "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream|/application/.*|/actuator.*|/cloudfoundryapplication"; + public static final String DEFAULT_SKIP_PATTERN = "/api-docs.*|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"; /** * When true enables instrumentation for web applications. @@ -67,6 +67,12 @@ public class SleuthWebProperties { */ private boolean exceptionLoggingFilterEnabled = true; + /** + * If set to true, auto-configured skip patterns will be ignored. + * @see TraceWebAutoConfiguration + */ + private boolean ignoreAutoConfiguredSkipPatterns = false; + /** * Properties related to HTTP clients. */ @@ -125,6 +131,15 @@ public class SleuthWebProperties { this.exceptionLoggingFilterEnabled = exceptionLoggingFilterEnabled; } + public boolean isIgnoreAutoConfiguredSkipPatterns() { + return ignoreAutoConfiguredSkipPatterns; + } + + public void setIgnoreAutoConfiguredSkipPatterns( + boolean ignoreAutoConfiguredSkipPatterns) { + this.ignoreAutoConfiguredSkipPatterns = ignoreAutoConfiguredSkipPatterns; + } + public Client getClient() { return this.client; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 287d56cbd..15088cb21 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; @@ -25,6 +26,9 @@ import brave.Tracing; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; +import org.springframework.boot.actuate.endpoint.EndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -66,6 +70,7 @@ public class TraceWebAutoConfiguration { @Configuration @ConditionalOnClass(ManagementServerProperties.class) + @ConditionalOnProperty(value = "spring.sleuth.web.ignoreAutoConfiguredSkipPatterns", havingValue = "false", matchIfMissing = true) protected static class ManagementSkipPatternProviderConfig { /** @@ -93,31 +98,53 @@ public class TraceWebAutoConfiguration { } @Configuration - @ConditionalOnClass({ ServerProperties.class, WebEndpointProperties.class }) - protected static class ServerSkipPatternProviderConfig { + @ConditionalOnClass({ ServerProperties.class, EndpointsSupplier.class, + ExposableWebEndpoint.class }) + @ConditionalOnBean(ServerProperties.class) + @ConditionalOnProperty(value = "spring.sleuth.web.ignoreAutoConfiguredSkipPatterns", havingValue = "false", matchIfMissing = true) + protected static class ActuatorSkipPatternProviderConfig { + + static Optional getEndpointsPatterns(ServerProperties serverProperties, + WebEndpointProperties webEndpointProperties, + EndpointsSupplier endpointsSupplier) { + Collection endpoints = endpointsSupplier.getEndpoints(); + + if (endpoints.isEmpty()) { + return Optional.empty(); + } - /** - * Uses {@link ServerProperties#getServlet()#getContextPath()} and - * {@link WebEndpointProperties#getBasePath()} to skip Actuator endpoints. - */ - static Optional getPatternForServerProperties( - ServerProperties serverProperties, - WebEndpointProperties webEndpointProperties) { String contextPath = serverProperties.getServlet().getContextPath(); - if (StringUtils.hasText(contextPath)) { - return Optional.of(Pattern.compile( - contextPath + webEndpointProperties.getBasePath() + ".*")); + + String pattern = endpoints.stream().map(PathMappedEndpoint::getRootPath) + .map(path -> path + "|" + path + "/.*").collect( + Collectors.joining("|", + getPathPrefix(contextPath, + webEndpointProperties.getBasePath()) + "/(", + ")")); + if (StringUtils.hasText(pattern)) { + return Optional.of(Pattern.compile(pattern)); } return Optional.empty(); } + private static String getPathPrefix(String contextPath, String actuatorBasePath) { + String result = ""; + if (StringUtils.hasText(contextPath)) { + result += contextPath; + } + if (!actuatorBasePath.equals("/")) { + result += actuatorBasePath; + } + return result; + } + @Bean - @ConditionalOnBean({ ServerProperties.class, WebEndpointProperties.class }) - public SingleSkipPattern skipPatternForServerProperties( + public SingleSkipPattern skipPatternForActuatorEndpoints( final ServerProperties serverProperties, - final WebEndpointProperties webEndpointProperties) { - return () -> getPatternForServerProperties(serverProperties, - webEndpointProperties); + final WebEndpointProperties webEndpointProperties, + final EndpointsSupplier endpointsSupplier) { + return () -> getEndpointsPatterns(serverProperties, webEndpointProperties, + endpointsSupplier); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/IgnoreAutoConfiguredSkipPatternsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/IgnoreAutoConfiguredSkipPatternsIntegrationTests.java new file mode 100644 index 000000000..3555d784b --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/IgnoreAutoConfiguredSkipPatternsIntegrationTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013-2018 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 + * + * http://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.sleuth.instrument.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.sleuth.DisableSecurity; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import brave.Tracer; +import brave.sampler.Sampler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = IgnoreAutoConfiguredSkipPatternsIntegrationTests.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { + "management.endpoints.web.exposure.include:*", + "server.servlet.context-path:/context-path", + "spring.sleuth.http.legacy.enabled:true", + "spring.sleuth.web.ignoreAutoConfiguredSkipPatterns:true" }) +public class IgnoreAutoConfiguredSkipPatternsIntegrationTests { + + @Autowired + ArrayListSpanReporter accumulator; + + @Autowired + Tracer tracer; + + @LocalServerPort + int port; + + @Before + @After + public void clearSpans() { + this.accumulator.clear(); + } + + @Test + public void should_sample_actuator_endpoint_when_override_pattern_is_true() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/context-path/actuator/health", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.accumulator.getSpans()).hasSize(1); + } + + @Test + public void should_sample_non_actuator_endpoint_when_override_pattern_is_true() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/context-path/something", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.accumulator.getSpans()).hasSize(1); + } + + @Test + public void should_not_sample_default_skip_patterns_when_override_pattern_is_true() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/context-path/index.html", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.accumulator.getSpans()).hasSize(0); + } + + @EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class) + @Configuration + @DisableSecurity + @RestController + public static class Config { + + @GetMapping("something") + void doNothing() { + } + + @GetMapping("index.html") + void html() { + } + + @Bean + ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } + + @Bean + Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/issues/issue971/DemoSleuthSkipApplicationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithContextPathWithBasePath.java similarity index 86% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/issues/issue971/DemoSleuthSkipApplicationTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithContextPathWithBasePath.java index d64c3dcbc..4421490d8 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/issues/issue971/DemoSleuthSkipApplicationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithContextPathWithBasePath.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.issues.issue971; +package org.springframework.cloud.sleuth.instrument.web; import brave.Tracer; import brave.sampler.Sampler; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -37,11 +39,11 @@ import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringRunner.class) -@SpringBootTest(classes = DemoSleuthSkipApplicationTests.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { +@SpringBootTest(classes = SkipEndPointsIntegrationTestsWithContextPathWithBasePath.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { "management.endpoints.web.exposure.include:*", "server.servlet.context-path:/context-path", "spring.sleuth.http.legacy.enabled:true" }) -public class DemoSleuthSkipApplicationTests { +public class SkipEndPointsIntegrationTestsWithContextPathWithBasePath { @Autowired ArrayListSpanReporter accumulator; @@ -52,6 +54,12 @@ public class DemoSleuthSkipApplicationTests { @LocalServerPort int port; + @Before + @After + public void clearSpans() { + this.accumulator.clear(); + } + @Test public void should_not_sample_skipped_endpoint_with_context_path() { new RestTemplate().getForObject( diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithContextPathWithoutBasePath.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithContextPathWithoutBasePath.java new file mode 100644 index 000000000..18c648024 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithContextPathWithoutBasePath.java @@ -0,0 +1,118 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.sleuth.DisableSecurity; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import brave.Tracer; +import brave.sampler.Sampler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SkipEndPointsIntegrationTestsWithContextPathWithoutBasePath.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { + "management.endpoints.web.exposure.include:*", + "server.servlet.context-path:/context-path", + "spring.sleuth.http.legacy.enabled:true", + "management.endpoints.web.base-path:/" }) +public class SkipEndPointsIntegrationTestsWithContextPathWithoutBasePath { + + @Autowired + private ArrayListSpanReporter spanReporter; + + @Autowired + private Tracer tracer; + + @LocalServerPort + int port; + + @Before + @After + public void clearSpans() { + this.spanReporter.clear(); + } + + @Test + public void should_sample_non_actuator_endpoint_with_context_path() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/context-path/something", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(1); + } + + @Test + public void should_sample_non_actuator_endpoint_with_context_path_and_health_in_path() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/context-path/healthcare", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(1); + } + + @Test + public void should_not_sample_actuator_endpoint_with_base_path_set_to_root() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/context-path/health", String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(0); + } + + @Test + public void should_not_sample_actuator_endpoint_with_base_path_set_to_root_and_parameter() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/context-path/metrics?xyz", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(0); + } + + @EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class) + @Configuration + @DisableSecurity + @RestController + public static class Config { + + @GetMapping("something") + void doNothing() { + } + + @GetMapping("healthcare") + void healthCare() { + } + + @GetMapping("metrics") + void metrics() { + } + + @Bean + ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } + + @Bean + Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithoutContextPathWithBasePath.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithoutContextPathWithBasePath.java new file mode 100644 index 000000000..6f2d88b6d --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithoutContextPathWithBasePath.java @@ -0,0 +1,113 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.sleuth.DisableSecurity; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import brave.Tracer; +import brave.sampler.Sampler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SkipEndPointsIntegrationTestsWithoutContextPathWithBasePath.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { + "management.endpoints.web.exposure.include:*", + "spring.sleuth.http.legacy.enabled:true" }) +public class SkipEndPointsIntegrationTestsWithoutContextPathWithBasePath { + + @Autowired + private ArrayListSpanReporter spanReporter; + + @Autowired + private Tracer tracer; + + @LocalServerPort + int port; + + @Before + @After + public void clearSpans() { + this.spanReporter.clear(); + } + + @Test + public void should_sample_non_actuator_endpoint() { + new RestTemplate().getForObject("http://localhost:" + this.port + "/something", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(1); + } + + @Test + public void should_sample_non_actuator_endpoint_and_healthcare_in_path() { + new RestTemplate().getForObject("http://localhost:" + this.port + "/healthcare", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(1); + } + + @Test + public void should_not_sample_actuator_endpoint() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/actuator/health", String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(0); + } + + @Test + public void should_not_sample_actuator_endpoint_with_parameter() { + new RestTemplate().getForObject( + "http://localhost:" + this.port + "/actuator/metrics?xyz", String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(0); + } + + @EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class) + @Configuration + @DisableSecurity + @RestController + public static class Config { + + @GetMapping("something") + void doNothing() { + } + + @GetMapping("healthcare") + void healthCare() { + } + + @GetMapping("metrics") + void metrics() { + } + + @Bean + ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } + + @Bean + Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithoutContextPathWithoutBasePath.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithoutContextPathWithoutBasePath.java new file mode 100644 index 000000000..9c74496f1 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipEndPointsIntegrationTestsWithoutContextPathWithoutBasePath.java @@ -0,0 +1,114 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.sleuth.DisableSecurity; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import brave.Tracer; +import brave.sampler.Sampler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SkipEndPointsIntegrationTestsWithoutContextPathWithoutBasePath.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { + "management.endpoints.web.exposure.include:*", + "spring.sleuth.http.legacy.enabled:true", + "management.endpoints.web.base-path:/" }) +public class SkipEndPointsIntegrationTestsWithoutContextPathWithoutBasePath { + + @Autowired + private ArrayListSpanReporter spanReporter; + + @Autowired + private Tracer tracer; + + @LocalServerPort + int port; + + @Before + @After + public void clearSpans() { + this.spanReporter.clear(); + } + + @Test + public void should_sample_non_actuator_endpoint() { + new RestTemplate().getForObject("http://localhost:" + this.port + "/something", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(1); + } + + @Test + public void should_sample_non_actuator_endpoint_with_healthcare_in_path() { + new RestTemplate().getForObject("http://localhost:" + this.port + "/healthcare", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(1); + } + + @Test + public void should_not_sample_actuator_endpoint() { + new RestTemplate().getForObject("http://localhost:" + this.port + "/health", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(0); + } + + @Test + public void should_not_sample_actuator_endpoint_with_parameter() { + new RestTemplate().getForObject("http://localhost:" + this.port + "/metrics?xyz", + String.class); + + then(this.tracer.currentSpan()).isNull(); + then(this.spanReporter.getSpans()).hasSize(0); + } + + @EnableAutoConfiguration(exclude = RabbitAutoConfiguration.class) + @Configuration + @DisableSecurity + @RestController + public static class Config { + + @GetMapping("something") + void doNothing() { + } + + @GetMapping("healthcare") + void healthCare() { + } + + @GetMapping("metrics") + void metrics() { + } + + @Bean + ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } + + @Bean + Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java index 2ad74e028..bd5e3466d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -17,6 +17,8 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Optional; import java.util.regex.Pattern; @@ -24,6 +26,10 @@ import org.junit.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.EndpointsSupplier; +import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; +import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.autoconfigure.web.ServerProperties; import static org.assertj.core.api.BDDAssertions.then; @@ -79,27 +85,103 @@ public class SkipPatternProviderConfigTest { } @Test - public void should_return_empty_when_server_props_have_no_context_path() - throws Exception { - Optional pattern = new TraceWebAutoConfiguration.ServerSkipPatternProviderConfig() - .skipPatternForServerProperties(new ServerProperties(), - new WebEndpointProperties()) + public void should_return_empty_when_no_endpoints() { + EndpointsSupplier endpointsSupplier = Collections::emptyList; + Optional pattern = new TraceWebAutoConfiguration.ActuatorSkipPatternProviderConfig() + .skipPatternForActuatorEndpoints(new ServerProperties(), + new WebEndpointProperties(), endpointsSupplier) .skipPattern(); then(pattern).isEmpty(); } @Test - public void should_return_server_props_with_context_path() throws Exception { + public void should_return_endpoints_without_context_path() { ServerProperties properties = new ServerProperties(); - properties.getServlet().setContextPath("foo"); + WebEndpointProperties webEndpointProperties = new WebEndpointProperties(); + EndpointsSupplier endpointsSupplier = () -> { + ExposableWebEndpoint infoEndpoint = createEndpoint("info"); + ExposableWebEndpoint healthEndpoint = createEndpoint("health"); - Optional pattern = new TraceWebAutoConfiguration.ServerSkipPatternProviderConfig() - .skipPatternForServerProperties(properties, new WebEndpointProperties()) + return Arrays.asList(infoEndpoint, healthEndpoint); + }; + + Optional pattern = new TraceWebAutoConfiguration.ActuatorSkipPatternProviderConfig() + .skipPatternForActuatorEndpoints(properties, webEndpointProperties, + endpointsSupplier) .skipPattern(); then(pattern).isNotEmpty(); - then(pattern.get().pattern()).isEqualTo("foo/actuator.*"); + then(pattern.get().pattern()) + .isEqualTo("/actuator/(info|info/.*|health|health/.*)"); + } + + @Test + public void should_return_endpoints_with_context_path() { + WebEndpointProperties webEndpointProperties = new WebEndpointProperties(); + ServerProperties properties = new ServerProperties(); + properties.getServlet().setContextPath("foo"); + + EndpointsSupplier endpointsSupplier = () -> { + ExposableWebEndpoint infoEndpoint = createEndpoint("info"); + ExposableWebEndpoint healthEndpoint = createEndpoint("health"); + + return Arrays.asList(infoEndpoint, healthEndpoint); + }; + + Optional pattern = new TraceWebAutoConfiguration.ActuatorSkipPatternProviderConfig() + .skipPatternForActuatorEndpoints(properties, webEndpointProperties, + endpointsSupplier) + .skipPattern(); + + then(pattern).isNotEmpty(); + then(pattern.get().pattern()) + .isEqualTo("foo/actuator/(info|info/.*|health|health/.*)"); + } + + @Test + public void should_return_endpoints_without_context_path_and_base_path_set_to_root() { + ServerProperties properties = new ServerProperties(); + WebEndpointProperties webEndpointProperties = new WebEndpointProperties(); + webEndpointProperties.setBasePath("/"); + + EndpointsSupplier endpointsSupplier = () -> { + ExposableWebEndpoint infoEndpoint = createEndpoint("info"); + ExposableWebEndpoint healthEndpoint = createEndpoint("health"); + + return Arrays.asList(infoEndpoint, healthEndpoint); + }; + + Optional pattern = new TraceWebAutoConfiguration.ActuatorSkipPatternProviderConfig() + .skipPatternForActuatorEndpoints(properties, webEndpointProperties, + endpointsSupplier) + .skipPattern(); + + then(pattern).isNotEmpty(); + then(pattern.get().pattern()).isEqualTo("/(info|info/.*|health|health/.*)"); + } + + @Test + public void should_return_endpoints_with_context_path_and_base_path_set_to_root() { + WebEndpointProperties webEndpointProperties = new WebEndpointProperties(); + webEndpointProperties.setBasePath("/"); + ServerProperties properties = new ServerProperties(); + properties.getServlet().setContextPath("foo"); + + EndpointsSupplier endpointsSupplier = () -> { + ExposableWebEndpoint infoEndpoint = createEndpoint("info"); + ExposableWebEndpoint healthEndpoint = createEndpoint("health"); + + return Arrays.asList(infoEndpoint, healthEndpoint); + }; + + Optional pattern = new TraceWebAutoConfiguration.ActuatorSkipPatternProviderConfig() + .skipPatternForActuatorEndpoints(properties, webEndpointProperties, + endpointsSupplier) + .skipPattern(); + + then(pattern).isNotEmpty(); + then(pattern.get().pattern()).isEqualTo("foo/(info|info/.*|health|health/.*)"); } @Test @@ -120,4 +202,29 @@ public class SkipPatternProviderConfigTest { return () -> Optional.of(Pattern.compile("bar")); } + private ExposableWebEndpoint createEndpoint(final String name) { + return new ExposableWebEndpoint() { + + @Override + public String getRootPath() { + return name; + } + + @Override + public EndpointId getEndpointId() { + return EndpointId.of(name); + } + + @Override + public boolean isEnableByDefault() { + return false; + } + + @Override + public Collection getOperations() { + return null; + } + }; + } + } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java index 1fb0336f3..211f002e0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRequestHttpHeadersFilterTests.java @@ -48,8 +48,7 @@ public class TraceRequestHttpHeadersFilterTests { .isEqualTo(Collections.singletonList("World")); BDDAssertions.then(filteredHeaders.get("X-Hello-Request")) .isEqualTo(Collections.singletonList("Request World")); - BDDAssertions.then(filteredHeaders.get("X-Auth-User")) - .hasSize(1); + BDDAssertions.then(filteredHeaders.get("X-Auth-User")).hasSize(1); BDDAssertions .then((Object) exchange .getAttribute(TraceRequestHttpHeadersFilter.SPAN_ATTRIBUTE))