22
22
import java .util .LinkedHashMap ;
23
23
import java .util .Map ;
24
24
import java .util .function .Function ;
25
+ import java .util .function .Supplier ;
25
26
27
+ import org .springframework .util .CollectionUtils ;
26
28
import org .springframework .util .StringUtils ;
29
+ import org .springframework .util .function .SupplierUtils ;
27
30
28
31
/**
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.
34
35
* <p>
35
36
* <a href= "https://opentelemetry.io/docs/specs/otel/resource/sdk/">OpenTelemetry
36
37
* Resource Specification</a>
37
38
*
38
39
* @author Dmytro Nosan
39
40
* @since 3.5.0
41
+ * @see #fromEnv()
42
+ * @see #from(Map)
40
43
*/
41
44
public final class OpenTelemetryResourceAttributes {
42
45
43
- private final Map <String , String > resourceAttributes ;
44
-
45
- private final Function <String , String > getEnv ;
46
+ private final Map <String , String > attributes = new LinkedHashMap <>();
46
47
47
48
/**
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}
50
54
*/
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 ;
63
59
}
64
60
65
61
/**
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.
69
66
* <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}
75
70
*/
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 );
84
73
}
85
74
86
75
/**
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 .
91
80
* <p>
92
81
* If {@code service.name} is also provided in {@code OTEL_RESOURCE_ATTRIBUTES}, then
93
82
* {@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}
95
85
*/
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" ), "," )) {
99
89
int index = attribute .indexOf ('=' );
100
90
if (index > 0 ) {
101
91
String key = attribute .substring (0 , index );
102
92
String value = attribute .substring (index + 1 );
103
- attributes .put (key . trim () , decode (value . trim () ));
93
+ attributes .put (key , decode (value ));
104
94
}
105
95
}
106
- String otelServiceName = getEnv ("OTEL_SERVICE_NAME" );
96
+ String otelServiceName = getEnv . apply ("OTEL_SERVICE_NAME" );
107
97
if (otelServiceName != null ) {
108
98
attributes .put ("service.name" , otelServiceName );
109
99
}
110
100
return attributes ;
111
101
}
112
102
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 );
115
156
}
116
157
117
158
/**
@@ -122,7 +163,7 @@ private String getEnv(String name) {
122
163
* @param value value to decode
123
164
* @return the decoded string
124
165
*/
125
- public static String decode (String value ) {
166
+ private static String decode (String value ) {
126
167
if (value .indexOf ('%' ) < 0 ) {
127
168
return value ;
128
169
}
0 commit comments