Add missing OTel Span attributes

OTel semantic conventions mandate certain resource attributes to present
on exported spans. This commits make sure that the attribute we add are
merged with the defaults, rather than replacing them.

See gh-36155
pull/37018/head
Jonatan Ivanov 1 year ago committed by Stephane Nicoll
parent 83457bb0e8
commit 58cfdbfa2d

@ -97,9 +97,10 @@ public class OpenTelemetryAutoConfiguration {
SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider<SpanProcessor> 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();
}

@ -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<AttributeKey<?>, 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<SpanData> exportedSpans = new ArrayList<>();
private final CountDownLatch latch = new CountDownLatch(1);
@Override
public CompletableResultCode export(Collection<SpanData> 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<SpanData> 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));
}
}
}
}

Loading…
Cancel
Save