diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java index 52c66a4485..c28a4446cb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java @@ -86,7 +86,7 @@ public final class OpenTelemetryResourceAttributes { *

* Additionally, {@code spring.application.name} or {@code unknown_service} will be * used as the default for {@code service.name}, and {@code spring.application.group} - * will serve as the default for {@code service.group}. + * will serve as the default for {@code service.group} and {@code service.namespace}. * @param consumer the {@link BiConsumer} to apply */ public void applyTo(BiConsumer consumer) { @@ -97,8 +97,9 @@ public final class OpenTelemetryResourceAttributes { attributes.put(name, value); } }); - attributes.computeIfAbsent("service.name", (k) -> getApplicationName()); - attributes.computeIfAbsent("service.group", (k) -> getApplicationGroup()); + attributes.computeIfAbsent("service.name", (key) -> getApplicationName()); + attributes.computeIfAbsent("service.group", (key) -> getApplicationGroup()); + attributes.computeIfAbsent("service.namespace", (key) -> getServiceNamespace()); attributes.forEach(consumer); } @@ -106,11 +107,21 @@ public final class OpenTelemetryResourceAttributes { return this.environment.getProperty("spring.application.name", DEFAULT_SERVICE_NAME); } + /** + * Returns the application group. + * @return the application group + * @deprecated since 3.5.0 for removal in 3.7.0 + */ + @Deprecated(since = "3.5.0", forRemoval = true) private String getApplicationGroup() { String applicationGroup = this.environment.getProperty("spring.application.group"); return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null; } + private String getServiceNamespace() { + return this.environment.getProperty("spring.application.group"); + } + /** * Parses resource attributes from the {@link System#getenv()}. This method fetches * attributes defined in the {@code OTEL_RESOURCE_ATTRIBUTES} and diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java index 14ab7b3c63..34492b1fee 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfigurationTests.java @@ -106,6 +106,23 @@ class OpenTelemetryAutoConfigurationTests { }); } + @Test + void shouldApplyServiceNamespaceIfApplicationGroupIsSet() { + this.runner.withPropertyValues("spring.application.group=my-group").run((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()).containsEntry(AttributeKey.stringKey("service.namespace"), + "my-group"); + }); + } + + @Test + void shouldNotApplyServiceNamespaceIfApplicationGroupIsNotSet() { + this.runner.run(((context) -> { + Resource resource = context.getBean(Resource.class); + assertThat(resource.getAttributes().asMap()).doesNotContainKey(AttributeKey.stringKey("service.namespace")); + })); + } + @Test void shouldFallbackToDefaultApplicationNameIfSpringApplicationNameIsNotSet() { this.runner.run((context) -> { @@ -156,7 +173,7 @@ class OpenTelemetryAutoConfigurationTests { } @Configuration(proxyBeanMethods = false) - private static final class UserConfiguration { + static class UserConfiguration { @Bean OpenTelemetry customOpenTelemetry() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributesTests.java index 61527264b9..6712744e91 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributesTests.java @@ -156,9 +156,10 @@ class OpenTelemetryResourceAttributesTests { @Test void springApplicationGroupNameShouldBeUsedAsDefaultServiceGroup() { this.environment.setProperty("spring.application.group", "spring-boot"); - assertThat(getAttributes()).hasSize(2) + assertThat(getAttributes()).hasSize(3) .containsEntry("service.name", "unknown_service") - .containsEntry("service.group", "spring-boot"); + .containsEntry("service.group", "spring-boot") + .containsEntry("service.namespace", "spring-boot"); } @Test @@ -167,6 +168,11 @@ class OpenTelemetryResourceAttributesTests { assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "spring-boot-app"); } + @Test + void serviceNamespaceShouldNotBePresentByDefault() { + assertThat(getAttributes()).hasSize(1).doesNotContainKey("service.namespace"); + } + @Test void resourceAttributesShouldTakePrecedenceOverSpringApplicationName() { this.resourceAttributes.put("service.name", "spring-boot"); @@ -192,18 +198,49 @@ class OpenTelemetryResourceAttributesTests { void resourceAttributesShouldTakePrecedenceOverSpringApplicationGroupName() { this.resourceAttributes.put("service.group", "spring-boot-app"); this.environment.setProperty("spring.application.group", "spring-boot"); - assertThat(getAttributes()).hasSize(2) + assertThat(getAttributes()).hasSize(3) .containsEntry("service.name", "unknown_service") .containsEntry("service.group", "spring-boot-app"); } + @Test + void resourceAttributesShouldTakePrecedenceOverApplicationGroupNameForPopulatingServiceNamespace() { + this.resourceAttributes.put("service.namespace", "spring-boot-app"); + this.environment.setProperty("spring.application.group", "overriden"); + assertThat(getAttributes()).hasSize(3) + .containsEntry("service.name", "unknown_service") + .containsEntry("service.group", "overriden") + .containsEntry("service.namespace", "spring-boot-app"); + } + @Test void otelResourceAttributesShouldTakePrecedenceOverSpringApplicationGroupName() { this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "service.group=spring-boot"); this.environment.setProperty("spring.application.group", "spring-boot-app"); - assertThat(getAttributes()).hasSize(2) + assertThat(getAttributes()).hasSize(3) .containsEntry("service.name", "unknown_service") - .containsEntry("service.group", "spring-boot"); + .containsEntry("service.group", "spring-boot") + .containsEntry("service.namespace", "spring-boot-app"); + } + + @Test + void otelResourceAttributesShouldTakePrecedenceOverSpringApplicationGroupNameForServiceNamespace() { + this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "service.namespace=spring-boot"); + this.environment.setProperty("spring.application.group", "overriden"); + assertThat(getAttributes()).hasSize(3) + .containsEntry("service.group", "overriden") + .containsEntry("service.namespace", "spring-boot"); + } + + @Test + void shouldUseServiceGroupForServiceNamespaceIfServiceGroupIsSet() { + this.environment.setProperty("spring.application.group", "alpha"); + assertThat(getAttributes()).containsEntry("service.namespace", "alpha"); + } + + @Test + void shouldNotSetServiceNamespaceIfServiceGroupIsNotSet() { + assertThat(getAttributes()).doesNotContainKey("service.namespace"); } private Map getAttributes() {