From 368eabb41204f220e3f566f83ea35aa43f496e40 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 27 Aug 2020 16:32:53 +0200 Subject: [PATCH] Added spanHandler that skips span name via patterns; fixes gh-1720 --- .../main/asciidoc/spring-cloud-sleuth.adoc | 14 +- .../sleuth/autoconfig/SleuthProperties.java | 67 +++++++++ .../autoconfig/SpanIgnoringSpanHandler.java | 83 ++++++++++++ .../autoconfig/TraceAutoConfiguration.java | 6 + .../SpanIgnoringSpanHandlerTests.java | 127 ++++++++++++++++++ 5 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandler.java create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandlerTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index d7b6748b0..903c83ef3 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -381,12 +381,12 @@ anything not already done by Sleuth with auto-configuration or properties. If you define one of the following as a `Bean`, Sleuth will invoke it to customize behaviour: -* RpcTracingCustomizer - for RPC tagging and sampling policy -* HttpTracingCustomizer - for HTTP tagging and sampling policy -* MessagingTracingCustomizer - for messaging tagging and sampling policy -* CurrentTraceContextCustomizer - to integrate decorators such as correlation. -* BaggagePropagationCustomizer - for propagating baggage fields in process and over headers -* CorrelationScopeDecoratorCustomizer - for scope decorations such as MDC (logging) field correlation +* `RpcTracingCustomizer` - for RPC tagging and sampling policy +* `HttpTracingCustomizer` - for HTTP tagging and sampling policy +* `MessagingTracingCustomizer` - for messaging tagging and sampling policy +* `CurrentTraceContextCustomizer` - to integrate decorators such as correlation. +* `BaggagePropagationCustomize`r - for propagating baggage fields in process and over headers +* `CorrelationScopeDecoratorCustomizer` - for scope decorations such as MDC (logging) field correlation === HTTP @@ -540,6 +540,8 @@ include::{project-root}//spring-cloud-sleuth-core/src/test/java/org/springframew The preceding example results in changing the name of the reported span to `foo bar`, just before it gets reported (for example, to Zipkin). +Sleuth registers a `SpanHandler` bean that can automatically skip reporting spans of given name patterns. The property `spring.sleuth.span-handler.span-name-patterns-to-skip` contains the default skip patterns for span names. The property `spring.sleuth.span-handler.additional-span-name-patterns-to-skip` will append the provided span name patterns to the existing ones. In order to disable this functionality just set `spring.sleuth.span-handler.enabled` to `false`. + === Host Locator IMPORTANT: This section is about defining *host* from service discovery. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java index fc472bc2e..b9b1dc3f5 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java @@ -16,6 +16,10 @@ package org.springframework.cloud.sleuth.autoconfig; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -38,6 +42,11 @@ class SleuthProperties { */ private boolean supportsJoin = true; + /** + * Properties related to handling of spans. + */ + private SpanHandler spanHandler = new SpanHandler(); + public boolean isEnabled() { return this.enabled; } @@ -62,4 +71,62 @@ class SleuthProperties { this.supportsJoin = supportsJoin; } + public SpanHandler getSpanHandler() { + return this.spanHandler; + } + + public void setSpanHandler(SpanHandler spanHandler) { + this.spanHandler = spanHandler; + } + + /** + * Properties related to handling of spans. + */ + public static class SpanHandler { + + /** + * Will turn on the default Sleuth handler mechanism. Might ignore exporting of + * certain spans; + */ + private boolean enabled; + + /** + * List of span names to ignore. They will not be sent to external systems. + */ + private List spanNamePatternsToSkip = Arrays + .asList("^catalogWatchTaskScheduler$"); + + /** + * Additional list of span names to ignore. Will be appended to + * {@link #spanNamePatternsToSkip}. + */ + private List additionalSpanNamePatternsToIgnore = Collections.emptyList(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List getSpanNamePatternsToSkip() { + return this.spanNamePatternsToSkip; + } + + public void setSpanNamePatternsToSkip(List spanNamePatternsToSkip) { + this.spanNamePatternsToSkip = spanNamePatternsToSkip; + } + + public List getAdditionalSpanNamePatternsToIgnore() { + return this.additionalSpanNamePatternsToIgnore; + } + + public void setAdditionalSpanNamePatternsToIgnore( + List additionalSpanNamePatternsToIgnore) { + this.additionalSpanNamePatternsToIgnore = additionalSpanNamePatternsToIgnore; + } + + } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandler.java new file mode 100644 index 000000000..b1e272e6a --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandler.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013-2020 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.sleuth.autoconfig; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import brave.propagation.TraceContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.StringUtils; + +/** + * {@link SpanHandler} that ignores spans via names. + * + * @author Marcin Grzejszczak + * @since 3.0.0 + */ +class SpanIgnoringSpanHandler extends SpanHandler { + + private static final Log log = LogFactory.getLog(SpanIgnoringSpanHandler.class); + + private final SleuthProperties sleuthProperties; + + static final Map cache = new ConcurrentHashMap<>(); + + SpanIgnoringSpanHandler(SleuthProperties sleuthProperties) { + this.sleuthProperties = sleuthProperties; + } + + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + if (cause != Cause.FINISHED) { + return true; + } + List spanNamesToIgnore = spanNamesToIgnore(); + String name = span.name(); + if (StringUtils.hasText(name) + && spanNamesToIgnore.stream().anyMatch(p -> p.matcher(name).matches())) { + if (log.isDebugEnabled()) { + log.debug("Will ignore a span with name [" + name + "]"); + } + return false; + } + return super.end(context, span, cause); + } + + private List spanNamesToIgnore() { + return spanNames().stream() + .map(regex -> cache.computeIfAbsent(regex, Pattern::compile)) + .collect(Collectors.toList()); + } + + private List spanNames() { + List spanNamesToIgnore = new ArrayList<>( + this.sleuthProperties.getSpanHandler().getSpanNamePatternsToSkip()); + spanNamesToIgnore.addAll(this.sleuthProperties.getSpanHandler() + .getAdditionalSpanNamePatternsToIgnore()); + return spanNamesToIgnore; + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index 57cb9b986..92e9b2413 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -142,4 +142,10 @@ public class TraceAutoConfiguration { return CurrentSpanCustomizer.create(tracing); } + @Bean + @ConditionalOnProperty(value = "spring.sleuth.span-handler.enabled", matchIfMissing = true) + SpanHandler spanIgnoringSpanHandler(SleuthProperties sleuthProperties) { + return new SpanIgnoringSpanHandler(sleuthProperties); + } + } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandlerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandlerTests.java new file mode 100644 index 000000000..4e19d9450 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/SpanIgnoringSpanHandlerTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013-2020 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.sleuth.autoconfig; + +import java.util.Collections; + +import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.BDDAssertions.then; + +class SpanIgnoringSpanHandlerTests { + + @Test + void should_handle_span_when_not_yet_finished() { + SpanIgnoringSpanHandler handler = new SpanIgnoringSpanHandler(new SleuthProperties()); + + then(handler.end(null, null, SpanHandler.Cause.ABANDONED)).isTrue(); + } + + @Test + void should_handle_span_when_name_null() { + SpanIgnoringSpanHandler handler = new SpanIgnoringSpanHandler(new SleuthProperties()); + + then(handler.end(null, new MutableSpan(), SpanHandler.Cause.FINISHED)).isTrue(); + } + + @Test + void should_handle_span_when_not_present_in_main_list_of_spans_to_skip() { + SpanIgnoringSpanHandler handler = new SpanIgnoringSpanHandler(new SleuthProperties()); + + then(handler.end(null, namedSpan(), SpanHandler.Cause.FINISHED)).isTrue(); + } + + private MutableSpan namedSpan() { + MutableSpan span = new MutableSpan(); + span.name("someName"); + return span; + } + + @Test + void should_not_handle_span_when_present_in_main_list_of_spans_to_skip() { + SleuthProperties sleuthProperties = new SleuthProperties(); + sleuthProperties.getSpanHandler().setSpanNamePatternsToSkip(Collections.singletonList("someName")); + SpanIgnoringSpanHandler handler = new SpanIgnoringSpanHandler(sleuthProperties); + + then(handler.end(null, namedSpan(), SpanHandler.Cause.FINISHED)).isFalse(); + } + + @Test + void should_not_handle_span_when_present_in_additional_list_of_spans_to_skip() { + SleuthProperties sleuthProperties = sleuthPropertiesWithAdditionalEntries(); + SpanIgnoringSpanHandler handler = new SpanIgnoringSpanHandler(sleuthProperties); + + then(handler.end(null, namedSpan(), SpanHandler.Cause.FINISHED)).isFalse(); + } + + @Test + void should_use_cached_entry_for_same_patterns() { + end(handler(sleuthPropertiesWithAdditionalEntries("someOtherName"))); + end(handler(sleuthPropertiesWithAdditionalEntries("someOtherName"))); + end(handler(sleuthPropertiesWithAdditionalEntries("someOtherName"))); + + then(SpanIgnoringSpanHandler.cache).containsKey("someOtherName"); + + end(handler(sleuthPropertiesWithAdditionalEntries("a"))); + end(handler(sleuthPropertiesWithAdditionalEntries("b"))); + end(handler(sleuthPropertiesWithAdditionalEntries("c"))); + + then(SpanIgnoringSpanHandler.cache).containsKey("someOtherName").containsKey("a").containsKey("b") + .containsKey("c"); + } + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(TraceAutoConfiguration.class)); + + @Test + void should_not_register_span_handler_when_property_passed() { + this.contextRunner.withPropertyValues("spring.sleuth.span-handler.enabled=false") + .run((context) -> BDDAssertions.thenThrownBy(() -> context.getBean(SpanIgnoringSpanHandler.class)) + .isInstanceOf(NoSuchBeanDefinitionException.class)); + } + + @Test + void should_register_span_handler_by_default() { + this.contextRunner.run((context) -> context.getBean(SpanIgnoringSpanHandler.class)); + } + + private SleuthProperties sleuthPropertiesWithAdditionalEntries() { + return sleuthPropertiesWithAdditionalEntries("someName"); + } + + private SleuthProperties sleuthPropertiesWithAdditionalEntries(String name) { + SleuthProperties sleuthProperties = new SleuthProperties(); + sleuthProperties.getSpanHandler().setAdditionalSpanNamePatternsToIgnore(Collections.singletonList(name)); + return sleuthProperties; + } + + private void end(SpanIgnoringSpanHandler handler) { + handler.end(null, namedSpan(), SpanHandler.Cause.FINISHED); + } + + private SpanIgnoringSpanHandler handler(SleuthProperties sleuthProperties) { + return new SpanIgnoringSpanHandler(sleuthProperties); + } + +}