diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index de4957584..11d2ccda5 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -305,15 +305,15 @@ Span nextSpan(final Request input) { === Sampling in Spring Cloud Sleuth -By default Spring Cloud Sleuth sets all spans to non-exportable. -That means that traces appear in logs but not in any remote store. -For testing the default is often enough, and it probably is all you need if you use only the logs (for example, with an ELK aggregator). -If you export span data to Zipkin, there is also an `Sampler.ALWAYS_SAMPLE` setting that exports everything, `RateLimitingSampler` setting that samples X transactions per second (defaults to `1000`) or `ProbabilityBasedSampler` setting that samples a fixed fraction of spans. +Sampling only applies to tracing backends, such as Zipkin. Trace IDs appear in logs regardless of +sample rate. Sampling is a way to prevent overloading the system, by consistently tracing some, but +not all requests. -NOTE: The `RateLimitingSampler` is the default if you use `spring-cloud-sleuth-zipkin`. -You can configure the rate limit by setting `spring.sleuth.sampler.rate`. +The default rate of 10 traces per second is controlled by the `spring.sleuth.sampler.rate` +property and applies when we know Sleuth is used for reasons besides logging. Use a rate above 100 +traces per second with extreme caution as it can overload your tracing system. -A sampler can be installed by creating a bean definition, as shown in the following example: +The sampler can be set by Java Config also, as shown in the following example: [source,java] ---- @@ -321,9 +321,7 @@ include::{project-root}/spring-cloud-sleuth-core/src/test/java/org/springframewo ---- TIP: You can set the HTTP header `X-B3-Flags` to `1`, or, when doing messaging, you can set the `spanFlags` header to `1`. -Doing so forces the current span to be exportable regardless of the sampling decision. - -In order to use the rate-limited sampler set the `spring.sleuth.sampler.rate` property to choose an amount of traces to accept on a per-second interval. The minimum number is 0 and the max is 2,147,483,647 (max int). +Doing so forces the corresponding trace to be sampled regardless of the sampling configuration. == Propagation diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerAutoConfiguration.java index 25270732d..473fbdd5e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerAutoConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.cloud.sleuth.sampler; +import brave.sampler.CountingSampler; import brave.sampler.Sampler; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -23,12 +24,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * {@linkplain Configuration configuration} for {@link Sampler}. * * @author Marcin Grzejszczak + * @see SamplerCondition * @since 2.1.0 */ @Configuration(proxyBeanMethods = false) @@ -44,14 +47,17 @@ public class SamplerAutoConfiguration { return Sampler.NEVER_SAMPLE; } + // NOTE: Brave's default samplers return Sampler.NEVER_SAMPLE if the config implies + // that static Sampler samplerFromProps(SamplerProperties config) { if (config.getProbability() != null) { - return new ProbabilityBasedSampler(config); + return CountingSampler.create(config.getProbability()); } - return new RateLimitingSampler(config); + return brave.sampler.RateLimitingSampler.create(config.getRate()); } @Configuration(proxyBeanMethods = false) + @Conditional(SamplerCondition.class) @ConditionalOnBean( type = "org.springframework.cloud.context.scope.refresh.RefreshScope") protected static class RefreshScopedSamplerConfiguration { @@ -60,12 +66,18 @@ public class SamplerAutoConfiguration { @RefreshScope @ConditionalOnMissingBean public Sampler defaultTraceSampler(SamplerProperties config) { - return samplerFromProps(config); + // TODO: Rewrite: refresh should replace the sampler, not change its state + // internally + if (config.getProbability() != null) { + return new ProbabilityBasedSampler(config); + } + return new RateLimitingSampler(config); } } @Configuration(proxyBeanMethods = false) + @Conditional(SamplerCondition.class) @ConditionalOnMissingBean( type = "org.springframework.cloud.context.scope.refresh.RefreshScope") protected static class NonRefreshScopeSamplerConfiguration { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerCondition.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerCondition.java new file mode 100644 index 000000000..77c6164d9 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerCondition.java @@ -0,0 +1,82 @@ +/* + * 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.sleuth.sampler; + +import brave.TracingCustomizer; +import brave.handler.FinishedSpanHandler; +import brave.sampler.Sampler; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + +/** + * Sleuth 1.x optimized for log-correlation only. Unless "spring-cloud-sleuth-zipkin" was + * present, the sampler defaulted to {@link Sampler#NEVER_SAMPLE}. This was to ensure the + * log only nodes never set the sampled flag. + * + *
+ * "spring-cloud-sleuth-zipkin" obviated the {@link Sampler#NEVER_SAMPLE} default by + * importing {@link SamplerAutoConfiguration}. Nothing else did, so sampling properties + * were effectively ignored unless "spring-cloud-sleuth-zipkin" was in use, or something + * else similarly imported {@link SamplerAutoConfiguration}. + * + *
+ * During a review of Wavefront integration, it was considered not correct to have other + * code import {@link SamplerAutoConfiguration}. To avoid that, retain the old behaviour + * about log only nodes, and also not pin configuration to Zipkin involves a more complex + * condition. + * + *
+ * This condition looks for signs of non-default setup which likely requires sampling + * configuration. It passes on one of the following beans exist: + * + *
+ *
+ * An integrated test that shows {@link Sampler#NEVER_SAMPLE} is default on fail exists in
+ * {@code TraceAutoConfigurationTests} intentionally, as users now needn't import
+ * {@link SamplerAutoConfiguration} directly.
+ */
+final class SamplerCondition extends AnyNestedCondition {
+
+ SamplerCondition() {
+ super(ConfigurationPhase.REGISTER_BEAN);
+ }
+
+ // zipkin2.reporter.Reporter is in the classpath here, but technically it is optional
+ @ConditionalOnBean(type = "zipkin2.reporter.Reporter")
+ static final class ReporterAvailable {
+
+ }
+
+ @ConditionalOnBean(FinishedSpanHandler.class)
+ static final class FinishedSpanHandlerAvailable {
+
+ }
+
+ @ConditionalOnBean(TracingCustomizer.class)
+ static final class TracingCustomizerAvailable {
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java
index ebfd2e26a..f61bf5902 100644
--- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java
@@ -19,11 +19,14 @@ package org.springframework.cloud.sleuth.autoconfig;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.Propagation;
+import brave.sampler.RateLimitingSampler;
+import brave.sampler.Sampler;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import zipkin2.reporter.InMemoryReporterMetrics;
+import zipkin2.reporter.Reporter;
import zipkin2.reporter.ReporterMetrics;
import zipkin2.reporter.metrics.micrometer.MicrometerReporterMetrics;
@@ -68,8 +71,48 @@ public class TraceAutoConfigurationTests {
});
}
+ /**
+ * Duplicates
+ * {@link org.springframework.cloud.sleuth.sampler.SamplerAutoConfigurationTests}
+ * intentionally, to ensure configuration condition bugs do not exist.
+ */
@Test
- public void should_use_B3Propagation_factory_if_no_have_any_config() {
+ public void should_use_NEVER_SAMPLER_when_only_logging() {
+ this.contextRunner.run((context -> {
+ final Sampler bean = context.getBean(Sampler.class);
+ BDDAssertions.then(bean).isSameAs(Sampler.NEVER_SAMPLE);
+ }));
+ }
+
+ /**
+ * Duplicates
+ * {@link org.springframework.cloud.sleuth.sampler.SamplerAutoConfigurationTests}
+ * intentionally, to ensure configuration condition bugs do not exist.
+ */
+ @Test
+ public void should_use_RateLimitedSampler_when_reporting() {
+ this.contextRunner.withUserConfiguration(WithReporter.class).run((context -> {
+ final Sampler bean = context.getBean(Sampler.class);
+ BDDAssertions.then(bean).isInstanceOf(RateLimitingSampler.class);
+ }));
+ }
+
+ /**
+ * Duplicates
+ * {@link org.springframework.cloud.sleuth.sampler.SamplerAutoConfigurationTests}
+ * intentionally, to ensure configuration condition bugs do not exist.
+ */
+ @Test
+ public void should_override_sampler() {
+ this.contextRunner.withUserConfiguration(WithReporter.class, WithSampler.class)
+ .run((context -> {
+ final Sampler bean = context.getBean(Sampler.class);
+ BDDAssertions.then(bean).isSameAs(Sampler.ALWAYS_SAMPLE);
+ }));
+ }
+
+ @Test
+ public void should_use_B3Propagation_factory_by_default() {
this.contextRunner.run((context -> {
final Propagation.Factory bean = context.getBean(Propagation.Factory.class);
BDDAssertions.then(bean).isInstanceOf(Propagation.Factory.class);
@@ -106,6 +149,26 @@ public class TraceAutoConfigurationTests {
}
+ @Configuration
+ static class WithReporter {
+
+ @Bean
+ Reporter