Skip to content

Commit f971927

Browse files
committed
Refine the handling of OpenTelemetry resource attributes
OpenTelemetryResourceAttributes now has convenient APIs to construct, modify, and merge attributes from various sources, including environment variables and user-defined attribute maps. Signed-off-by: Dmytro Nosan <[email protected]>
1 parent e886785 commit f971927

File tree

4 files changed

+172
-121
lines changed

4 files changed

+172
-121
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: 5 additions & 7 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,11 +74,11 @@ 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));
83-
attributes.forEach(builder::put);
77+
OpenTelemetryResourceAttributes attributes = OpenTelemetryResourceAttributes.fromEnv();
78+
attributes.putAll(properties.getResourceAttributes());
79+
attributes.putIfAbsent("service.name", () -> getApplicationName(environment));
80+
attributes.putIfAbsent("service.group", () -> getApplicationGroup(environment));
81+
attributes.asMap().forEach(builder::put);
8482
return builder.build();
8583
}
8684

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

Lines changed: 92 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,96 +22,137 @@
2222
import java.util.LinkedHashMap;
2323
import java.util.Map;
2424
import java.util.function.Function;
25+
import java.util.function.Supplier;
2526

27+
import org.springframework.util.CollectionUtils;
2628
import org.springframework.util.StringUtils;
29+
import org.springframework.util.function.SupplierUtils;
2730

