21
21
import java .util .Collections ;
22
22
import java .util .LinkedHashMap ;
23
23
import java .util .Map ;
24
+ import java .util .function .BiConsumer ;
24
25
import java .util .function .Function ;
26
+ import java .util .function .Supplier ;
25
27
28
+ import org .springframework .util .CollectionUtils ;
26
29
import org .springframework .util .StringUtils ;
30
+ import org .springframework .util .function .SupplierUtils ;
27
31
28
32
/**
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.
32
35
* <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.
34
38
* <p>
35
39
* <a href= "https://opentelemetry.io/docs/specs/otel/resource/sdk/">OpenTelemetry
36
40
* Resource Specification</a>
37
41
*
38
42
* @author Dmytro Nosan
39
43
* @since 3.5.0
44
+ * @see #fromEnv()
40
45
*/
41
46
public final class OpenTelemetryResourceAttributes {
42
47
43
- private final Map <String , String > resourceAttributes ;
48
+ private final Map <String , String > attributes = new LinkedHashMap <>() ;
44
49
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
+ }
46
80
47
81
/**
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
50
84
*/
51
- public OpenTelemetryResourceAttributes ( Map <String , String > resourceAttributes ) {
52
- this ( resourceAttributes , null );
85
+ public Map <String , String > asMap ( ) {
86
+ return Collections . unmodifiableMap ( this . attributes );
53
87
}
54
88
55
89
/**
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
59
92
*/
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 );
63
95
}
64
96
65
97
/**
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.
69
100
* <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.
72
103
* <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
75
108
*/
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
+ }
84
113
}
85
114
86
115
/**
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.
91
118
* <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
95
124
*/
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 ));
105
128
}
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 ());
109
134
}
110
- return attributes ;
111
135
}
112
136
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 ());
115
142
}
116
143
117
144
/**
@@ -122,7 +149,7 @@ private String getEnv(String name) {
122
149
* @param value value to decode
123
150
* @return the decoded string
124
151
*/
125
- public static String decode (String value ) {
152
+ private static String decode (String value ) {
126
153
if (value .indexOf ('%' ) < 0 ) {
127
154
return value ;
128
155
}
0 commit comments