From 5142e8e15718b80aceebb40354ee7d4d1f461137 Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Tue, 25 Apr 2023 13:46:19 -0700 Subject: [PATCH] Support Brave's joint spans Brave has a feature called joint spans that was enabled by default with Spring Cloud Sleuth (and Boot 2.x). This has changed with Micrometer Tracing and Boot 3.x. In order to make migration simpler, joint spans should be set through a property. This is a Brave+B3-only feature, W3C is not supported by Brave and OTel does not have this capability. See gh-35165 --- .../tracing/BraveAutoConfiguration.java | 12 +++++- .../tracing/TracingProperties.java | 31 ++++++++++++++- .../tracing/BraveAutoConfigurationTests.java | 39 +++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java index 8deaf835ff..9be6eca7c3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java @@ -54,12 +54,14 @@ import io.micrometer.tracing.exporter.SpanFilter; import io.micrometer.tracing.exporter.SpanReporter; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties.Propagation.PropagationType; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.IncompatibleConfigurationException; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -71,6 +73,7 @@ import org.springframework.core.env.Environment; * * @author Moritz Halbritter * @author Marcin Grzejszczak + * @author Jonatan Ivanov * @since 3.0.0 */ @AutoConfiguration(before = MicrometerTracingAutoConfiguration.class) @@ -97,14 +100,19 @@ public class BraveAutoConfiguration { @Bean @ConditionalOnMissingBean - public Tracing braveTracing(Environment environment, List spanHandlers, + public Tracing braveTracing(Environment environment, TracingProperties properties, List spanHandlers, List tracingCustomizers, CurrentTraceContext currentTraceContext, Factory propagationFactory, Sampler sampler) { + if (properties.getPropagation().getType() == PropagationType.W3C + && properties.getBrave().isSpanJoiningSupported()) { + throw new IncompatibleConfigurationException("management.tracing.propagation.type", + "management.tracing.brave.span-joining-supported"); + } String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); Builder builder = Tracing.newBuilder() .currentTraceContext(currentTraceContext) .traceId128Bit(true) - .supportsJoin(false) + .supportsJoin(properties.getBrave().isSpanJoiningSupported()) .propagationFactory(propagationFactory) .sampler(sampler) .localServiceName(applicationName); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java index 88fedf9733..f642ad117d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -25,6 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * Configuration properties for tracing. * * @author Moritz Halbritter + * @author Jonatan Ivanov * @since 3.0.0 */ @ConfigurationProperties("management.tracing") @@ -50,6 +51,11 @@ public class TracingProperties { */ private final Propagation propagation = new Propagation(); + /** + * Brave configuration. + */ + private final Brave brave = new Brave(); + public boolean isEnabled() { return this.enabled; } @@ -70,6 +76,10 @@ public class TracingProperties { return this.propagation; } + public Brave getBrave() { + return this.brave; + } + public static class Sampling { /** @@ -194,4 +204,23 @@ public class TracingProperties { } + public static class Brave { + + /** + * Whether the propagation type and tracing backend support sharing the span ID + * between client and server spans. Requires B3 propagation and a compatible + * backend. + */ + private boolean spanJoiningSupported = false; + + public boolean isSpanJoiningSupported() { + return this.spanJoiningSupported; + } + + public void setSpanJoiningSupported(boolean spanJoiningSupported) { + this.spanJoiningSupported = spanJoiningSupported; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java index 0f7d2e547b..ed6fe42054 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfigurationTests.java @@ -43,6 +43,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfigurationTests.SpanHandlerConfiguration.AdditionalSpanHandler; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.properties.IncompatibleConfigurationException; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -50,12 +51,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; /** * Tests for {@link BraveAutoConfiguration}. * * @author Moritz Halbritter + * @author Jonatan Ivanov */ class BraveAutoConfigurationTests { @@ -216,17 +219,45 @@ class BraveAutoConfigurationTests { } @Test - void shouldNotSupportJoinedSpans() { + void shouldNotSupportJoinedSpansByDefault() { this.contextRunner.run((context) -> { Tracing tracing = context.getBean(Tracing.class); Span parentSpan = tracing.tracer().nextSpan(); Span childSpan = tracing.tracer().joinSpan(parentSpan.context()); - assertThat(parentSpan.context().traceIdString()).isEqualTo(childSpan.context().traceIdString()); - assertThat(parentSpan.context().spanIdString()).isEqualTo(childSpan.context().parentIdString()); - assertThat(parentSpan.context().spanIdString()).isNotEqualTo(childSpan.context().spanIdString()); + assertThat(childSpan.context().traceIdString()).isEqualTo(parentSpan.context().traceIdString()); + assertThat(childSpan.context().spanIdString()).isNotEqualTo(parentSpan.context().spanIdString()); + assertThat(childSpan.context().parentIdString()).isEqualTo(parentSpan.context().spanIdString()); + assertThat(parentSpan.context().parentIdString()).isNull(); }); } + @Test + void shouldSupportJoinedSpansIfB3UsedAndBackendSupportsIt() { + this.contextRunner + .withPropertyValues("management.tracing.propagation.type=B3", + "management.tracing.brave.span-joining-supported=true") + .run((context) -> { + Tracing tracing = context.getBean(Tracing.class); + Span parentSpan = tracing.tracer().nextSpan(); + Span childSpan = tracing.tracer().joinSpan(parentSpan.context()); + assertThat(childSpan.context().traceIdString()).isEqualTo(parentSpan.context().traceIdString()); + assertThat(childSpan.context().spanIdString()).isEqualTo(parentSpan.context().spanIdString()); + assertThat(childSpan.context().parentIdString()).isNull(); + assertThat(parentSpan.context().parentIdString()).isNull(); + }); + } + + @Test + void shouldFailIfSupportJoinedSpansIsEnabledAndW3cIsChosen() { + this.contextRunner + .withPropertyValues("management.tracing.propagation.type=W3C", + "management.tracing.brave.span-joining-supported=true") + .run((context) -> assertThatThrownBy(() -> context.getBean(Tracing.class)).rootCause() + .isExactlyInstanceOf(IncompatibleConfigurationException.class) + .hasMessage( + "The following configuration properties have incompatible values: [management.tracing.propagation.type, management.tracing.brave.span-joining-supported]")); + } + @Test @SuppressWarnings("rawtypes") void compositeSpanHandlerShouldBeFirstSpanHandler() {