Skip to content

Commit 2f59144

Browse files
committed
Refine the handling of OpenTelemetry resource attributes
The `OpenTelemetryResourceAttributes` class now includes convenient methods to work with resource attributes. Additionally, the `OpenTelemetryResourceAttributes.fromEnv()` method has been added to create attributes based on environment variables. Signed-off-by: Dmytro Nosan <[email protected]>
1 parent e886785 commit 2f59144

File tree

4 files changed

+178
-134
lines changed

4 files changed

+178
-134
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsPropertiesConfigAdapter.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
1818

19-
import java.util.Collections;
2019
import java.util.Map;
2120
import java.util.concurrent.TimeUnit;
2221

@@ -77,12 +76,11 @@ public AggregationTemporality aggregationTemporality() {
7776

7877
@Override
7978
public Map<String, String> resourceAttributes() {
80-
Map<String, String> attributes = new OpenTelemetryResourceAttributes(
81-
this.openTelemetryProperties.getResourceAttributes())
82-
.asMap();
83-
attributes.computeIfAbsent("service.name", (key) -> getApplicationName());
84-
attributes.computeIfAbsent("service.group", (key) -> getApplicationGroup());
85-
return Collections.unmodifiableMap(attributes);
79+
OpenTelemetryResourceAttributes attributes = OpenTelemetryResourceAttributes.fromEnv();
80+
attributes.putAll(this.openTelemetryProperties.getResourceAttributes());
81+
attributes.putIfAbsent("service.name", this::getApplicationName);
82+
attributes.putIfAbsent("service.group", this::getApplicationGroup);
83+
return attributes.asMap();
8684
}
8785

8886
private String getApplicationName() {

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.opentelemetry;
1818

19-
import java.util.Map;
20-
2119
import io.opentelemetry.api.OpenTelemetry;
2220
import io.opentelemetry.context.propagation.ContextPropagators;
2321
import io.opentelemetry.sdk.OpenTelemetrySdk;
@@ -76,10 +74,10 @@ Resource openTelemetryResource(Environment environment, OpenTelemetryProperties
7674

7775
private Resource toResource(Environment environment, OpenTelemetryProperties properties) {
7876
ResourceBuilder builder = Resource.builder();
79-
Map<String, String> attributes = new OpenTelemetryResourceAttributes(properties.getResourceAttributes())
80-
.asMap();
81-
attributes.computeIfAbsent("service.name", (key) -> getApplicationName(environment));
82-
attributes.computeIfAbsent("service.group", (key) -> getApplicationGroup(environment));
77+
OpenTelemetryResourceAttributes attributes = OpenTelemetryResourceAttributes.fromEnv();
78+
attributes.putAll(properties.getResourceAttributes());
79+
attributes.putIfAbsent("service.name", () -> getApplicationName(environment));
80+
attributes.putIfAbsent("service.group", () -> getApplicationGroup(environment));
8381
attributes.forEach(builder::put);
8482
return builder.build();
8583
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryResourceAttributes.java

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,97 +21,124 @@
2121
import java.util.Collections;
2222
import java.util.LinkedHashMap;
2323
import java.util.Map;
24+
import java.util.function.BiConsumer;
2425
import java.util.function.Function;
26+
import java.util.function.Supplier;
2527

28+
import org.springframework.util.CollectionUtils;
2629
import org.springframework.util.StringUtils;
30+
import org.springframework.util.function.SupplierUtils;
2731

2832
/**
29-
* OpenTelemetryResourceAttributes retrieves information from the
30-
* {@code OTEL_RESOURCE_ATTRIBUTES} and {@code OTEL_SERVICE_NAME} environment variables
31-
* and merges it with the resource attributes provided by the user.
33+
* {@link OpenTelemetryResourceAttributes} is used for handling string-based OpenTelemetry
34+
* resource attributes.
3235
* <p>
33-
* <b>User-provided resource attributes take precedence.</b>
36+
* This class is meant for internal use only and is not a replacement for the
37+
* OpenTelemetry Java Resource SDK.
3438
* <p>
3539
* <a href= "https://opentelemetry.io/docs/specs/otel/resource/sdk/">OpenTelemetry
3640
* Resource Specification</a>
3741
*
3842
* @author Dmytro Nosan
3943
* @since 3.5.0
44+
* @see #fromEnv()
4045
*/
4146
public final class OpenTelemetryResourceAttributes {
4247

43-
private final Map<String, String> resourceAttributes;
48+
private final Map<String, String> attributes = new LinkedHashMap<>();
4449

45-
private final Function<String, String> getEnv;
50+
/**
51+
* Creates an {@link OpenTelemetryResourceAttributes} instance based on environment
52+
* variables. This method fetches attributes defined in the
53+
* {@code OTEL_RESOURCE_ATTRIBUTES} and {@code OTEL_SERVICE_NAME} environment
54+
* variables.
55+
* <p>
56+
* If {@code service.name} is also provided in {@code OTEL_RESOURCE_ATTRIBUTES}, then
57+
* {@code OTEL_SERVICE_NAME} takes precedence.
58+
* @return an {@link OpenTelemetryResourceAttributes}
59+
*/
60+
public static OpenTelemetryResourceAttributes fromEnv() {
61+
return fromEnv(System::getenv);
62+
}
63+
64+
static OpenTelemetryResourceAttributes fromEnv(Function<String, String> getEnv) {
65+
OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes();
66+
for (String attribute : StringUtils.tokenizeToStringArray(getEnv.apply("OTEL_RESOURCE_ATTRIBUTES"), ",")) {
67+
int index = attribute.indexOf('=');
68+
if (index > 0) {
69+
String key = attribute.substring(0, index);
70+
String value = attribute.substring(index + 1);
71+
attributes.put(key, decode(value));
72+
}
73+
}
74+
String otelServiceName = getEnv.apply("OTEL_SERVICE_NAME");
75+
if (otelServiceName != null) {
76+
attributes.put("service.name", otelServiceName);
77+
}
78+
return attributes;
79+
}
4680

4781
/**
48-
* Creates a new instance of {@link OpenTelemetryResourceAttributes}.
49-
* @param resourceAttributes user provided resource attributes to be used
82+
* Return Resource attributes as a Map.
83+
* @return the resource attributes as key-value pairs
5084
*/
51-
public OpenTelemetryResourceAttributes(Map<String, String> resourceAttributes) {
52-
this(resourceAttributes, null);
85+
public Map<String, String> asMap() {
86+
return Collections.unmodifiableMap(this.attributes);
5387
}
5488

5589
/**
56-
* Creates a new {@link OpenTelemetryResourceAttributes} instance.
57-
* @param resourceAttributes user provided resource attributes to be used
58-
* @param getEnv a function to retrieve environment variables by name
90+
* Performs the given action for each key-value pairs.
91+
* @param consumer the operation to perform for each entry
5992
*/
60-
OpenTelemetryResourceAttributes(Map<String, String> resourceAttributes, Function<String, String> getEnv) {
61-
this.resourceAttributes = (resourceAttributes != null) ? resourceAttributes : Collections.emptyMap();
62-
this.getEnv = (getEnv != null) ? getEnv : System::getenv;
93+
public void forEach(BiConsumer<String, String> consumer) {
94+
this.attributes.forEach(consumer);
6395
}
6496

6597
/**
66-
* Returns resource attributes by combining attributes from environment variables and
67-
* user-defined resource attributes. The final resource contains all attributes from
68-
* both sources.
98+
* Merge attributes with the provided resource attributes. Both keys and values will
99+
* be trimmed.
69100
* <p>
70-
* If a key exists in both environment variables and user-defined resources, the value
71-
* from the user-defined resource takes precedence, even if it is empty.
101+
* If a key exists in both, the value from provided resource takes precedence, even if
102+
* it is empty.
72103
* <p>
73-
* <b>Null keys and values are ignored.</b>
74-
* @return the resource attributes
104+
* <b>Keys that are null or empty will be skipped.</b>
105+
* <p>
106+
* <b>Values that are null will be skipped.</b>
107+
* @param resourceAttributes resource attributes
75108
*/
76-
public Map<String, String> asMap() {
77-
Map<String, String> attributes = getResourceAttributesFromEnv();
78-
this.resourceAttributes.forEach((name, value) -> {
79-
if (name != null && value != null) {
80-
attributes.put(name, value);
81-
}
82-
});
83-
return attributes;
109+
public void putAll(Map<String, String> resourceAttributes) {
110+
if (!CollectionUtils.isEmpty(resourceAttributes)) {
111+
resourceAttributes.forEach(this::put);
112+
}
84113
}
85114

86115
/**
87-
* Parses resource attributes from the {@link System#getenv()}. This method fetches
88-
* attributes defined in the {@code OTEL_RESOURCE_ATTRIBUTES} and
89-
* {@code OTEL_SERVICE_NAME} environment variables and provides them as key-value
90-
* pairs.
116+
* Adds a key-value pair to the resource attributes. Both the key and supplied value
117+
* will be trimmed.
91118
* <p>
92-
* If {@code service.name} is also provided in {@code OTEL_RESOURCE_ATTRIBUTES}, then
93-
* {@code OTEL_SERVICE_NAME} takes precedence.
94-
* @return resource attributes
119+
* <b>Key that is null or empty it will be skipped.</b>
120+
* <p>
121+
* <b>Value that is null will be skipped.</b>
122+
* @param key the attribute key to add, must not be null or empty
123+
* @param valueSupplier the attribute value supplier
95124
*/
96-
private Map<String, String> getResourceAttributesFromEnv() {
97-
Map<String, String> attributes = new LinkedHashMap<>();
98-
for (String attribute : StringUtils.tokenizeToStringArray(getEnv("OTEL_RESOURCE_ATTRIBUTES"), ",")) {
99-
int index = attribute.indexOf('=');
100-
if (index > 0) {
101-
String key = attribute.substring(0, index);
102-
String value = attribute.substring(index + 1);
103-
attributes.put(key.trim(), decode(value.trim()));
104-
}
125+
public void putIfAbsent(String key, Supplier<String> valueSupplier) {
126+
if (!contains(key)) {
127+
put(key, SupplierUtils.resolve(valueSupplier));
105128
}
106-
String otelServiceName = getEnv("OTEL_SERVICE_NAME");
107-
if (otelServiceName != null) {
108-
attributes.put("service.name", otelServiceName);
129+
}
130+
131+
private void put(String key, String value) {
132+
if (StringUtils.hasText(key) && value != null) {
133+
this.attributes.put(key.trim(), value.trim());
109134
}
110-
return attributes;
111135
}
112136

113-
private String getEnv(String name) {
114-
return this.getEnv.apply(name);
137+
private boolean contains(String key) {
138+
if (!StringUtils.hasText(key)) {
139+
return false;
140+
}
141+
return this.attributes.containsKey(key.trim());
115142
}
116143

117144
/**
@@ -122,7 +149,7 @@ private String getEnv(String name) {
122149
* @param value value to decode
123150
* @return the decoded string
124151
*/
125-
public static String decode(String value) {
152+
private static String decode(String value) {
126153
if (value.indexOf('%') < 0) {
127154
return value;
128155
}

0 commit comments

Comments
 (0)