2121import java .util .Collections ;
2222import java .util .LinkedHashMap ;
2323import java .util .Map ;
24+ import java .util .function .BiConsumer ;
2425import java .util .function .Function ;
26+ import java .util .function .Supplier ;
2527
28+ import org .springframework .util .CollectionUtils ;
2629import 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 */
4146public 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