Skip to content

Commit db474af

Browse files
committed
Spring Boot Starter service-name is constant
Pattern-based resource configuration
1 parent d2bd06e commit db474af

File tree

9 files changed

+317
-2
lines changed

9 files changed

+317
-2
lines changed

instrumentation/spring/spring-boot-autoconfigure/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,13 @@ If an exporter is present in the classpath during runtime and a spring bean of t
392392

393393
<!-- Slf4j Log Correlation otel.springboot.loggers.slf4j.enabled true org.slf4j.MDC -->
394394

395+
##### Resource Properties
396+
397+
| Feature | Property | Default Value |
398+
|----------|--------------------------------------------------|------------------------|
399+
| Resource | otel.springboot.resource.enabled | `true` |
400+
| | otel.springboot.resource.attributes.service.name | `unknown_service:java` |
401+
395402
##### Exporter Properties
396403

397404
| Feature | Property | Default Value |

instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525
compileOnly("io.opentelemetry:opentelemetry-extension-annotations")
2626
compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators")
2727
compileOnly("io.opentelemetry:opentelemetry-extension-aws")
28+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-resources")
2829
compileOnly("io.opentelemetry:opentelemetry-exporter-logging")
2930
compileOnly("io.opentelemetry:opentelemetry-exporter-jaeger")
3031
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
@@ -41,6 +42,7 @@ dependencies {
4142
testImplementation(project(":testing-common"))
4243
testImplementation("io.opentelemetry:opentelemetry-sdk")
4344
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
45+
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-resources")
4446
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
4547
testImplementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
4648
testImplementation("io.opentelemetry:opentelemetry-extension-aws")

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,26 @@
66
package io.opentelemetry.instrumentation.spring.autoconfigure;
77

88
import io.opentelemetry.api.OpenTelemetry;
9+
import io.opentelemetry.api.common.Attributes;
910
import io.opentelemetry.api.trace.TracerProvider;
1011
import io.opentelemetry.context.propagation.ContextPropagators;
1112
import io.opentelemetry.sdk.OpenTelemetrySdk;
13+
import io.opentelemetry.sdk.resources.Resource;
1214
import io.opentelemetry.sdk.trace.SdkTracerProvider;
1315
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
1416
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
1517
import io.opentelemetry.sdk.trace.export.SpanExporter;
1618
import io.opentelemetry.sdk.trace.samplers.Sampler;
19+
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
1720
import java.util.Collections;
1821
import java.util.List;
22+
import java.util.function.Supplier;
1923
import org.springframework.beans.factory.ObjectProvider;
2024
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2125
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2226
import org.springframework.context.annotation.Bean;
2327
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.core.env.Environment;
2429

2530
/**
2631
* Create {@link io.opentelemetry.api.trace.Tracer} bean if bean is missing.
@@ -41,18 +46,42 @@ public static class OpenTelemetryBeanConfig {
4146
@ConditionalOnMissingBean
4247
public SdkTracerProvider sdkTracerProvider(
4348
SamplerProperties samplerProperties,
44-
ObjectProvider<List<SpanExporter>> spanExportersProvider) {
49+
ObjectProvider<List<SpanExporter>> spanExportersProvider,
50+
Resource otelResource) {
4551
SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder();
4652

4753
spanExportersProvider.getIfAvailable(Collections::emptyList).stream()
4854
.map(spanExporter -> BatchSpanProcessor.builder(spanExporter).build())
4955
.forEach(tracerProviderBuilder::addSpanProcessor);
5056

5157
return tracerProviderBuilder
58+
.setResource(otelResource)
5259
.setSampler(Sampler.traceIdRatioBased(samplerProperties.getProbability()))
5360
.build();
5461
}
5562

63+
@Bean
64+
@ConditionalOnMissingBean
65+
public Resource otelResource(
66+
Environment env, ObjectProvider<List<Supplier<Resource>>> resourceProviders) {
67+
String applicationName = env.getProperty("spring.application.name");
68+
Resource resource = defaultResource(applicationName);
69+
List<Supplier<Resource>> resourceCustomizers =
70+
resourceProviders.getIfAvailable(Collections::emptyList);
71+
for (Supplier<Resource> resourceCustomizer : resourceCustomizers) {
72+
resource = resource.merge(resourceCustomizer.get());
73+
}
74+
return resource;
75+
}
76+
77+
private static Resource defaultResource(String applicationName) {
78+
if (applicationName == null) {
79+
return Resource.getDefault();
80+
}
81+
return Resource.getDefault()
82+
.merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)));
83+
}
84+
5685
@Bean
5786
public OpenTelemetry openTelemetry(
5887
ObjectProvider<ContextPropagators> propagatorsProvider, SdkTracerProvider tracerProvider) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import io.opentelemetry.api.common.AttributesBuilder;
10+
import io.opentelemetry.sdk.extension.resources.ContainerResource;
11+
import io.opentelemetry.sdk.extension.resources.HostResource;
12+
import io.opentelemetry.sdk.extension.resources.OsResource;
13+
import io.opentelemetry.sdk.extension.resources.ProcessResource;
14+
import io.opentelemetry.sdk.extension.resources.ProcessRuntimeResource;
15+
import io.opentelemetry.sdk.resources.Resource;
16+
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
17+
import java.util.Map;
18+
import java.util.function.Supplier;
19+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.core.env.Environment;
26+
27+
@Configuration
28+
@EnableConfigurationProperties(OtelResourceProperties.class)
29+
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
30+
@ConditionalOnProperty(prefix = "otel.springboot.resource", name = "enabled", matchIfMissing = true)
31+
public class OtelResourceAutoConfiguration {
32+
33+
@Bean
34+
public Supplier<Resource> otelResourceProvider(
35+
Environment env, OtelResourceProperties otelResourceProperties) {
36+
return () -> {
37+
AttributesBuilder attributesBuilder = Attributes.builder();
38+
for (Map.Entry<String, String> entry : otelResourceProperties.getAttributes().entrySet()) {
39+
attributesBuilder.put(entry.getKey(), entry.getValue());
40+
}
41+
String otelServiceName = env.getProperty("otel.springboot.resource.attributes.service.name");
42+
if (otelServiceName != null) {
43+
attributesBuilder.put(ResourceAttributes.SERVICE_NAME, otelServiceName);
44+
}
45+
Attributes attributes = attributesBuilder.build();
46+
return Resource.create(attributes);
47+
};
48+
}
49+
50+
@Bean
51+
@ConditionalOnClass(OsResource.class)
52+
public Supplier<Resource> otelOsResourceProvider() {
53+
return OsResource::get;
54+
}
55+
56+
@Bean
57+
@ConditionalOnClass(ProcessResource.class)
58+
public Supplier<Resource> otelProcessResourceProvider() {
59+
return ProcessResource::get;
60+
}
61+
62+
@Bean
63+
@ConditionalOnClass(ProcessRuntimeResource.class)
64+
public Supplier<Resource> otelProcessRuntimeResourceProvider() {
65+
return ProcessRuntimeResource::get;
66+
}
67+
68+
@Bean
69+
@ConditionalOnClass(HostResource.class)
70+
public Supplier<Resource> otelHostResourceProvider() {
71+
return HostResource::get;
72+
}
73+
74+
@Bean
75+
@ConditionalOnClass(ContainerResource.class)
76+
public Supplier<Resource> otelContainerResource() {
77+
return ContainerResource::get;
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure;
7+
8+
import java.util.Collections;
9+
import java.util.Map;
10+
import org.springframework.boot.context.properties.ConfigurationProperties;
11+
12+
@ConfigurationProperties(prefix = "otel.springboot.resource")
13+
public class OtelResourceProperties {
14+
private Map<String, String> attributes = Collections.emptyMap();
15+
16+
public Map<String, String> getAttributes() {
17+
return attributes;
18+
}
19+
20+
public void setAttributes(Map<String, String> attributes) {
21+
this.attributes = attributes;
22+
}
23+
}

instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura
88
io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration,\
99
io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration,\
1010
io.opentelemetry.instrumentation.spring.autoconfigure.webmvc.WebMvcFilterAutoConfiguration,\
11-
io.opentelemetry.instrumentation.spring.autoconfigure.aspects.TraceAspectAutoConfiguration
11+
io.opentelemetry.instrumentation.spring.autoconfigure.aspects.TraceAspectAutoConfiguration,\
12+
io.opentelemetry.instrumentation.spring.autoconfigure.OtelResourceAutoConfiguration

instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java

+31
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
package io.opentelemetry.instrumentation.spring.autoconfigure;
77

8+
import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME;
89
import static org.assertj.core.api.Assertions.assertThat;
910

1011
import io.opentelemetry.api.GlobalOpenTelemetry;
1112
import io.opentelemetry.api.OpenTelemetry;
13+
import io.opentelemetry.sdk.resources.Resource;
1214
import io.opentelemetry.sdk.trace.SdkTracerProvider;
1315
import org.junit.jupiter.api.AfterEach;
1416
import org.junit.jupiter.api.DisplayName;
@@ -76,4 +78,33 @@ void initializeOpenTelemetry() {
7678
.hasBean("customTracerProvider")
7779
.doesNotHaveBean("sdkTracerProvider"));
7880
}
81+
82+
@Test
83+
@DisplayName(
84+
"when spring.application.name is set value should be passed to service name attribute")
85+
void shouldDetermineServiceNameBySpringApplicationName() {
86+
this.contextRunner
87+
.withPropertyValues("spring.application.name=myapp-backend")
88+
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
89+
.run(
90+
context -> {
91+
Resource otelResource = context.getBean("otelResource", Resource.class);
92+
93+
assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("myapp-backend");
94+
});
95+
}
96+
97+
@Test
98+
@DisplayName(
99+
"when spring application name and otel service name are not set service name should be default")
100+
void hasDefaultServiceName() {
101+
this.contextRunner
102+
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
103+
.run(
104+
context -> {
105+
Resource otelResource = context.getBean("otelResource", Resource.class);
106+
107+
assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("unknown_service:java");
108+
});
109+
}
79110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure;
7+
8+
import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME;
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
11+
import io.opentelemetry.api.GlobalOpenTelemetry;
12+
import io.opentelemetry.api.common.AttributeKey;
13+
import io.opentelemetry.sdk.resources.Resource;
14+
import org.junit.jupiter.api.AfterEach;
15+
import org.junit.jupiter.api.DisplayName;
16+
import org.junit.jupiter.api.Test;
17+
import org.springframework.boot.autoconfigure.AutoConfigurations;
18+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
19+
20+
public class OtelResourceAutoConfigurationTest {
21+
private final ApplicationContextRunner contextRunner =
22+
new ApplicationContextRunner()
23+
.withConfiguration(
24+
AutoConfigurations.of(
25+
OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class));
26+
27+
@AfterEach
28+
void tearDown() {
29+
GlobalOpenTelemetry.resetForTest();
30+
}
31+
32+
@Test
33+
@DisplayName("when otel service name is set it should be set as service name attribute")
34+
void shouldDetermineServiceNameByOtelServiceName() {
35+
this.contextRunner
36+
.withPropertyValues(
37+
"otel.springboot.resource.attributes.service.name=otel-name-backend",
38+
"otel.springboot.resource.enabled=true")
39+
.run(
40+
context -> {
41+
Resource otelResource = context.getBean("otelResource", Resource.class);
42+
43+
assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("otel-name-backend");
44+
});
45+
}
46+
47+
@Test
48+
@DisplayName(
49+
"when otel.springboot.resource.enabled is not specified configuration should be initialized")
50+
void shouldInitAutoConfigurationByDefault() {
51+
this.contextRunner
52+
.withPropertyValues("otel.springboot.resource.attributes.service.name=otel-name-backend")
53+
.run(
54+
context -> {
55+
Resource otelResource = context.getBean("otelResource", Resource.class);
56+
57+
assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("otel-name-backend");
58+
});
59+
}
60+
61+
@Test
62+
@DisplayName(
63+
"when otel.springboot.resource.enabled is set to false configuration should NOT be initialized")
64+
void shouldNotInitAutoConfiguration() {
65+
this.contextRunner
66+
.withPropertyValues(
67+
"otel.springboot.resource.attributes.service.name=otel-name-backend",
68+
"otel.springboot.resource.enabled=false")
69+
.run(context -> assertThat(context.containsBean("otelResourceProvider")).isFalse());
70+
}
71+
72+
@Test
73+
@DisplayName("when otel attributes are set in properties they should be put in resource")
74+
void shouldInitializeAttributes() {
75+
this.contextRunner
76+
.withPropertyValues(
77+
"otel.springboot.resource.attributes.xyz=foo",
78+
"otel.springboot.resource.attributes.environment=dev",
79+
"otel.springboot.resource.enabled=true")
80+
.run(
81+
context -> {
82+
Resource otelResource = context.getBean("otelResource", Resource.class);
83+
84+
assertThat(otelResource.getAttribute(AttributeKey.stringKey("environment")))
85+
.isEqualTo("dev");
86+
assertThat(otelResource.getAttribute(AttributeKey.stringKey("xyz"))).isEqualTo("foo");
87+
});
88+
}
89+
}

0 commit comments

Comments
 (0)