diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc index e0cccc30931..6cdd91017dd 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc @@ -236,6 +236,9 @@ Baggage is propagated wherever Micrometer Observation instruments the transport, If you want to propagate the baggage to the MDC, use the configprop:management.tracing.baggage.correlation.fields[] configuration property. For the example above, setting this property to `baggage1` results in an MDC entry named `baggage1`. +If you want baggage values to be added as tags on spans, use the configprop:management.tracing.baggage.tag-fields[] configuration property. +For the example above, setting this property to `baggage1` results in spans that are tagged with `baggage1=value1`. + [[actuator.micrometer-tracing.tests]] diff --git a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryPropagationConfigurations.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryPropagationConfigurations.java index 4f9fb4dbb01..540fc9053a2 100644 --- a/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryPropagationConfigurations.java +++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/main/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryPropagationConfigurations.java @@ -18,18 +18,22 @@ package org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure; import java.util.List; +import io.micrometer.tracing.otel.bridge.BaggageTaggingSpanProcessor; import io.micrometer.tracing.otel.bridge.OtelBaggageManager; import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.opentelemetry.context.propagation.TextMapPropagator; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.OnPropertyListCondition; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.micrometer.tracing.autoconfigure.ConditionalOnEnabledTracingExport; import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** @@ -37,6 +41,7 @@ import org.springframework.context.annotation.Configuration; * {@link OpenTelemetryTracingAutoConfiguration}. * * @author Moritz Halbritter + * @author Oleksandr Shevchenko */ class OpenTelemetryPropagationConfigurations { @@ -87,6 +92,26 @@ class OpenTelemetryPropagationConfigurations { return new Slf4JBaggageEventListener(this.tracingProperties.getBaggage().getCorrelation().getFields()); } + @Bean + @ConditionalOnMissingBean + @Conditional(OnBaggageTagFieldsCondition.class) + BaggageTaggingSpanProcessor otelBaggageTaggingSpanProcessor() { + return new BaggageTaggingSpanProcessor(this.tracingProperties.getBaggage().getTagFields()); + } + + /** + * Condition that matches when {@code management.tracing.baggage.tag-fields} has + * at least one entry. + */ + static class OnBaggageTagFieldsCondition extends OnPropertyListCondition { + + OnBaggageTagFieldsCondition() { + super("management.tracing.baggage.tag-fields", + () -> ConditionMessage.forCondition("Baggage tag fields")); + } + + } + } /** diff --git a/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java index 9e86db47034..4c79a1b6ece 100644 --- a/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java +++ b/module/spring-boot-micrometer-tracing-opentelemetry/src/test/java/org/springframework/boot/micrometer/tracing/opentelemetry/autoconfigure/OpenTelemetryTracingAutoConfigurationTests.java @@ -30,6 +30,7 @@ import io.micrometer.tracing.SpanCustomizer; import io.micrometer.tracing.Tracer.SpanInScope; import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler; import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler; +import io.micrometer.tracing.otel.bridge.BaggageTaggingSpanProcessor; import io.micrometer.tracing.otel.bridge.EventListener; import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; import io.micrometer.tracing.otel.bridge.OtelPropagator; @@ -40,18 +41,21 @@ import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; import io.micrometer.tracing.otel.bridge.Slf4JEventListener; import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.micrometer.tracing.propagation.Propagator; +import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.extension.trace.propagation.B3Propagator; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanLimits; import io.opentelemetry.sdk.trace.SpanProcessor; @@ -91,6 +95,7 @@ import static org.mockito.Mockito.mock; * @author Moritz Halbritter * @author Andy Wilkinson * @author Yanming Zhou + * @author Oleksandr Shevchenko */ class OpenTelemetryTracingAutoConfigurationTests { @@ -390,6 +395,54 @@ class OpenTelemetryTracingAutoConfigurationTests { }); } + @Test + void shouldSupplyBaggageTaggingSpanProcessorWhenTagFieldsAreConfigured() { + this.contextRunner.withPropertyValues("management.tracing.baggage.tag-fields=t1,t2").run((context) -> { + assertThat(context).hasSingleBean(BaggageTaggingSpanProcessor.class); + SpanProcessors spanProcessors = context.getBean(SpanProcessors.class); + assertThat(spanProcessors).anyMatch(BaggageTaggingSpanProcessor.class::isInstance); + }); + } + + @Test + void shouldNotSupplyBaggageTaggingSpanProcessorWhenTagFieldsAreNotConfigured() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(BaggageTaggingSpanProcessor.class)); + } + + @Test + void shouldNotSupplyBaggageTaggingSpanProcessorWhenBaggageDisabled() { + this.contextRunner + .withPropertyValues("management.tracing.baggage.enabled=false", "management.tracing.baggage.tag-fields=t1") + .run((context) -> assertThat(context).doesNotHaveBean(BaggageTaggingSpanProcessor.class)); + } + + @Test + void shouldBackOffOnCustomBaggageTaggingSpanProcessor() { + this.contextRunner.withPropertyValues("management.tracing.baggage.tag-fields=t1") + .withUserConfiguration(CustomBaggageTaggingSpanProcessorConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("customBaggageTaggingSpanProcessor"); + assertThat(context).hasSingleBean(BaggageTaggingSpanProcessor.class); + }); + } + + @Test + void baggageTaggingSpanProcessorShouldTagSpansWithBaggageFromParentContext() { + this.contextRunner + .withPropertyValues("management.tracing.baggage.tag-fields=t1", + "management.tracing.sampling.probability=1.0") + .run((context) -> { + Tracer tracer = context.getBean(Tracer.class); + try (Scope scope = Baggage.builder().put("t1", "v1").put("other", "v2").build().makeCurrent()) { + Span span = tracer.spanBuilder("test").startSpan(); + span.end(); + assertThat(span).isInstanceOf(ReadableSpan.class); + assertThat(((ReadableSpan) span).getAttribute(AttributeKey.stringKey("t1"))).isEqualTo("v1"); + assertThat(((ReadableSpan) span).getAttribute(AttributeKey.stringKey("other"))).isNull(); + } + }); + } + @Test void spanLimitsShouldBeConfiguredWithCustomProperties() { this.contextRunner @@ -608,6 +661,16 @@ class OpenTelemetryTracingAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + private static final class CustomBaggageTaggingSpanProcessorConfiguration { + + @Bean + BaggageTaggingSpanProcessor customBaggageTaggingSpanProcessor() { + return new BaggageTaggingSpanProcessor(List.of("custom")); + } + + } + @Configuration(proxyBeanMethods = false) private static final class AdditionalSpanProcessorConfiguration {