From 8f4e0518f320b9dc02a68bdde357380d453c5ee4 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 28 Feb 2025 14:35:12 +0100 Subject: [PATCH] Polish "Add support for OTel-specific environment variables" See gh-44394 --- .../OtlpMetricsPropertiesConfigAdapter.java | 7 ++++-- .../OpenTelemetryAutoConfiguration.java | 4 +++- .../OpenTelemetryResourceAttributes.java | 24 +++++++++---------- .../OpenTelemetryResourceAttributesTests.java | 15 +++++++----- .../pages/actuator/observability.adoc | 7 ++++-- 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java index 1f5a0d07c9a7..dbbcd6efe244 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; +import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -27,6 +28,7 @@ import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties; import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryResourceAttributes; import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; /** * Adapter to convert {@link OtlpMetricsProperties} to an {@link OtlpConfig}. @@ -80,7 +82,7 @@ public Map resourceAttributes() { .asMap(); attributes.computeIfAbsent("service.name", (key) -> getApplicationName()); attributes.computeIfAbsent("service.group", (key) -> getApplicationGroup()); - return attributes; + return Collections.unmodifiableMap(attributes); } private String getApplicationName() { @@ -88,7 +90,8 @@ private String getApplicationName() { } private String getApplicationGroup() { - return this.environment.getProperty("spring.application.group"); + String applicationGroup = this.environment.getProperty("spring.application.group"); + return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null; } @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java index 92e66ba61a8c..173b76ca83bb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry. @@ -88,7 +89,8 @@ private String getApplicationName(Environment environment) { } private String getApplicationGroup(Environment environment) { - return environment.getProperty("spring.application.group"); + String applicationGroup = environment.getProperty("spring.application.group"); + return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null; } } 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 fa72bc6be1f9..d6ba172e0dcc 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 @@ -130,21 +130,21 @@ public static String decode(String value) { ByteArrayOutputStream bos = new ByteArrayOutputStream(bytes.length); for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; - if (b == '%') { - int u = decodeHex(bytes, ++i); - int l = decodeHex(bytes, ++i); - if (u >= 0 && l >= 0) { - bos.write((u << 4) + l); - } - else { - throw new IllegalArgumentException( - "Failed to decode percent-encoded characters at index %d in the value: '%s'" - .formatted(i - 2, value)); - } + if (b != '%') { + bos.write(b); + continue; + } + int u = decodeHex(bytes, i + 1); + int l = decodeHex(bytes, i + 2); + if (u >= 0 && l >= 0) { + bos.write((u << 4) + l); } else { - bos.write(b); + throw new IllegalArgumentException( + "Failed to decode percent-encoded characters at index %d in the value: '%s'".formatted(i, + value)); } + i += 2; } return bos.toString(StandardCharsets.UTF_8); } 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 455ba39c603c..799ea7bc2605 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 @@ -24,6 +24,7 @@ import io.opentelemetry.api.internal.PercentEscaper; import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +37,7 @@ */ class OpenTelemetryResourceAttributesTests { - private static final Random random = new Random(); + private static Random random; private static final PercentEscaper escaper = PercentEscaper.create(); @@ -44,11 +45,17 @@ class OpenTelemetryResourceAttributesTests { private final Map resourceAttributes = new LinkedHashMap<>(); + @BeforeAll + static void beforeAll() { + long seed = new Random().nextLong(); + System.out.println("Seed: " + seed); + random = new Random(seed); + } + @Test void otelServiceNameShouldTakePrecedenceOverOtelResourceAttributes() { this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "service.name=ignored"); this.environmentVariables.put("OTEL_SERVICE_NAME", "otel-service"); - OpenTelemetryResourceAttributes attributes = getAttributes(); assertThat(attributes.asMap()).hasSize(1).containsEntry("service.name", "otel-service"); } @@ -57,7 +64,6 @@ void otelServiceNameShouldTakePrecedenceOverOtelResourceAttributes() { void otelServiceNameWhenEmptyShouldTakePrecedenceOverOtelResourceAttributes() { this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "service.name=ignored"); this.environmentVariables.put("OTEL_SERVICE_NAME", ""); - OpenTelemetryResourceAttributes attributes = getAttributes(); assertThat(attributes.asMap()).hasSize(1).containsEntry("service.name", ""); } @@ -66,7 +72,6 @@ void otelServiceNameWhenEmptyShouldTakePrecedenceOverOtelResourceAttributes() { void otelResourceAttributesShouldBeUsed() { this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", ", ,,key1=value1,key2= value2, key3=value3,key4=,=value5,key6,=,key7=spring+boot,key8=ś"); - OpenTelemetryResourceAttributes attributes = getAttributes(); assertThat(attributes.asMap()).hasSize(6) .containsEntry("key1", "value1") @@ -83,7 +88,6 @@ void resourceAttributesShouldBeMergedWithEnvironmentVariables() { this.resourceAttributes.put("key2", ""); this.environmentVariables.put("OTEL_SERVICE_NAME", "custom-service"); this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key1=value1,key2=value2"); - OpenTelemetryResourceAttributes attributes = getAttributes(); assertThat(attributes.asMap()).hasSize(4) .containsEntry("service.name", "custom-service") @@ -99,7 +103,6 @@ void resourceAttributesWithNullKeyOrValueShouldBeIgnored() { this.resourceAttributes.put(null, "value"); this.environmentVariables.put("OTEL_SERVICE_NAME", "custom-service"); this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key1=value1,key2=value2"); - OpenTelemetryResourceAttributes attributes = getAttributes(); assertThat(attributes.asMap()).hasSize(3) .containsEntry("service.name", "custom-service") diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc index 1896ce200f14..2a37108a344a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/observability.adoc @@ -95,7 +95,8 @@ Spring Boot's actuator module includes basic support for OpenTelemetry. It provides a bean of type javadoc:io.opentelemetry.api.OpenTelemetry[], and if there are beans of type javadoc:io.opentelemetry.sdk.trace.SdkTracerProvider[], javadoc:io.opentelemetry.context.propagation.ContextPropagators[], javadoc:io.opentelemetry.sdk.logs.SdkLoggerProvider[] or javadoc:io.opentelemetry.sdk.metrics.SdkMeterProvider[] in the application context, they automatically get registered. Additionally, it provides a javadoc:io.opentelemetry.sdk.resources.Resource[] bean. -The attributes of the auto-configured javadoc:io.opentelemetry.sdk.resources.Resource[] can be configured via the configprop:management.opentelemetry.resource-attributes[] configuration property. Auto-configured attributes will be merged with attributes from the `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME` environment variables, with attributes configured through the configuration property taking precedence over those from the environment variables. +The attributes of the auto-configured javadoc:io.opentelemetry.sdk.resources.Resource[] can be configured via the configprop:management.opentelemetry.resource-attributes[] configuration property. +Auto-configured attributes will be merged with attributes from the `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME` environment variables, with attributes configured through the configuration property taking precedence over those from the environment variables. If you have defined your own javadoc:io.opentelemetry.sdk.resources.Resource[] bean, this will no longer be the case. @@ -103,7 +104,9 @@ If you have defined your own javadoc:io.opentelemetry.sdk.resources.Resource[] b NOTE: Spring Boot does not provide auto-configuration for OpenTelemetry metrics or logging. OpenTelemetry tracing is only auto-configured when used together with xref:actuator/tracing.adoc[Micrometer Tracing]. -NOTE: The `OTEL_RESOURCE_ATTRIBUTES` environment variable consist of a list of key-value pairs. For example: `key1=value1,key2=value2,key3=spring%20boot`. All attribute values are treated as strings, and any characters outside the baggage-octet range must be **percent-encoded**. +NOTE: The `OTEL_RESOURCE_ATTRIBUTES` environment variable consist of a list of key-value pairs. +For example: `key1=value1,key2=value2,key3=spring%20boot`. +All attribute values are treated as strings, and any characters outside the baggage-octet range must be **percent-encoded**. The next sections will provide more details about logging, metrics and traces.