Skip to content

Commit 58cfdbf

Browse files
jonatan-ivanovsnicoll
authored andcommitted
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
1 parent 83457bb commit 58cfdbf

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,10 @@ OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagat
9797
SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider<SpanProcessor> spanProcessors,
9898
Sampler sampler) {
9999
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
100+
Resource springResource = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName));
100101
SdkTracerProviderBuilder builder = SdkTracerProvider.builder()
101102
.setSampler(sampler)
102-
.setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)));
103+
.setResource(Resource.getDefault().merge(springResource));
103104
spanProcessors.orderedStream().forEach(builder::addSpanProcessor);
104105
return builder.build();
105106
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfigurationTests.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.tracing;
1818

19+
import java.time.Duration;
1920
import java.util.ArrayList;
21+
import java.util.Collection;
2022
import java.util.List;
23+
import java.util.Map;
24+
import java.util.concurrent.CountDownLatch;
25+
import java.util.concurrent.TimeUnit;
26+
import java.util.concurrent.TimeoutException;
2127
import java.util.stream.Stream;
2228

2329
import io.micrometer.tracing.SpanCustomizer;
@@ -30,18 +36,26 @@
3036
import io.micrometer.tracing.otel.bridge.Slf4JEventListener;
3137
import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator;
3238
import io.opentelemetry.api.OpenTelemetry;
39+
import io.opentelemetry.api.common.AttributeKey;
40+
import io.opentelemetry.api.common.Attributes;
3341
import io.opentelemetry.api.trace.Tracer;
3442
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
3543
import io.opentelemetry.context.propagation.ContextPropagators;
3644
import io.opentelemetry.context.propagation.TextMapPropagator;
3745
import io.opentelemetry.extension.trace.propagation.B3Propagator;
46+
import io.opentelemetry.sdk.common.CompletableResultCode;
47+
import io.opentelemetry.sdk.resources.Resource;
3848
import io.opentelemetry.sdk.trace.SdkTracerProvider;
3949
import io.opentelemetry.sdk.trace.SpanProcessor;
50+
import io.opentelemetry.sdk.trace.data.SpanData;
51+
import io.opentelemetry.sdk.trace.export.SpanExporter;
4052
import io.opentelemetry.sdk.trace.samplers.Sampler;
53+
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
4154
import org.junit.jupiter.api.Test;
4255
import org.junit.jupiter.params.ParameterizedTest;
4356
import org.junit.jupiter.params.provider.ValueSource;
4457

58+
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
4559
import org.springframework.boot.autoconfigure.AutoConfigurations;
4660
import org.springframework.boot.test.context.FilteredClassLoader;
4761
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -143,6 +157,26 @@ void shouldBackOffOnCustomBeans() {
143157
});
144158
}
145159

160+
@Test
161+
void shouldSetupDefaultResourceAttributes() {
162+
this.contextRunner
163+
.withConfiguration(
164+
AutoConfigurations.of(ObservationAutoConfiguration.class, MicrometerTracingAutoConfiguration.class))
165+
.withUserConfiguration(InMemoryRecordingSpanExporterConfiguration.class)
166+
.withPropertyValues("management.tracing.sampling.probability=1.0")
167+
.run((context) -> {
168+
context.getBean(io.micrometer.tracing.Tracer.class).nextSpan().name("test").end();
169+
InMemoryRecordingSpanExporter exporter = context.getBean(InMemoryRecordingSpanExporter.class);
170+
exporter.await(Duration.ofSeconds(10));
171+
SpanData spanData = exporter.getExportedSpans().get(0);
172+
Map<AttributeKey<?>, Object> expectedAttributes = Resource.getDefault()
173+
.merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "application")))
174+
.getAttributes()
175+
.asMap();
176+
assertThat(spanData.getResource().getAttributes().asMap()).isEqualTo(expectedAttributes);
177+
});
178+
}
179+
146180
@Test
147181
void shouldAllowMultipleSpanProcessors() {
148182
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
@@ -297,4 +331,50 @@ SpanCustomizer customSpanCustomizer() {
297331

298332
}
299333

334+
@Configuration(proxyBeanMethods = false)
335+
private static class InMemoryRecordingSpanExporterConfiguration {
336+
337+
@Bean
338+
InMemoryRecordingSpanExporter spanExporter() {
339+
return new InMemoryRecordingSpanExporter();
340+
}
341+
342+
}
343+
344+
private static class InMemoryRecordingSpanExporter implements SpanExporter {
345+
346+
private final List<SpanData> exportedSpans = new ArrayList<>();
347+
348+
private final CountDownLatch latch = new CountDownLatch(1);
349+
350+
@Override
351+
public CompletableResultCode export(Collection<SpanData> spans) {
352+
this.exportedSpans.addAll(spans);
353+
this.latch.countDown();
354+
return CompletableResultCode.ofSuccess();
355+
}
356+
357+
@Override
358+
public CompletableResultCode flush() {
359+
return CompletableResultCode.ofSuccess();
360+
}
361+
362+
@Override
363+
public CompletableResultCode shutdown() {
364+
this.exportedSpans.clear();
365+
return CompletableResultCode.ofSuccess();
366+
}
367+
368+
List<SpanData> getExportedSpans() {
369+
return this.exportedSpans;
370+
}
371+
372+
void await(Duration timeout) throws InterruptedException, TimeoutException {
373+
if (!this.latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
374+
throw new TimeoutException("Waiting for exporting spans timed out (%s)".formatted(timeout));
375+
}
376+
}
377+
378+
}
379+
300380
}

0 commit comments

Comments
 (0)