-
Notifications
You must be signed in to change notification settings - Fork 881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spring Boot Starter service-name is constant #5359
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* 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 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; | ||
|
||
@Configuration | ||
@EnableConfigurationProperties(OtelResourceProperties.class) | ||
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class) | ||
@ConditionalOnProperty(prefix = "otel.springboot.resource", name = "enabled", matchIfMissing = true) | ||
public class OtelResourceAutoConfiguration { | ||
|
||
@Bean | ||
public Supplier<Resource> otelResourceProvider(OtelResourceProperties otelResourceProperties) { | ||
return () -> { | ||
AttributesBuilder attributesBuilder = Attributes.builder(); | ||
for (Map.Entry<String, String> entry : otelResourceProperties.getAttributes().entrySet()) { | ||
attributesBuilder.put(entry.getKey(), entry.getValue()); | ||
} | ||
Attributes attributes = attributesBuilder.build(); | ||
return Resource.create(attributes); | ||
}; | ||
} | ||
|
||
@Bean | ||
@ConditionalOnClass(OsResource.class) | ||
public Supplier<Resource> otelOsResourceProvider() { | ||
return OsResource::get; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you think of loading the resources via SPI? https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider or are there advantages to having them as separate named beans? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, I dont deeply understand how SPI works. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to load resource objects via spring.factories and it seems spring does not load bean by interface or I did it in wrong manner.
I added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about registering the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you explain a bit more, please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, instead of all the @Bean
@ConditionalOnClass(OsResource.class)
public ResourceProvider osResourceProvider() {
return new OsResourceProvider();
}
@Bean
@ConditionalOnClass(ProcessResource.class)
public ResourceProvider processResourceProvider() {
return new ProcessResourceProvider();
}
// ... Then, in the @Bean
@ConditionalOnMissingBean
public Resource otelResource(
Environment env, ObjectProvider<List<ResourceProvider>> resourceProviders) {
String applicationName = env.getProperty("spring.application.name");
Resource resource = defaultResource(applicationName);
ConfigProperties config = SpringConfigProperties(env);
for (ResourceProvider provider : resourceProviders.getIfAvailable(Collections::emptyList)) {
resource = resource.merge(provider.createResource(config));
}
return resource;
}
This way you could reuse the SDK WDYT about that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it is a great idea to reuse existed interface for getting all available resources. If I understand correctly I should implement something like SpringResourceProvider and encapsulate there logic of determination of application name and attributes. I will add these changes in second commit in this PR in a day or two. So you will be able to evaluate changes and choose more appropriate implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
👍 Thanks! |
||
|
||
@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") | ||
mateuszrzeszutek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* 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.common.AttributeKey; | ||
import io.opentelemetry.sdk.resources.Resource; | ||
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)); | ||
|
||
@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"); | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice 👍