Skip to content

Commit

Permalink
Spring Boot Starter service-name is constant
Browse files Browse the repository at this point in the history
Pattern-based resource configuration
  • Loading branch information
aschugunov committed Mar 13, 2022
1 parent d2bd06e commit db474af
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 2 deletions.
7 changes: 7 additions & 0 deletions instrumentation/spring/spring-boot-autoconfigure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,13 @@ If an exporter is present in the classpath during runtime and a spring bean of t

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

##### Resource Properties

| Feature | Property | Default Value |
|----------|--------------------------------------------------|------------------------|
| Resource | otel.springboot.resource.enabled | `true` |
| | otel.springboot.resource.attributes.service.name | `unknown_service:java` |

##### Exporter Properties

| Feature | Property | Default Value |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
compileOnly("io.opentelemetry:opentelemetry-extension-annotations")
compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators")
compileOnly("io.opentelemetry:opentelemetry-extension-aws")
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-resources")
compileOnly("io.opentelemetry:opentelemetry-exporter-logging")
compileOnly("io.opentelemetry:opentelemetry-exporter-jaeger")
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
Expand All @@ -41,6 +42,7 @@ dependencies {
testImplementation(project(":testing-common"))
testImplementation("io.opentelemetry:opentelemetry-sdk")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-resources")
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
testImplementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
testImplementation("io.opentelemetry:opentelemetry-extension-aws")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@
package io.opentelemetry.instrumentation.spring.autoconfigure;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

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

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

return tracerProviderBuilder
.setResource(otelResource)
.setSampler(Sampler.traceIdRatioBased(samplerProperties.getProbability()))
.build();
}

@Bean
@ConditionalOnMissingBean
public Resource otelResource(
Environment env, ObjectProvider<List<Supplier<Resource>>> resourceProviders) {
String applicationName = env.getProperty("spring.application.name");
Resource resource = defaultResource(applicationName);
List<Supplier<Resource>> resourceCustomizers =
resourceProviders.getIfAvailable(Collections::emptyList);
for (Supplier<Resource> resourceCustomizer : resourceCustomizers) {
resource = resource.merge(resourceCustomizer.get());
}
return resource;
}

private static Resource defaultResource(String applicationName) {
if (applicationName == null) {
return Resource.getDefault();
}
return Resource.getDefault()
.merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)));
}

