diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 4dcae0960..c7464c99d 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -330,7 +330,8 @@ Features from this section can be disabled by providing the `spring.sleuth.web.e Via the `TraceFilter` all sampled incoming requests result in creation of a Span. That Span's name is `http:` + the path to which the request was sent. E.g. if the request was sent to `/foo/bar` then the name will be `http:/foo/bar`. You can configure which URIs you would - like to skip via the `spring.sleuth.instrument.web.skipPattern` property. + like to skip via the `spring.sleuth.instrument.web.skipPattern` property. If you have `ManagementServerProperties` on classpath then + its value of `contextPath` gets appended to the provided skip pattern. ==== Async Servlet support diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index d57bd3af2..c036396f1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -74,8 +74,8 @@ public class TraceFilter extends OncePerRequestFilter protected static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName() + ".TRACE"; - public static final Pattern DEFAULT_SKIP_PATTERN = Pattern.compile( - "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"); + 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"; private final Tracer tracer; private final TraceKeys traceKeys; @@ -86,7 +86,7 @@ public class TraceFilter extends OncePerRequestFilter private ApplicationEventPublisher publisher; public TraceFilter(Tracer tracer, TraceKeys traceKeys) { - this(tracer, traceKeys, DEFAULT_SKIP_PATTERN, new Random()); + this(tracer, traceKeys, Pattern.compile(DEFAULT_SKIP_PATTERN), new Random()); } public TraceFilter(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern, @@ -105,7 +105,7 @@ public class TraceFilter extends OncePerRequestFilter @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + throws ServletException, IOException { String uri = this.urlPathHelper.getPathWithinApplication(request); boolean skip = this.skipPattern.matcher(uri).matches() 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 c4cff5fbb..2c448fa67 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 @@ -20,17 +20,20 @@ import java.util.regex.Pattern; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.SpanAccessor; import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; -import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -77,11 +80,82 @@ public class TraceWebAutoConfiguration { @Bean @ConditionalOnMissingBean - public TraceFilter traceFilter(ApplicationEventPublisher publisher, Random random) { - Pattern pattern = StringUtils.hasText(this.skipPattern) ? Pattern.compile(this.skipPattern) - : TraceFilter.DEFAULT_SKIP_PATTERN; - TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, pattern, random); + public TraceFilter traceFilter(ApplicationEventPublisher publisher, Random random, SkipPatternProvider skipPatternProvider) { + TraceFilter filter = new TraceFilter(this.tracer, this.traceKeys, skipPatternProvider.skipPattern(), random); filter.setApplicationEventPublisher(publisher); return filter; } + + @Configuration + @ConditionalOnClass(ManagementServerProperties.class) + @ConditionalOnMissingBean(SkipPatternProvider.class) + protected static class SkipPatternProviderConfig { + + /** + * Pattern for URLs that should be skipped in tracing + */ + @Value("${spring.sleuth.instrument.web.skipPattern:}") + private String skipPattern; + + @Bean + @ConditionalOnBean(ManagementServerProperties.class) + public SkipPatternProvider skipPatternForManagementServerProperties( + final ManagementServerProperties managementServerProperties) { + return new SkipPatternProvider() { + @Override + public Pattern skipPattern() { + return getPatternForManagementServerProperties( + managementServerProperties, SkipPatternProviderConfig.this.skipPattern); + } + }; + } + + /** + * Sets or appends {@link ManagementServerProperties#getContextPath()} to the + * skip pattern. If neither is available then sets the default one + */ + static Pattern getPatternForManagementServerProperties( + ManagementServerProperties managementServerProperties, String skipPattern) { + if (StringUtils.hasText(skipPattern) && + StringUtils.hasText(managementServerProperties.getContextPath())) { + return Pattern.compile(skipPattern + "|" + + managementServerProperties.getContextPath() + ".*"); + } else if (StringUtils.hasText(managementServerProperties.getContextPath())) { + return Pattern.compile(managementServerProperties.getContextPath() + ".*"); + } + return defaultSkipPattern(skipPattern); + } + + @Bean + @ConditionalOnMissingBean(ManagementServerProperties.class) + public SkipPatternProvider defaultSkipPatternBeanIfManagementServerPropsArePresent() { + return defaultSkipPatternProvider(this.skipPattern); + } + } + + @Bean + @ConditionalOnMissingClass("org.springframework.boot.actuate.autoconfigure.ManagementServerProperties") + @ConditionalOnMissingBean(SkipPatternProvider.class) + public SkipPatternProvider defaultSkipPatternBean() { + return defaultSkipPatternProvider(this.skipPattern); + } + + private static SkipPatternProvider defaultSkipPatternProvider(final String skipPattern) { + return new SkipPatternProvider() { + @Override + public Pattern skipPattern() { + return defaultSkipPattern(skipPattern); + } + }; + } + + private static Pattern defaultSkipPattern(String skipPattern) { + return StringUtils.hasText(skipPattern) ? + Pattern.compile(skipPattern) + : Pattern.compile(TraceFilter.DEFAULT_SKIP_PATTERN); + } + + interface SkipPatternProvider { + Pattern skipPattern(); + } } 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 new file mode 100644 index 000000000..104030042 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013-2016 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 java.util.regex.Pattern; + +import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; +import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration.SkipPatternProviderConfig; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +public class SkipPatternProviderConfigTest { + + @Test + public void should_combine_skip_pattern_and_management_context_when_they_are_both_not_empty() throws Exception { + String skipPattern = "foo.*|bar.*"; + + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties( + managementServerPropertiesWithContextPath(), skipPattern); + + then(pattern.pattern()).isEqualTo("foo.*|bar.*|/management/context.*"); + } + + @Test + public void should_pick_skip_pattern_when_its_not_empty_and_management_context_is_empty() throws Exception { + String skipPattern = "foo.*|bar.*"; + + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), skipPattern); + + then(pattern.pattern()).isEqualTo("foo.*|bar.*"); + } + + @Test + public void should_pick_management_context_when_skip_patterns_is_empty_and_context_path_is_not() throws Exception { + String skipPattern = ""; + + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties( + managementServerPropertiesWithContextPath(), skipPattern); + + then(pattern.pattern()).isEqualTo("/management/context.*"); + } + + @Test + public void should_pick_default_pattern_when_both_management_context_and_skip_patterns_are_empty() throws Exception { + String skipPattern = ""; + + Pattern pattern = SkipPatternProviderConfig.getPatternForManagementServerProperties(new ManagementServerProperties(), skipPattern); + + then(pattern.pattern()).isEqualTo(TraceFilter.DEFAULT_SKIP_PATTERN); + } + + private ManagementServerProperties managementServerPropertiesWithContextPath() { + ManagementServerProperties managementServerProperties = new ManagementServerProperties(); + managementServerProperties.setContextPath("/management/context"); + return managementServerProperties; + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index 662661f09..7fd964dc1 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -1,9 +1,5 @@ package org.springframework.cloud.sleuth.instrument.web; -import static org.assertj.core.api.BDDAssertions.then; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import java.util.Random; import java.util.concurrent.CompletableFuture; @@ -12,12 +8,15 @@ import org.apache.commons.logging.LogFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; -import org.springframework.cloud.sleuth.TraceKeys; import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MvcResult; @@ -26,6 +25,10 @@ import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(TraceFilterIntegrationTests.class) @DefaultTestAutoConfiguration @@ -34,10 +37,9 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { private static Log logger = LogFactory.getLog(TraceFilterIntegrationTests.class); - @Autowired - Tracer tracer; - @Autowired - TraceKeys traceKeys; + @Autowired Tracer tracer; + @Autowired TraceKeys traceKeys; + @Autowired TraceFilter traceFilter; static Span span; @@ -61,6 +63,13 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { then(tracingHeaderFrom(mvcResult)).isNotNull(); } + @Test + public void should_ignore_sampling_the_span_if_uri_matches_management_properties_context_path() throws Exception { + MvcResult mvcResult = whenSentInfoWithTraceId(new Random().nextLong()); + + then(notSampledHeaderIsPresent(mvcResult)).isEqualTo(true); + } + @Test public void when_correlationId_is_sent_should_not_create_a_new_one_but_return_the_existing_one_instead() throws Exception { @@ -85,7 +94,7 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { @Override protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) { - mockMvcBuilder.addFilters(new TraceFilter(this.tracer, this.traceKeys)); + mockMvcBuilder.addFilters(traceFilter); } private MvcResult whenSentPingWithoutTracingData() throws Exception { @@ -98,6 +107,10 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { return sendPingWithTraceId(Span.TRACE_ID_NAME, passedTraceId); } + private MvcResult whenSentInfoWithTraceId(Long passedTraceId) throws Exception { + return sendPingWithTraceId("/additionalContextPath/info", Span.TRACE_ID_NAME, passedTraceId); + } + private MvcResult whenSentFutureWithTraceId(Long passedTraceId) throws Exception { return sendPingWithTraceId("/future", Span.TRACE_ID_NAME, passedTraceId); } @@ -119,4 +132,18 @@ public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { private Long tracingHeaderFrom(MvcResult mvcResult) { return Span.hexToId(mvcResult.getResponse().getHeader(Span.TRACE_ID_NAME)); } + + private boolean notSampledHeaderIsPresent(MvcResult mvcResult) { + return mvcResult.getResponse().containsHeader(Span.NOT_SAMPLED_NAME); + } + + @Configuration + static class Config { + @Bean + ManagementServerProperties managementServerProperties() { + ManagementServerProperties managementServerProperties = new ManagementServerProperties(); + managementServerProperties.setContextPath("/additionalContextPath"); + return managementServerProperties; + } + } }