Skip to content

Commit 97d63b0

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 97d63b0

File tree

4 files changed

+144
-98
lines changed

4 files changed

+144
-98
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 & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,11 @@ public AggregationTemporality aggregationTemporality() {
7777

7878
@Override
7979
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);
80+
OpenTelemetryResourceAttributes attributes = OpenTelemetryResourceAttributes.fromEnv();
81+
attributes.putAll(this.openTelemetryProperties.getResourceAttributes());
82+
attributes.putIfAbsent("service.name", this::getApplicationName);
83+
attributes.putIfAbsent("service.group", this::getApplicationGroup);
84+
return Collections.unmodifiableMap(attributes.asMap());
8685
}
8786

8887
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: 82 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,100 +18,135 @@
1818

1919
import java.io.ByteArrayOutputStream;
2020
import java.nio.charset.StandardCharsets;
21-
import java.util.Collections;
2221
import java.util.LinkedHashMap;
2322
import java.util.Map;
2423
import java.util.function.Function;
24+
import java.util.function.Supplier;
2525

26+
import org.springframework.util.CollectionUtils;
2627
import org.springframework.util.StringUtils;
28+
import org.springframework.util.function.SupplierUtils;
2729

2830
/**
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>
31+
* {@link OpenTelemetryResourceAttributes} for managing OpenTelemetry resource attributes
32+
* and provides convenient API to construct, modify, and merge attributes from different
33+
* sources.
3434
* <p>
3535
* <a href= "https://opentelemetry.io/docs/specs/otel/resource/sdk/">OpenTelemetry
3636
* Resource Specification</a>
3737
*
3838
* @author Dmytro Nosan
3939
* @since 3.5.0
40+
* @see #fromEnv()
4041
*/
4142
public final class OpenTelemetryResourceAttributes {
4243

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

4746
/**
48-
* Creates a new instance of {@link OpenTelemetryResourceAttributes}.
49-
* @param resourceAttributes user provided resource attributes to be used
47+
* Return Resource attributes as a Map.
48+
* @return the resource attributes as key-value pairs
5049
*/
51-
public OpenTelemetryResourceAttributes(Map<String, String> resourceAttributes) {
52-
this(resourceAttributes, null);
50+
public Map<String, String> asMap() {
51+
return new LinkedHashMap<>(this.attributes);
5352
}
5453

5554
/**
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
55+
* Adds a name-value pair to the resource attributes. Both the name and value will be
56+
* trimmed.
57+
* @param name the attribute name to add, must not be null or empty
58+
* @param value the attribute value to add, must not be null
5959
*/
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;
60+
public void put(String name, String value) {
61+
if (StringUtils.hasText(name) && value != null) {
62+
this.attributes.put(name.trim(), value.trim());
63+
}
6364
}
6465

6566
/**
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.
67+
* Merge attributes with the provided resource attributes.
68+
* <p>
69+
* If a key exists in both, the value from provided resource takes precedence, even if
70+
* it is empty.
6971
* <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+
* <b>Keys that are null or empty will be ignored, and all keys will be trimmed.</b>
7273
* <p>
73-
* <b>Null keys and values are ignored.</b>
74-
* @return the resource attributes
74+
* <b>Values that are null will be ignored, and all values will be trimmed.</b>
75+
* @param resourceAttributes resource attributes
7576
*/
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;
77+
public void putAll(Map<String, String> resourceAttributes) {
78+
if (!CollectionUtils.isEmpty(resourceAttributes)) {
79+
resourceAttributes.forEach(this::put);
80+
}
81+
}
82+
83+
/**
84+
* Adds a name-value pair to the resource attributes. Both the name and supplied value
85+
* will be trimmed.
86+
* @param name the attribute name to add, must not be null or empty
87+
* @param valueSupplier the attribute value supplier
88+
*/
89+
public void putIfAbsent(String name, Supplier<String> valueSupplier) {
90+
if (!contains(name)) {
91+
put(name, SupplierUtils.resolve(valueSupplier));
92+
}
93+
}
94+
95+
@Override
96+
public boolean equals(Object obj) {
97+
if (obj == null || getClass() != obj.getClass()) {
98+
return false;
99+
}
100+
OpenTelemetryResourceAttributes that = (OpenTelemetryResourceAttributes) obj;
101+
return this.attributes.equals(that.attributes);
102+
}
103+
104+
@Override
105+
public int hashCode() {
106+
return this.attributes.hashCode();
107+
}
108+
109+
@Override
110+
public String toString() {
111+
return this.attributes.toString();
84112
}
85113

86114
/**
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.
115+
* Creates an {@link OpenTelemetryResourceAttributes} instance based on environment
116+
* variables. This method fetches attributes defined in the
117+
* {@code OTEL_RESOURCE_ATTRIBUTES} and {@code OTEL_SERVICE_NAME} environment
118+
* variables.
91119
* <p>
92120
* If {@code service.name} is also provided in {@code OTEL_RESOURCE_ATTRIBUTES}, then
93121
* {@code OTEL_SERVICE_NAME} takes precedence.
94-
* @return resource attributes
122+
* @return an {@link OpenTelemetryResourceAttributes}
95123
*/
96-
private Map<String, String> getResourceAttributesFromEnv() {
97-
Map<String, String> attributes = new LinkedHashMap<>();
98-
for (String attribute : StringUtils.tokenizeToStringArray(getEnv("OTEL_RESOURCE_ATTRIBUTES"), ",")) {
124+
public static OpenTelemetryResourceAttributes fromEnv() {
125+
return fromEnv(System::getenv);
126+
}
127+
128+
static OpenTelemetryResourceAttributes fromEnv(Function<String, String> getEnv) {
129+
OpenTelemetryResourceAttributes attributes = new OpenTelemetryResourceAttributes();
130+
for (String attribute : StringUtils.tokenizeToStringArray(getEnv.apply("OTEL_RESOURCE_ATTRIBUTES"), ",")) {
99131
int index = attribute.indexOf('=');
100132
if (index > 0) {
101133
String key = attribute.substring(0, index);
102134
String value = attribute.substring(index + 1);
103-
attributes.put(key.trim(), decode(value.trim()));
135+
attributes.put(key, decode(value));
104136
}
105137
}
106-
String otelServiceName = getEnv("OTEL_SERVICE_NAME");
138+
String otelServiceName = getEnv.apply("OTEL_SERVICE_NAME");
107139
if (otelServiceName != null) {
108140
attributes.put("service.name", otelServiceName);
109141
}
110142
return attributes;
111143
}
112144

113-
private String getEnv(String name) {
114-
return this.getEnv.apply(name);
145+
private boolean contains(String name) {
146+
if (!StringUtils.hasText(name)) {
147+
return false;
148+
}
149+
return this.attributes.containsKey(name.trim());
115150
}
116151

117152
/**
@@ -122,7 +157,7 @@ private String getEnv(String name) {
122157
* @param value value to decode
123158
* @return the decoded string
124159
*/
125-
public static String decode(String value) {
160+
private static String decode(String value) {
126161
if (value.indexOf('%') < 0) {
127162
return value;
128163
}

0 commit comments

Comments
 (0)