@Bean
public OpenTelemetry openTelemetry(
ObjectProvider<ContextPropagators> propagatorsProvider, SdkTracerProvider tracerProvider) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.autoconfigure;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.sdk.extension.resources.ContainerResource;
import io.opentelemetry.sdk.extension.resources.HostResource;
import io.opentelemetry.sdk.extension.resources.OsResource;
import io.opentelemetry.sdk.extension.resources.ProcessResource;
import io.opentelemetry.sdk.extension.resources.ProcessRuntimeResource;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.util.Map;
import java.util.function.Supplier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
@EnableConfigurationProperties(OtelResourceProperties.class)
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@ConditionalOnProperty(prefix = "otel.springboot.resource", name = "enabled", matchIfMissing = true)
public class OtelResourceAutoConfiguration {

@Bean
public Supplier<Resource> otelResourceProvider(
Environment env, OtelResourceProperties otelResourceProperties) {
return () -> {
AttributesBuilder attributesBuilder = Attributes.builder();
for (Map.Entry<String, String> entry : otelResourceProperties.getAttributes().entrySet()) {
attributesBuilder.put(entry.getKey(), entry.getValue());
}
String otelServiceName = env.getProperty("otel.springboot.resource.attributes.service.name");
if (otelServiceName != null) {
attributesBuilder.put(ResourceAttributes.SERVICE_NAME, otelServiceName);
}
Attributes attributes = attributesBuilder.build();
return Resource.create(attributes);
};
}

@Bean
@ConditionalOnClass(OsResource.class)
public Supplier<Resource> otelOsResourceProvider() {
return OsResource::get;
}

@Bean
@ConditionalOnClass(ProcessResource.class)
public Supplier<Resource> otelProcessResourceProvider() {
return ProcessResource::get;
}

@Bean
@ConditionalOnClass(ProcessRuntimeResource.class)
public Supplier<Resource> otelProcessRuntimeResourceProvider() {
return ProcessRuntimeResource::get;
}

@Bean
@ConditionalOnClass(HostResource.class)
public Supplier<Resource> otelHostResourceProvider() {
return HostResource::get;
}

@Bean
@ConditionalOnClass(ContainerResource.class)
public Supplier<Resource> otelContainerResource() {
return ContainerResource::get;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.autoconfigure;

import java.util.Collections;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "otel.springboot.resource")
public class OtelResourceProperties {
private Map<String, String> attributes = Collections.emptyMap();

public Map<String, String> getAttributes() {
return attributes;
}

public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura
io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.webmvc.WebMvcFilterAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.aspects.TraceAspectAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.aspects.TraceAspectAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.OtelResourceAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

package io.opentelemetry.instrumentation.spring.autoconfigure;

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

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -76,4 +78,33 @@ void initializeOpenTelemetry() {
.hasBean("customTracerProvider")
.doesNotHaveBean("sdkTracerProvider"));
}

@Test
@DisplayName(
"when spring.application.name is set value should be passed to service name attribute")
void shouldDetermineServiceNameBySpringApplicationName() {
this.contextRunner
.withPropertyValues("spring.application.name=myapp-backend")
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
.run(
context -> {
Resource otelResource = context.getBean("otelResource", Resource.class);

assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("myapp-backend");
});
}

@Test
@DisplayName(
"when spring application name and otel service name are not set service name should be default")
void hasDefaultServiceName() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
.run(
context -> {
Resource otelResource = context.getBean("otelResource", Resource.class);

assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("unknown_service:java");
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.autoconfigure;

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

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.sdk.resources.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

public class OtelResourceAutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class));

@AfterEach
void tearDown() {
GlobalOpenTelemetry.resetForTest();
}

@Test
@DisplayName("when otel service name is set it should be set as service name attribute")
void shouldDetermineServiceNameByOtelServiceName() {
this.contextRunner
.withPropertyValues(
"otel.springboot.resource.attributes.service.name=otel-name-backend",
"otel.springboot.resource.enabled=true")
.run(
context -> {
Resource otelResource = context.getBean("otelResource", Resource.class);

assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("otel-name-backend");
});
}

@Test
@DisplayName(
"when otel.springboot.resource.enabled is not specified configuration should be initialized")
void shouldInitAutoConfigurationByDefault() {
this.contextRunner
.withPropertyValues("otel.springboot.resource.attributes.service.name=otel-name-backend")
.run(
context -> {
Resource otelResource = context.getBean("otelResource", Resource.class);

assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("otel-name-backend");
});
}

@Test
@DisplayName(
"when otel.springboot.resource.enabled is set to false configuration should NOT be initialized")
void shouldNotInitAutoConfiguration() {
this.contextRunner
.withPropertyValues(
"otel.springboot.resource.attributes.service.name=otel-name-backend",
"otel.springboot.resource.enabled=false")
.run(context -> assertThat(context.containsBean("otelResourceProvider")).isFalse());
}

@Test
@DisplayName("when otel attributes are set in properties they should be put in resource")
void shouldInitializeAttributes() {
this.contextRunner
.withPropertyValues(
"otel.springboot.resource.attributes.xyz=foo",
"otel.springboot.resource.attributes.environment=dev",
"otel.springboot.resource.enabled=true")
.run(
context -> {
Resource otelResource = context.getBean("otelResource", Resource.class);

assertThat(otelResource.getAttribute(AttributeKey.stringKey("environment")))
.isEqualTo("dev");
assertThat(otelResource.getAttribute(AttributeKey.stringKey("xyz"))).isEqualTo("foo");
});
}
}
Loading

0 comments on commit db474af

Please sign in to comment.