diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java index 952b6ed7c1..e2b895d3e3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java @@ -97,9 +97,10 @@ public class OpenTelemetryAutoConfiguration { SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider spanProcessors, Sampler sampler) { String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + Resource springResource = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)); SdkTracerProviderBuilder builder = SdkTracerProvider.builder() .setSampler(sampler) - .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName))); + .setResource(Resource.getDefault().merge(springResource)); spanProcessors.orderedStream().forEach(builder::addSpanProcessor); return builder.build(); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java index 128dad34e0..8f611a2c9c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java @@ -16,8 +16,14 @@ package org.springframework.boot.actuate.autoconfigure.tracing; +import java.time.Duration; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Stream; import io.micrometer.tracing.SpanCustomizer; @@ -30,18 +36,26 @@ import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; import io.micrometer.tracing.otel.bridge.Slf4JEventListener; import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.extension.trace.propagation.B3Propagator; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -143,6 +157,26 @@ class OpenTelemetryAutoConfigurationTests { }); } + @Test + void shouldSetupDefaultResourceAttributes() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(ObservationAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)) + .withUserConfiguration(InMemoryRecordingSpanExporterConfiguration.class) + .withPropertyValues("management.tracing.sampling.probability=1.0") + .run((context) -> { + context.getBean(io.micrometer.tracing.Tracer.class).nextSpan().name("test").end(); + InMemoryRecordingSpanExporter exporter = context.getBean(InMemoryRecordingSpanExporter.class); + exporter.await(Duration.ofSeconds(10)); + SpanData spanData = exporter.getExportedSpans().get(0); + Map, Object> expectedAttributes = Resource.getDefault() + .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "application"))) + .getAttributes() + .asMap(); + assertThat(spanData.getResource().getAttributes().asMap()).isEqualTo(expectedAttributes); + }); + } + @Test void shouldAllowMultipleSpanProcessors() { this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> { @@ -297,4 +331,50 @@ class OpenTelemetryAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + private static class InMemoryRecordingSpanExporterConfiguration { + + @Bean + InMemoryRecordingSpanExporter spanExporter() { + return new InMemoryRecordingSpanExporter(); + } + + } + + private static class InMemoryRecordingSpanExporter implements SpanExporter { + + private final List exportedSpans = new ArrayList<>(); + + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public CompletableResultCode export(Collection spans) { + this.exportedSpans.addAll(spans); + this.latch.countDown(); + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + this.exportedSpans.clear(); + return CompletableResultCode.ofSuccess(); + } + + List getExportedSpans() { + return this.exportedSpans; + } + + void await(Duration timeout) throws InterruptedException, TimeoutException { + if (!this.latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) { + throw new TimeoutException("Waiting for exporting spans timed out (%s)".formatted(timeout)); + } + } + + } + }