2831
/**
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.
32-
* <p>
33-
* <b>User-provided resource attributes take precedence.</b>
32+
* {@link OpenTelemetryResourceAttributes} for managing OpenTelemetry resource attributes
33+
* and provides convenient API to construct, modify, and merge attributes from different
34+
* sources, including environment variables and user-defined attribute maps.
3435
* <p>
3536
* <a href= "https://opentelemetry.io/docs/specs/otel/resource/sdk/">OpenTelemetry
3637
* Resource Specification</a>
3738
*
3839
* @author Dmytro Nosan
3940
* @since 3.5.0
41+
* @see #fromEnv()
42+
* @see #from(Map)
4043
*/
4144
public final class OpenTelemetryResourceAttributes {
4245

43-
private final Map<String, String> resourceAttributes;
44-
45-
private final Function<String, String> getEnv;
46+
private final Map<String, String> attributes = new LinkedHashMap<>();
4647

4748
/**
48-
* Creates a new instance of {@link OpenTelemetryResourceAttributes}.
49-
* @param resourceAttributes user provided resource attributes to be used
49+
* Creates an instance of {@link OpenTelemetryResourceAttributes} from the given map.
50+
* Trims the keys and values, ignoring keys that are null, empty, or have a null
51+
* value.
52+
* @param resourceAttributes a map containing resource attribute key-value pairs
53+
* @return an {@link OpenTelemetryResourceAttributes}
5054
*/
51-
public OpenTelemetryResourceAttributes(Map<String, String> resourceAttributes) {
52-
this(resourceAttributes, null);
53-
}
54-
55-
/**
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
59-
*/
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;
55+
public static OpenTelemetryResourceAttributes from(Map<String, String> resourceAttributes) {
56+
OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes();
57+
attributes.putAll(resourceAttributes);
58+
return attributes;
6359
}
6460

6561
/**
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.
62+
* Creates an {@link OpenTelemetryResourceAttributes} instance based on environment
63+
* variables. This method fetches attributes defined in the
64+
* {@code OTEL_RESOURCE_ATTRIBUTES} and {@code OTEL_SERVICE_NAME} environment
65+
* variables.
6966
* <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.
72-
* <p>
73-
* <b>Null keys and values are ignored.</b>
74-
* @return the resource attributes
67+
* If {@code service.name} is also provided in {@code OTEL_RESOURCE_ATTRIBUTES}, then
68+
* {@code OTEL_SERVICE_NAME} takes precedence.
69+
* @return an {@link OpenTelemetryResourceAttributes}
7570
*/
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;
71+
public static OpenTelemetryResourceAttributes fromEnv() {
72+
return fromEnv(System::getenv);
8473
}
8574

8675
/**
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.
76+
* Creates an {@link OpenTelemetryResourceAttributes} instance based on environment
77+
* variables. This method fetches attributes defined in the
78+
* {@code OTEL_RESOURCE_ATTRIBUTES} and {@code OTEL_SERVICE_NAME} environment
79+
* variables.
9180
* <p>
9281
* If {@code service.name} is also provided in {@code OTEL_RESOURCE_ATTRIBUTES}, then
9382
* {@code OTEL_SERVICE_NAME} takes precedence.
94-
* @return resource attributes
83+
* @param getEnv the function to be used to get environment variable value
84+
* @return an {@link OpenTelemetryResourceAttributes}
9585
*/
96-
private Map<String, String> getResourceAttributesFromEnv() {
97-
Map<String, String> attributes = new LinkedHashMap<>();
98-
for (String attribute : StringUtils.tokenizeToStringArray(getEnv("OTEL_RESOURCE_ATTRIBUTES"), ",")) {
86+
static OpenTelemetryResourceAttributes fromEnv(Function<String, String> getEnv) {
87+
OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes();
88+
for (String attribute : StringUtils.tokenizeToStringArray(getEnv.apply("OTEL_RESOURCE_ATTRIBUTES"), ",")) {
9989
int index = attribute.indexOf('=');
10090
if (index > 0) {
10191
String key = attribute.substring(0, index);
10292
String value = attribute.substring(index + 1);
103-
attributes.put(key.trim(), decode(value.trim()));
93+
attributes.put(key, decode(value));
10494
}
10595
}
106-
String otelServiceName = getEnv("OTEL_SERVICE_NAME");
96+
String otelServiceName = getEnv.apply("OTEL_SERVICE_NAME");
10797
if (otelServiceName != null) {
10898
attributes.put("service.name", otelServiceName);
10999
}
110100
return attributes;
111101
}
112102

113-
private String getEnv(String name) {
114-
return this.getEnv.apply(name);
103+
/**
104+
* Adds a name-value pair to the resource attributes. Both the name and value will be
105+
* trimmed.
106+
* @param name the attribute name to add, must not be null or empty
107+
* @param value the attribute value to add, must not be null
108+
*/
109+
public void put(String name, String value) {
110+
if (StringUtils.hasText(name) && value != null) {
111+
this.attributes.put(name.trim(), value.trim());
112+
}
113+
}
114+
115+
/**
116+
* Merge attributes with the provided resource attributes. The final resource contains
117+
* all attributes from both sources.
118+
* <p>
119+
* If a key exists in both, the value from provided resource takes precedence, even if
120+
* it is empty.
121+
* <p>
122+
* <b>Keys that are null or empty will be ignored, and all keys will be trimmed.</b>
123+
* <p>
124+
* <b>Values that are null will be ignored, and all values will be trimmed.</b>
125+
* @param resourceAttributes resource attributes
126+
*/
127+
public void putAll(Map<String, String> resourceAttributes) {
128+
if (!CollectionUtils.isEmpty(resourceAttributes)) {
129+
resourceAttributes.forEach(this::put);
130+
}
131+
}
132+
133+
/**
134+
* Adds a name-value pair to the resource attributes. Both the name and supplied value
135+
* will be trimmed.
136+
* @param name the attribute name to add, must not be null or empty
137+
* @param valueSupplier the attribute value supplier
138+
*/
139+
public void putIfAbsent(String name, Supplier<String> valueSupplier) {
140+
if (!StringUtils.hasText(name)) {
141+
return;
142+
}
143+
if (this.attributes.containsKey(name.trim())) {
144+
return;
145+
}
146+
put(name, SupplierUtils.resolve(valueSupplier));
147+
}
148+
149+
/**
150+
* Returns resource attributes as a map.
151+
* @return an <b>unmodifiable</b> map containing the resource attributes, never
152+
* {@code null}.
153+
*/
154+
public Map<String, String> asMap() {
155+
return Collections.unmodifiableMap(this.attributes);
115156
}
116157

117158
/**
@@ -122,7 +163,7 @@ private String getEnv(String name) {
122163
* @param value value to decode
123164
* @return the decoded string
124165
*/
125-
public static String decode(String value) {
166+
private static String decode(String value) {
126167
if (value.indexOf('%') < 0) {
127168
return value;
128169
}

0 commit comments

Comments
 (0)