Merge pull request #44394 from nosan
* pr/44394: Polish "Add support for OTel-specific environment variables" Add support for OTel-specific environment variables Closes gh-44394
This commit is contained in:
commit
e886785f76
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,7 +17,6 @@
|
|||||||
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
|
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -27,8 +26,8 @@ import io.micrometer.registry.otlp.OtlpConfig;
|
|||||||
|
|
||||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
|
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
|
||||||
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties;
|
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.core.env.Environment;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,12 +77,12 @@ class OtlpMetricsPropertiesConfigAdapter extends StepRegistryPropertiesConfigAda
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> resourceAttributes() {
|
public Map<String, String> resourceAttributes() {
|
||||||
Map<String, String> resourceAttributes = this.openTelemetryProperties.getResourceAttributes();
|
Map<String, String> attributes = new OpenTelemetryResourceAttributes(
|
||||||
Map<String, String> result = new HashMap<>((!CollectionUtils.isEmpty(resourceAttributes)) ? resourceAttributes
|
this.openTelemetryProperties.getResourceAttributes())
|
||||||
: OtlpConfig.super.resourceAttributes());
|
.asMap();
|
||||||
result.computeIfAbsent("service.name", (key) -> getApplicationName());
|
attributes.computeIfAbsent("service.name", (key) -> getApplicationName());
|
||||||
result.computeIfAbsent("service.group", (key) -> getApplicationGroup());
|
attributes.computeIfAbsent("service.group", (key) -> getApplicationGroup());
|
||||||
return Collections.unmodifiableMap(result);
|
return Collections.unmodifiableMap(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getApplicationName() {
|
private String getApplicationName() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.opentelemetry;
|
package org.springframework.boot.actuate.autoconfigure.opentelemetry;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import io.opentelemetry.api.OpenTelemetry;
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
import io.opentelemetry.api.common.AttributeKey;
|
|
||||||
import io.opentelemetry.api.common.Attributes;
|
|
||||||
import io.opentelemetry.context.propagation.ContextPropagators;
|
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
|
import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
|
||||||
@ -54,10 +54,6 @@ public class OpenTelemetryAutoConfiguration {
|
|||||||
*/
|
*/
|
||||||
private static final String DEFAULT_APPLICATION_NAME = "unknown_service";
|
private static final String DEFAULT_APPLICATION_NAME = "unknown_service";
|
||||||
|
|
||||||
private static final AttributeKey<String> ATTRIBUTE_KEY_SERVICE_NAME = AttributeKey.stringKey("service.name");
|
|
||||||
|
|
||||||
private static final AttributeKey<String> ATTRIBUTE_KEY_SERVICE_GROUP = AttributeKey.stringKey("service.group");
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(OpenTelemetry.class)
|
@ConditionalOnMissingBean(OpenTelemetry.class)
|
||||||
OpenTelemetrySdk openTelemetry(ObjectProvider<SdkTracerProvider> tracerProvider,
|
OpenTelemetrySdk openTelemetry(ObjectProvider<SdkTracerProvider> tracerProvider,
|
||||||
@ -74,20 +70,27 @@ public class OpenTelemetryAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) {
|
Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) {
|
||||||
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
|
Resource resource = Resource.getDefault();
|
||||||
String applicationGroup = environment.getProperty("spring.application.group");
|
return resource.merge(toResource(environment, properties));
|
||||||
Resource resource = Resource.getDefault()
|
|
||||||
.merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_NAME, applicationName)));
|
|
||||||
if (StringUtils.hasLength(applicationGroup)) {
|
|
||||||
resource = resource.merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_GROUP, applicationGroup)));
|
|
||||||
}
|
|
||||||
return resource.merge(toResource(properties));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Resource toResource(OpenTelemetryProperties properties) {
|
private Resource toResource(Environment environment, OpenTelemetryProperties properties) {
|
||||||
ResourceBuilder builder = Resource.builder();
|
ResourceBuilder builder = Resource.builder();
|
||||||
properties.getResourceAttributes().forEach(builder::put);
|
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);
|
||||||
return builder.build();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.autoconfigure.opentelemetry;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>
|
||||||
|
* <a href= "https://opentelemetry.io/docs/specs/otel/resource/sdk/">OpenTelemetry
|
||||||
|
* Resource Specification</a>
|
||||||
|
*
|
||||||
|
* @author Dmytro Nosan
|
||||||
|
* @since 3.5.0
|
||||||
|
*/
|
||||||
|
public final class OpenTelemetryResourceAttributes {
|
||||||
|
|
||||||
|
private final Map<String, String> resourceAttributes;
|
||||||
|
|
||||||
|
private final Function<String, String> getEnv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@link OpenTelemetryResourceAttributes}.
|
||||||
|
* @param resourceAttributes user provided resource attributes to be used
|
||||||
|
*/
|
||||||
|
public OpenTelemetryResourceAttributes(Map<String, String> resourceAttributes) {
|
||||||
|
this(resourceAttributes, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link OpenTelemetryResourceAttributes} instance.
|
||||||
|
* @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) {
|
||||||
|
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.
|
||||||
|
* <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
|
||||||
|
*/
|
||||||
|
public Map<String, String> asMap() {
|
||||||
|
Map<String, String> attributes = getResourceAttributesFromEnv();
|
||||||
|
this.resourceAttributes.forEach((name, value) -> {
|
||||||
|
if (name != null && value != null) {
|
||||||
|
attributes.put(name, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses resource attributes from the {@link System#getenv()}. This method fetches
|
||||||
|
* attributes defined in the {@code OTEL_RESOURCE_ATTRIBUTES} and
|
||||||
|
* {@code OTEL_SERVICE_NAME} environment variables and provides them as key-value
|
||||||
|
* pairs.
|
||||||
|
* <p>
|
||||||
|
* If {@code service.name} is also provided in {@code OTEL_RESOURCE_ATTRIBUTES}, then
|
||||||
|
* {@code OTEL_SERVICE_NAME} takes precedence.
|
||||||
|
* @return resource attributes
|
||||||
|
*/
|
||||||
|
private Map<String, String> getResourceAttributesFromEnv() {
|
||||||
|
Map<String, String> attributes = new LinkedHashMap<>();
|
||||||
|
for (String attribute : StringUtils.tokenizeToStringArray(getEnv("OTEL_RESOURCE_ATTRIBUTES"), ",")) {
|
||||||
|
int index = attribute.indexOf('=');
|
||||||
|
if (index > 0) {
|
||||||
|
String key = attribute.substring(0, index);
|
||||||
|
String value = attribute.substring(index + 1);
|
||||||
|
attributes.put(key.trim(), decode(value.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String otelServiceName = getEnv("OTEL_SERVICE_NAME");
|
||||||
|
if (otelServiceName != null) {
|
||||||
|
attributes.put("service.name", otelServiceName);
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getEnv(String name) {
|
||||||
|
return this.getEnv.apply(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a percent-encoded string. Converts sequences like '%HH' (where HH
|
||||||
|
* represents hexadecimal digits) back into their literal representations.
|
||||||
|
* <p>
|
||||||
|
* Inspired by {@code org.apache.commons.codec.net.PercentCodec}.
|
||||||
|
* @param value value to decode
|
||||||
|
* @return the decoded string
|
||||||
|
*/
|
||||||
|
public static String decode(String value) {
|
||||||
|
if (value.indexOf('%') < 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(bytes.length);
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
byte b = bytes[i];
|
||||||
|
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 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int decodeHex(byte[] bytes, int index) {
|
||||||
|
return (index < bytes.length) ? Character.digit(bytes[index], 16) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.autoconfigure.opentelemetry;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link OpenTelemetryResourceAttributes}.
|
||||||
|
*
|
||||||
|
* @author Dmytro Nosan
|
||||||
|
*/
|
||||||
|
class OpenTelemetryResourceAttributesTests {
|
||||||
|
|
||||||
|
private static Random random;
|
||||||
|
|
||||||
|
private static final PercentEscaper escaper = PercentEscaper.create();
|
||||||
|
|
||||||
|
private final Map<String, String> environmentVariables = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, String> 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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")
|
||||||
|
.containsEntry("key2", "value2")
|
||||||
|
.containsEntry("key3", "value3")
|
||||||
|
.containsEntry("key4", "")
|
||||||
|
.containsEntry("key7", "spring+boot")
|
||||||
|
.containsEntry("key8", "ś");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resourceAttributesShouldBeMergedWithEnvironmentVariables() {
|
||||||
|
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)
|
||||||
|
.containsEntry("service.name", "custom-service")
|
||||||
|
.containsEntry("service.group", "custom-group")
|
||||||
|
.containsEntry("key1", "value1")
|
||||||
|
.containsEntry("key2", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void systemGetEnvShouldBeUsedAsDefaultEnvFunctionAndResourceAttributesAreEmpty() {
|
||||||
|
OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes(null);
|
||||||
|
assertThat(attributes).extracting("resourceAttributes")
|
||||||
|
.asInstanceOf(InstanceOfAssertFactories.MAP)
|
||||||
|
.isNotNull()
|
||||||
|
.isEmpty();
|
||||||
|
Function<String, String> getEnv = assertThat(attributes).extracting("getEnv")
|
||||||
|
.asInstanceOf(InstanceOfAssertFactories.type(Function.class))
|
||||||
|
.actual();
|
||||||
|
System.getenv().forEach((key, value) -> assertThat(getEnv.apply(key)).isEqualTo(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDecodeOtelResourceAttributeValues() {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowIllegalArgumentExceptionWhenDecodingPercentIllegalHexChar() {
|
||||||
|
this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=abc%ß");
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> getAttributes().asMap())
|
||||||
|
.withMessage("Failed to decode percent-encoded characters at index 3 in the value: 'abc%ß'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseReplacementCharWhenDecodingNonUtf8Character() {
|
||||||
|
this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=%a3%3e");
|
||||||
|
OpenTelemetryResourceAttributes attributes = getAttributes();
|
||||||
|
assertThat(attributes.asMap()).containsEntry("key", "\ufffd>");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowIllegalArgumentExceptionWhenDecodingPercent() {
|
||||||
|
this.environmentVariables.put("OTEL_RESOURCE_ATTRIBUTES", "key=%");
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> getAttributes().asMap())
|
||||||
|
.withMessage("Failed to decode percent-encoded characters at index 0 in the value: '%'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private OpenTelemetryResourceAttributes getAttributes() {
|
||||||
|
return new OpenTelemetryResourceAttributes(this.resourceAttributes, this.environmentVariables::get);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateRandomString() {
|
||||||
|
return random.ints(32, 127)
|
||||||
|
.limit(64)
|
||||||
|
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -96,11 +96,19 @@ 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.
|
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.
|
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.
|
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.
|
If you have defined your own javadoc:io.opentelemetry.sdk.resources.Resource[] bean, this will no longer be the case.
|
||||||
|
|
||||||
NOTE: Spring Boot does not provide auto-configuration for OpenTelemetry metrics or logging.
|
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].
|
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**.
|
||||||
|
|
||||||
|
|
||||||
The next sections will provide more details about logging, metrics and traces.
|
The next sections will provide more details about logging, metrics and traces.
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user