From cf2353bdb3345249d15033f4a30b17c4376498ed Mon Sep 17 00:00:00 2001 From: Vanio Begic Date: Sun, 2 Mar 2025 16:53:31 +0100 Subject: [PATCH 1/2] Add support for OpenTelemetry's service.namespace See gh-44499 Signed-off-by: Vanio Begic --- .../OpenTelemetryResourceAttributes.java | 8 ++++ .../OpenTelemetryAutoConfigurationTests.java | 16 +++++++ .../OpenTelemetryResourceAttributesTests.java | 48 +++++++++++++++++-- 3 files changed, 67 insertions(+), 5 deletions(-) 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..b468c18f45 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 @@ -99,6 +99,7 @@ public final class OpenTelemetryResourceAttributes { }); attributes.computeIfAbsent("service.name", (k) -> getApplicationName()); attributes.computeIfAbsent("service.group", (k) -> getApplicationGroup()); + attributes.computeIfAbsent("service.namespace", (key) -> getServiceNamespace()); attributes.forEach(consumer); } @@ -106,11 +107,18 @@ public final class OpenTelemetryResourceAttributes { return this.environment.getProperty("spring.application.name", DEFAULT_SERVICE_NAME); } + @Deprecated(since = "3.5.0", forRemoval = true) + // See https://github.com/spring-projects/spring-boot/issues/44411 for potential + // information about deprecation of "service.group" attribute 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..dba908b681 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,22 @@ 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) -> { 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..f19f74f82e 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,50 @@ 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() { From 7fd29cf7c66f5c2bfb1e25012ece6c809c3541c5 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 6 Mar 2025 09:52:13 +0100 Subject: [PATCH 2/2] Polish "Add support for OpenTelemetry's service.namespace" See gh-44499 --- .../OpenTelemetryResourceAttributes.java | 13 ++++++++----- .../OpenTelemetryAutoConfigurationTests.java | 9 +++++---- .../OpenTelemetryResourceAttributesTests.java | 1 - 3 files changed, 13 insertions(+), 10 deletions(-) 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 b468c18f45..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,8 @@ 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); } @@ -107,9 +107,12 @@ 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) - // See https://github.com/spring-projects/spring-boot/issues/44411 for potential - // information about deprecation of "service.group" attribute private String getApplicationGroup() { String applicationGroup = this.environment.getProperty("spring.application.group"); return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null; 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 dba908b681..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 @@ -110,13 +110,14 @@ class OpenTelemetryAutoConfigurationTests { 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"); + assertThat(resource.getAttributes().asMap()).containsEntry(AttributeKey.stringKey("service.namespace"), + "my-group"); }); } @Test - void shouldNOtApplyServiceNamespaceIfApplicationGroupIsNotSet() { - this.runner.run((context -> { + void shouldNotApplyServiceNamespaceIfApplicationGroupIsNotSet() { + this.runner.run(((context) -> { Resource resource = context.getBean(Resource.class); assertThat(resource.getAttributes().asMap()).doesNotContainKey(AttributeKey.stringKey("service.namespace")); })); @@ -172,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 f19f74f82e..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 @@ -227,7 +227,6 @@ class OpenTelemetryResourceAttributesTests { 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");