Merge pull request #44494 from nosan
* pr/44494: Polish "Refine the handling of OpenTelemetry resource attributes" Refine the handling of OpenTelemetry resource attributes Closes gh-44494
This commit is contained in:
		
						commit
						362cfb4f8b
					
				| 
						 | 
				
			
			@ -17,6 +17,7 @@
 | 
			
		|||
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,6 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.
 | 
			
		|||
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}.
 | 
			
		||||
| 
						 | 
				
			
			@ -40,11 +40,6 @@ import org.springframework.util.StringUtils;
 | 
			
		|||
class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<OtlpMetricsProperties>
 | 
			
		||||
		implements OtlpConfig {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Default value for application name if {@code spring.application.name} is not set.
 | 
			
		||||
	 */
 | 
			
		||||
	private static final String DEFAULT_APPLICATION_NAME = "unknown_service";
 | 
			
		||||
 | 
			
		||||
	private final OpenTelemetryProperties openTelemetryProperties;
 | 
			
		||||
 | 
			
		||||
	private final OtlpMetricsConnectionDetails connectionDetails;
 | 
			
		||||
| 
						 | 
				
			
			@ -77,21 +72,10 @@ class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAda
 | 
			
		|||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Map<String, String> resourceAttributes() {
 | 
			
		||||
		Map<String, String> attributes = new OpenTelemetryResourceAttributes(
 | 
			
		||||
				this.openTelemetryProperties.getResourceAttributes())
 | 
			
		||||
			.asMap();
 | 
			
		||||
		attributes.computeIfAbsent("service.name", (key) -> getApplicationName());
 | 
			
		||||
		attributes.computeIfAbsent("service.group", (key) -> getApplicationGroup());
 | 
			
		||||
		return Collections.unmodifiableMap(attributes);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String getApplicationName() {
 | 
			
		||||
		return this.environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String getApplicationGroup() {
 | 
			
		||||
		String applicationGroup = this.environment.getProperty("spring.application.group");
 | 
			
		||||
		return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null;
 | 
			
		||||
		Map<String, String> resourceAttributes = new LinkedHashMap<>();
 | 
			
		||||
		new OpenTelemetryResourceAttributes(this.environment, this.openTelemetryProperties.getResourceAttributes())
 | 
			
		||||
			.applyTo(resourceAttributes::put);
 | 
			
		||||
		return Collections.unmodifiableMap(resourceAttributes);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,6 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.boot.actuate.autoconfigure.opentelemetry;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import io.opentelemetry.api.OpenTelemetry;
 | 
			
		||||
import io.opentelemetry.context.propagation.ContextPropagators;
 | 
			
		||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +34,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
 | 
			
		|||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -49,11 +46,6 @@ import org.springframework.util.StringUtils;
 | 
			
		|||
@EnableConfigurationProperties(OpenTelemetryProperties.class)
 | 
			
		||||
public class OpenTelemetryAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Default value for application name if {@code spring.application.name} is not set.
 | 
			
		||||
	 */
 | 
			
		||||
	private static final String DEFAULT_APPLICATION_NAME = "unknown_service";
 | 
			
		||||
 | 
			
		||||
	@Bean
 | 
			
		||||
	@ConditionalOnMissingBean(OpenTelemetry.class)
 | 
			
		||||
	OpenTelemetrySdk openTelemetry(ObjectProvider<SdkTracerProvider> tracerProvider,
 | 
			
		||||
| 
						 | 
				
			
			@ -76,21 +68,8 @@ public class OpenTelemetryAutoConfiguration {
 | 
			
		|||
 | 
			
		||||
	private Resource toResource(Environment environment, OpenTelemetryProperties properties) {
 | 
			
		||||
		ResourceBuilder builder = Resource.builder();
 | 
			
		||||
		Map<String, String> attributes = new OpenTelemetryResourceAttributes(properties.getResourceAttributes())
 | 
			
		||||
			.asMap();
 | 
			
		||||
		attributes.computeIfAbsent("service.name", (key) -> getApplicationName(environment));
 | 
			
		||||
		attributes.computeIfAbsent("service.group", (key) -> getApplicationGroup(environment));
 | 
			
		||||
		attributes.forEach(builder::put);
 | 
			
		||||
		new OpenTelemetryResourceAttributes(environment, properties.getResourceAttributes()).applyTo(builder::put);
 | 
			
		||||
		return builder.build();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String getApplicationName(Environment environment) {
 | 
			
		||||
		return environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String getApplicationGroup(Environment environment) {
 | 
			
		||||
		String applicationGroup = environment.getProperty("spring.application.group");
 | 
			
		||||
		return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,16 +21,19 @@ import java.nio.charset.StandardCharsets;
 | 
			
		|||
import java.util.Collections;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.BiConsumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
import org.springframework.core.env.Environment;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
import org.springframework.util.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * OpenTelemetryResourceAttributes retrieves information from the
 | 
			
		||||
 * {@link OpenTelemetryResourceAttributes} retrieves information from the
 | 
			
		||||
 * {@code OTEL_RESOURCE_ATTRIBUTES} and {@code OTEL_SERVICE_NAME} environment variables
 | 
			
		||||
 * and merges it with the resource attributes provided by the user.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <b>User-provided resource attributes take precedence.</b>
 | 
			
		||||
 * and merges it with the resource attributes provided by the user. User-provided resource
 | 
			
		||||
 * attributes take precedence. Additionally, {@code spring.application.*} related
 | 
			
		||||
 * properties can be applied as defaults.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <a href= "https://opentelemetry.io/docs/specs/otel/resource/sdk/">OpenTelemetry
 | 
			
		||||
 * Resource Specification</a>
 | 
			
		||||
| 
						 | 
				
			
			@ -40,47 +43,72 @@ import org.springframework.util.StringUtils;
 | 
			
		|||
 */
 | 
			
		||||
public final class OpenTelemetryResourceAttributes {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Default value for service name if {@code service.name} is not set.
 | 
			
		||||
	 */
 | 
			
		||||
	private static final String DEFAULT_SERVICE_NAME = "unknown_service";
 | 
			
		||||
 | 
			
		||||
	private final Environment environment;
 | 
			
		||||
 | 
			
		||||
	private final Map<String, String> resourceAttributes;
 | 
			
		||||
 | 
			
		||||
	private final Function<String, String> getEnv;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Creates a new instance of {@link OpenTelemetryResourceAttributes}.
 | 
			
		||||
	 * @param environment the environment
 | 
			
		||||
	 * @param resourceAttributes user provided resource attributes to be used
 | 
			
		||||
	 */
 | 
			
		||||
	public OpenTelemetryResourceAttributes(Map<String, String> resourceAttributes) {
 | 
			
		||||
		this(resourceAttributes, null);
 | 
			
		||||
	public OpenTelemetryResourceAttributes(Environment environment, Map<String, String> resourceAttributes) {
 | 
			
		||||
		this(environment, resourceAttributes, null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Creates a new {@link OpenTelemetryResourceAttributes} instance.
 | 
			
		||||
	 * @param environment the environment
 | 
			
		||||
	 * @param resourceAttributes user provided resource attributes to be used
 | 
			
		||||
	 * @param getEnv a function to retrieve environment variables by name
 | 
			
		||||
	 */
 | 
			
		||||
	OpenTelemetryResourceAttributes(Map<String, String> resourceAttributes, Function<String, String> getEnv) {
 | 
			
		||||
	OpenTelemetryResourceAttributes(Environment environment, Map<String, String> resourceAttributes,
 | 
			
		||||
			Function<String, String> getEnv) {
 | 
			
		||||
		Assert.notNull(environment, "'environment' must not be null");
 | 
			
		||||
		this.environment = environment;
 | 
			
		||||
		this.resourceAttributes = (resourceAttributes != null) ? resourceAttributes : Collections.emptyMap();
 | 
			
		||||
		this.getEnv = (getEnv != null) ? getEnv : System::getenv;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns resource attributes by combining attributes from environment variables and
 | 
			
		||||
	 * user-defined resource attributes. The final resource contains all attributes from
 | 
			
		||||
	 * both sources.
 | 
			
		||||
	 * Applies resource attributes to the provided BiConsumer after being combined from
 | 
			
		||||
	 * environment variables and user-defined resource attributes.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * If a key exists in both environment variables and user-defined resources, the value
 | 
			
		||||
	 * from the user-defined resource takes precedence, even if it is empty.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * <b>Null keys and values are ignored.</b>
 | 
			
		||||
	 * @return the resource attributes
 | 
			
		||||
	 * 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}.
 | 
			
		||||
	 * @param consumer the {@link BiConsumer} to apply
 | 
			
		||||
	 */
 | 
			
		||||
	public Map<String, String> asMap() {
 | 
			
		||||
	public void applyTo(BiConsumer<String, String> consumer) {
 | 
			
		||||
		Assert.notNull(consumer, "'consumer' must not be null");
 | 
			
		||||
		Map<String, String> attributes = getResourceAttributesFromEnv();
 | 
			
		||||
		this.resourceAttributes.forEach((name, value) -> {
 | 
			
		||||
			if (name != null && value != null) {
 | 
			
		||||
			if (StringUtils.hasLength(name) && value != null) {
 | 
			
		||||
				attributes.put(name, value);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		return attributes;
 | 
			
		||||
		attributes.computeIfAbsent("service.name", (k) -> getApplicationName());
 | 
			
		||||
		attributes.computeIfAbsent("service.group", (k) -> getApplicationGroup());
 | 
			
		||||
		attributes.forEach(consumer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String getApplicationName() {
 | 
			
		||||
		return this.environment.getProperty("spring.application.name", DEFAULT_SERVICE_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String getApplicationGroup() {
 | 
			
		||||
		String applicationGroup = this.environment.getProperty("spring.application.group");
 | 
			
		||||
		return (StringUtils.hasLength(applicationGroup)) ? applicationGroup : null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +150,7 @@ public final class OpenTelemetryResourceAttributes {
 | 
			
		|||
	 * @param value value to decode
 | 
			
		||||
	 * @return the decoded string
 | 
			
		||||
	 */
 | 
			
		||||
	public static String decode(String value) {
 | 
			
		||||
	private static String decode(String value) {
 | 
			
		||||
		if (value.indexOf('%') < 0) {
 | 
			
		||||
			return value;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,8 @@ import org.assertj.core.api.InstanceOfAssertFactories;
 | 
			
		|||
import org.junit.jupiter.api.BeforeAll;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
 | 
			
		||||
import org.springframework.mock.env.MockEnvironment;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +43,8 @@ class OpenTelemetryResourceAttributesTests {
 | 
			
		|||
 | 
			
		||||
	private static final PercentEscaper escaper = PercentEscaper.create();
 | 
			
		||||
 | 
			
		||||
	private final MockEnvironment environment = new MockEnvironment();
 | 
			
		||||
 | 
			
		||||
	private final Map<String, String> environmentVariables = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
	private final Map<String, String> resourceAttributes = new LinkedHashMap<>();
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +52,7 @@ class OpenTelemetryResourceAttributesTests {
 | 
			
		|||
	@BeforeAll
 | 
			
		||||
	static void beforeAll() {
 | 
			
		||||
		long seed = new Random().nextLong();
 | 
			
		||||
		System.out.println("Seed: " + seed);
 | 
			
		||||
		System.out.println(OpenTelemetryResourceAttributesTests.class.getSimpleName() + " seed: " + seed);
 | 
			
		||||
		random = new Random(seed);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,40 +60,37 @@ class OpenTelemetryResourceAttributesTests {
 | 
			
		|||
	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");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "otel-service");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	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", "");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void otelResourceAttributesShouldBeUsed() {
 | 
			
		||||
	void otelResourceAttributes() {
 | 
			
		||||
		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)
 | 
			
		||||
		assertThat(getAttributes()).hasSize(7)
 | 
			
		||||
			.containsEntry("key1", "value1")
 | 
			
		||||
			.containsEntry("key2", "value2")
 | 
			
		||||
			.containsEntry("key3", "value3")
 | 
			
		||||
			.containsEntry("key4", "")
 | 
			
		||||
			.containsEntry("key7", "spring+boot")
 | 
			
		||||
			.containsEntry("key8", "ś");
 | 
			
		||||
			.containsEntry("key8", "ś")
 | 
			
		||||
			.containsEntry("service.name", "unknown_service");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void resourceAttributesShouldBeMergedWithEnvironmentVariables() {
 | 
			
		||||
	void resourceAttributesShouldBeMergedWithEnvironmentVariablesAndTakePrecedence() {
 | 
			
		||||
		this.resourceAttributes.put("service.group", "custom-group");
 | 
			
		||||
		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)
 | 
			
		||||
		assertThat(getAttributes()).hasSize(4)
 | 
			
		||||
			.containsEntry("service.name", "custom-service")
 | 
			
		||||
			.containsEntry("service.group", "custom-group")
 | 
			
		||||
			.containsEntry("key1", "value1")
 | 
			
		||||
| 
						 | 
				
			
			@ -97,27 +98,20 @@ class OpenTelemetryResourceAttributesTests {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void resourceAttributesWithNullKeyOrValueShouldBeIgnored() {
 | 
			
		||||
		this.resourceAttributes.put("service.group", null);
 | 
			
		||||
		this.resourceAttributes.put("service.name", null);
 | 
			
		||||
		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")
 | 
			
		||||
			.containsEntry("key1", "value1")
 | 
			
		||||
			.containsEntry("key2", "value2");
 | 
			
		||||
	void invalidResourceAttributesShouldBeIgnored() {
 | 
			
		||||
		this.resourceAttributes.put("", "empty-key");
 | 
			
		||||
		this.resourceAttributes.put(null, "null-key");
 | 
			
		||||
		this.resourceAttributes.put("null-value", null);
 | 
			
		||||
		this.resourceAttributes.put("empty-value", "");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(2)
 | 
			
		||||
			.containsEntry("service.name", "unknown_service")
 | 
			
		||||
			.containsEntry("empty-value", "");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	@SuppressWarnings("unchecked")
 | 
			
		||||
	void systemGetEnvShouldBeUsedAsDefaultEnvFunctionAndResourceAttributesAreEmpty() {
 | 
			
		||||
		OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes(null);
 | 
			
		||||
		assertThat(attributes).extracting("resourceAttributes")
 | 
			
		||||
			.asInstanceOf(InstanceOfAssertFactories.MAP)
 | 
			
		||||
			.isNotNull()
 | 
			
		||||
			.isEmpty();
 | 
			
		||||
	void systemGetEnvShouldBeUsedAsDefaultEnvFunction() {
 | 
			
		||||
		OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes(this.environment, null);
 | 
			
		||||
		Function<String, String> getEnv = assertThat(attributes).extracting("getEnv")
 | 
			
		||||
			.asInstanceOf(InstanceOfAssertFactories.type(Function.class))
 | 
			
		||||
			.actual();
 | 
			
		||||
| 
						 | 
				
			
			@ -125,38 +119,98 @@ class OpenTelemetryResourceAttributesTests {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void shouldDecodeOtelResourceAttributeValues() {
 | 
			
		||||
	void otelResourceAttributeValuesShouldBePercentDecoded() {
 | 
			
		||||
		Stream.generate(this::generateRandomString).limit(10000).forEach((value) -> {
 | 
			
		||||
			String key = "key";
 | 
			
		||||
			this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", key + "=" + escaper.escape(value));
 | 
			
		||||
			OpenTelemetryResourceAttributes attributes = getAttributes();
 | 
			
		||||
			assertThat(attributes.asMap()).hasSize(1).containsEntry(key, value);
 | 
			
		||||
			this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=" + escaper.escape(value));
 | 
			
		||||
			assertThat(getAttributes()).hasSize(2)
 | 
			
		||||
				.containsEntry("service.name", "unknown_service")
 | 
			
		||||
				.containsEntry("key", value);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void shouldThrowIllegalArgumentExceptionWhenDecodingPercentIllegalHexChar() {
 | 
			
		||||
	void illegalArgumentExceptionShouldBeThrownWhenDecodingIllegalHexCharPercentEncodedValue() {
 | 
			
		||||
		this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=abc%ß");
 | 
			
		||||
		assertThatIllegalArgumentException().isThrownBy(() -> getAttributes().asMap())
 | 
			
		||||
		assertThatIllegalArgumentException().isThrownBy(this::getAttributes)
 | 
			
		||||
			.withMessage("Failed to decode percent-encoded characters at index 3 in the value: 'abc%ß'");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void shouldUseReplacementCharWhenDecodingNonUtf8Character() {
 | 
			
		||||
	void replacementCharShouldBeUsedWhenDecodingNonUtf8Character() {
 | 
			
		||||
		this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=%a3%3e");
 | 
			
		||||
		OpenTelemetryResourceAttributes attributes = getAttributes();
 | 
			
		||||
		assertThat(attributes.asMap()).containsEntry("key", "\ufffd>");
 | 
			
		||||
		assertThat(getAttributes()).containsEntry("key", "\ufffd>");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void shouldThrowIllegalArgumentExceptionWhenDecodingPercent() {
 | 
			
		||||
	void illegalArgumentExceptionShouldBeThrownWhenDecodingInvalidPercentEncodedValue() {
 | 
			
		||||
		this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=%");
 | 
			
		||||
		assertThatIllegalArgumentException().isThrownBy(() -> getAttributes().asMap())
 | 
			
		||||
		assertThatIllegalArgumentException().isThrownBy(this::getAttributes)
 | 
			
		||||
			.withMessage("Failed to decode percent-encoded characters at index 0 in the value: '%'");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private OpenTelemetryResourceAttributes getAttributes() {
 | 
			
		||||
		return new OpenTelemetryResourceAttributes(this.resourceAttributes, this.environmentVariables::get);
 | 
			
		||||
	@Test
 | 
			
		||||
	void unknownServiceShouldBeUsedAsDefaultServiceName() {
 | 
			
		||||
		assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "unknown_service");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void springApplicationGroupNameShouldBeUsedAsDefaultServiceGroup() {
 | 
			
		||||
		this.environment.setProperty("spring.application.group", "spring-boot");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(2)
 | 
			
		||||
			.containsEntry("service.name", "unknown_service")
 | 
			
		||||
			.containsEntry("service.group", "spring-boot");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void springApplicationNameShouldBeUsedAsDefaultServiceName() {
 | 
			
		||||
		this.environment.setProperty("spring.application.name", "spring-boot-app");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "spring-boot-app");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void resourceAttributesShouldTakePrecedenceOverSpringApplicationName() {
 | 
			
		||||
		this.resourceAttributes.put("service.name", "spring-boot");
 | 
			
		||||
		this.environment.setProperty("spring.application.name", "spring-boot-app");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "spring-boot");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void otelResourceAttributesShouldTakePrecedenceOverSpringApplicationName() {
 | 
			
		||||
		this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "service.name=spring-boot");
 | 
			
		||||
		this.environment.setProperty("spring.application.name", "spring-boot-app");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "spring-boot");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void otelServiceNameShouldTakePrecedenceOverSpringApplicationName() {
 | 
			
		||||
		this.environmentVariables.put("OTEL_SERVICE_NAME", "spring-boot");
 | 
			
		||||
		this.environment.setProperty("spring.application.name", "spring-boot-app");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(1).containsEntry("service.name", "spring-boot");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void resourceAttributesShouldTakePrecedenceOverSpringApplicationGroupName() {
 | 
			
		||||
		this.resourceAttributes.put("service.group", "spring-boot-app");
 | 
			
		||||
		this.environment.setProperty("spring.application.group", "spring-boot");
 | 
			
		||||
		assertThat(getAttributes()).hasSize(2)
 | 
			
		||||
			.containsEntry("service.name", "unknown_service")
 | 
			
		||||
			.containsEntry("service.group", "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)
 | 
			
		||||
			.containsEntry("service.name", "unknown_service")
 | 
			
		||||
			.containsEntry("service.group", "spring-boot");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private Map<String, String> getAttributes() {
 | 
			
		||||
		Map<String, String> attributes = new LinkedHashMap<>();
 | 
			
		||||
		new OpenTelemetryResourceAttributes(this.environment, this.resourceAttributes, this.environmentVariables::get)
 | 
			
		||||
			.applyTo(attributes::put);
 | 
			
		||||
		return attributes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String generateRandomString() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue