Skip to content

Commit 4335cf2

Browse files
authored
Stabilize complex attributes (#7973)
1 parent 3f5411d commit 4335cf2

File tree

63 files changed

+2076
-134
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2076
-134
lines changed

api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedAttributes.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
import io.opentelemetry.api.internal.ImmutableKeyValuePairs;
99
import java.util.ArrayList;
10+
import java.util.Collections;
1011
import java.util.Comparator;
12+
import java.util.List;
1113
import javax.annotation.Nullable;
1214
import javax.annotation.concurrent.Immutable;
1315

@@ -45,9 +47,117 @@ public AttributesBuilder toBuilder() {
4547
@Override
4648
@Nullable
4749
public <T> T get(AttributeKey<T> key) {
50+
if (key == null) {
51+
return null;
52+
}
53+
if (key.getType() == AttributeType.VALUE) {
54+
return (T) getAsValue(key.getKey());
55+
}
56+
// Check if we're looking for an array type but have a VALUE with empty array
57+
if (isArrayType(key.getType())) {
58+
T value = (T) super.get(key);
59+
if (value == null) {
60+
// Check if there's a VALUE with the same key that contains an empty array
61+
Value<?> valueAttr = getValueAttribute(key.getKey());
62+
if (valueAttr != null && isEmptyArray(valueAttr)) {
63+
return (T) Collections.emptyList();
64+
}
65+
}
66+
return value;
67+
}
4868
return (T) super.get(key);
4969
}
5070

71+
private static boolean isArrayType(AttributeType type) {
72+
return type == AttributeType.STRING_ARRAY
73+
|| type == AttributeType.LONG_ARRAY
74+
|| type == AttributeType.DOUBLE_ARRAY
75+
|| type == AttributeType.BOOLEAN_ARRAY;
76+
}
77+
78+
@Nullable
79+
private Value<?> getValueAttribute(String keyName) {
80+
List<Object> data = data();
81+
for (int i = 0; i < data.size(); i += 2) {
82+
AttributeKey<?> currentKey = (AttributeKey<?>) data.get(i);
83+
if (currentKey.getKey().equals(keyName) && currentKey.getType() == AttributeType.VALUE) {
84+
return (Value<?>) data.get(i + 1);
85+
}
86+
}
87+
return null;
88+
}
89+
90+
private static boolean isEmptyArray(Value<?> value) {
91+
if (value.getType() != ValueType.ARRAY) {
92+
return false;
93+
}
94+
@SuppressWarnings("unchecked")
95+
List<Value<?>> arrayValues = (List<Value<?>>) value.getValue();
96+
return arrayValues.isEmpty();
97+
}
98+
99+
@Nullable
100+
private Value<?> getAsValue(String keyName) {
101+
// Find any attribute with the same key name and convert it to Value
102+
List<Object> data = data();
103+
for (int i = 0; i < data.size(); i += 2) {
104+
AttributeKey<?> currentKey = (AttributeKey<?>) data.get(i);
105+
if (currentKey.getKey().equals(keyName)) {
106+
Object value = data.get(i + 1);
107+
return asValue(currentKey.getType(), value);
108+
}
109+
}
110+
return null;
111+
}
112+
113+
@SuppressWarnings("unchecked")
114+
@Nullable
115+
private static Value<?> asValue(AttributeType type, Object value) {
116+
switch (type) {
117+
case STRING:
118+
return Value.of((String) value);
119+
case LONG:
120+
return Value.of((Long) value);
121+
case DOUBLE:
122+
return Value.of((Double) value);
123+
case BOOLEAN:
124+
return Value.of((Boolean) value);
125+
case STRING_ARRAY:
126+
List<String> stringList = (List<String>) value;
127+
Value<?>[] stringValues = new Value<?>[stringList.size()];
128+
for (int i = 0; i < stringList.size(); i++) {
129+
stringValues[i] = Value.of(stringList.get(i));
130+
}
131+
return Value.of(stringValues);
132+
case LONG_ARRAY:
133+
List<Long> longList = (List<Long>) value;
134+
Value<?>[] longValues = new Value<?>[longList.size()];
135+
for (int i = 0; i < longList.size(); i++) {
136+
longValues[i] = Value.of(longList.get(i));
137+
}
138+
return Value.of(longValues);
139+
case DOUBLE_ARRAY:
140+
List<Double> doubleList = (List<Double>) value;
141+
Value<?>[] doubleValues = new Value<?>[doubleList.size()];
142+
for (int i = 0; i < doubleList.size(); i++) {
143+
doubleValues[i] = Value.of(doubleList.get(i));
144+
}
145+
return Value.of(doubleValues);
146+
case BOOLEAN_ARRAY:
147+
List<Boolean> booleanList = (List<Boolean>) value;
148+
Value<?>[] booleanValues = new Value<?>[booleanList.size()];
149+
for (int i = 0; i < booleanList.size(); i++) {
150+
booleanValues[i] = Value.of(booleanList.get(i));
151+
}
152+
return Value.of(booleanValues);
153+
case VALUE:
154+
// Already a Value
155+
return (Value<?>) value;
156+
}
157+
// Should not reach here
158+
return null;
159+
}
160+
51161
static Attributes sortAndFilterToAttributes(Object... data) {
52162
// null out any empty keys or keys with null values
53163
// so they will then be removed by the sortAndFilter method.

api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedAttributesBuilder.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55

66
package io.opentelemetry.api.common;
77

8+
import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey;
9+
import static io.opentelemetry.api.common.AttributeKey.booleanKey;
10+
import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey;
11+
import static io.opentelemetry.api.common.AttributeKey.doubleKey;
12+
import static io.opentelemetry.api.common.AttributeKey.longArrayKey;
13+
import static io.opentelemetry.api.common.AttributeKey.longKey;
14+
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
15+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
16+
817
import java.util.ArrayList;
918
import java.util.Arrays;
1019
import java.util.List;
@@ -42,11 +51,114 @@ public <T> AttributesBuilder put(AttributeKey<T> key, @Nullable T value) {
4251
if (key == null || key.getKey().isEmpty() || value == null) {
4352
return this;
4453
}
54+
if (key.getType() == AttributeType.VALUE && value instanceof Value) {
55+
putValue(key, (Value<?>) value);
56+
return this;
57+
}
4558
data.add(key);
4659
data.add(value);
4760
return this;
4861
}
4962

63+
@SuppressWarnings("unchecked")
64+
private void putValue(AttributeKey<?> key, Value<?> valueObj) {
65+
// Convert VALUE type to narrower type when possible
66+
String keyName = key.getKey();
67+
switch (valueObj.getType()) {
68+
case STRING:
69+
put(stringKey(keyName), ((Value<String>) valueObj).getValue());
70+
return;
71+
case LONG:
72+
put(longKey(keyName), ((Value<Long>) valueObj).getValue());
73+
return;
74+
case DOUBLE:
75+
put(doubleKey(keyName), ((Value<Double>) valueObj).getValue());
76+
return;
77+
case BOOLEAN:
78+
put(booleanKey(keyName), ((Value<Boolean>) valueObj).getValue());
79+
return;
80+
case ARRAY:
81+
List<Value<?>> arrayValues = (List<Value<?>>) valueObj.getValue();
82+
AttributeType attributeType = attributeType(arrayValues);
83+
switch (attributeType) {
84+
case STRING_ARRAY:
85+
List<String> strings = new ArrayList<>(arrayValues.size());
86+
for (Value<?> v : arrayValues) {
87+
strings.add((String) v.getValue());
88+
}
89+
put(stringArrayKey(keyName), strings);
90+
return;
91+
case LONG_ARRAY:
92+
List<Long> longs = new ArrayList<>(arrayValues.size());
93+
for (Value<?> v : arrayValues) {
94+
longs.add((Long) v.getValue());
95+
}
96+
put(longArrayKey(keyName), longs);
97+
return;
98+
case DOUBLE_ARRAY:
99+
List<Double> doubles = new ArrayList<>(arrayValues.size());
100+
for (Value<?> v : arrayValues) {
101+
doubles.add((Double) v.getValue());
102+
}
103+
put(doubleArrayKey(keyName), doubles);
104+
return;
105+
case BOOLEAN_ARRAY:
106+
List<Boolean> booleans = new ArrayList<>(arrayValues.size());
107+
for (Value<?> v : arrayValues) {
108+
booleans.add((Boolean) v.getValue());
109+
}
110+
put(booleanArrayKey(keyName), booleans);
111+
return;
112+
case VALUE:
113+
// Not coercible (empty, non-homogeneous, or unsupported element type)
114+
data.add(key);
115+
data.add(valueObj);
116+
return;
117+
default:
118+
throw new IllegalArgumentException("Unexpected array attribute type: " + attributeType);
119+
}
120+
case KEY_VALUE_LIST:
121+
case BYTES:
122+
case EMPTY:
123+
// Keep as VALUE type
124+
data.add(key);
125+
data.add(valueObj);
126+
}
127+
}
128+
129+
/**
130+
* Returns the AttributeType for a homogeneous array (STRING_ARRAY, LONG_ARRAY, DOUBLE_ARRAY, or
131+
* BOOLEAN_ARRAY), or VALUE if the array is empty, non-homogeneous, or contains unsupported
132+
* element types.
133+
*/
134+
private static AttributeType attributeType(List<Value<?>> arrayValues) {
135+
if (arrayValues.isEmpty()) {
136+
return AttributeType.VALUE;
137+
}
138+
ValueType elementType = arrayValues.get(0).getType();
139+
for (Value<?> v : arrayValues) {
140+
if (v.getType() != elementType) {
141+
return AttributeType.VALUE;
142+
}
143+
}
144+
switch (elementType) {
145+
case STRING:
146+
return AttributeType.STRING_ARRAY;
147+
case LONG:
148+
return AttributeType.LONG_ARRAY;
149+
case DOUBLE:
150+
return AttributeType.DOUBLE_ARRAY;
151+
case BOOLEAN:
152+
return AttributeType.BOOLEAN_ARRAY;
153+
case ARRAY:
154+
case KEY_VALUE_LIST:
155+
case BYTES:
156+
case EMPTY:
157+
return AttributeType.VALUE;
158+
}
159+
throw new IllegalArgumentException("Unsupported element type: " + elementType);
160+
}
161+
50162
@Override
51163
@SuppressWarnings({"unchecked", "rawtypes"})
52164
// Safe: Attributes guarantees iteration over matching AttributeKey<T> / value pairs.

api/all/src/main/java/io/opentelemetry/api/common/AttributeKey.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,19 @@ static AttributeKey<List<Long>> longArrayKey(String key) {
7070
static AttributeKey<List<Double>> doubleArrayKey(String key) {
7171
return InternalAttributeKeyImpl.create(key, AttributeType.DOUBLE_ARRAY);
7272
}
73+
74+
/**
75+
* Returns a new ExtendedAttributeKey for {@link Value} valued attributes.
76+
*
77+
* <p>Simple attributes ({@link AttributeType#STRING}, {@link AttributeType#LONG}, {@link
78+
* AttributeType#DOUBLE}, {@link AttributeType#BOOLEAN}, {@link AttributeType#STRING_ARRAY},
79+
* {@link AttributeType#LONG_ARRAY}, {@link AttributeType#DOUBLE_ARRAY}, {@link
80+
* AttributeType#BOOLEAN_ARRAY}) should be used whenever possible. Instrumentations should assume
81+
* that backends do not index individual properties of complex attributes, that querying or
82+
* aggregating on such properties is inefficient and complicated, and that reporting complex
83+
* attributes carries higher performance overhead.
84+
*/
85+
static AttributeKey<Value<?>> valueKey(String key) {
86+
return InternalAttributeKeyImpl.create(key, AttributeType.VALUE);
87+
}
7388
}

api/all/src/main/java/io/opentelemetry/api/common/AttributeType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ public enum AttributeType {
1717
STRING_ARRAY,
1818
BOOLEAN_ARRAY,
1919
LONG_ARRAY,
20-
DOUBLE_ARRAY
20+
DOUBLE_ARRAY,
21+
VALUE
2122
}

api/all/src/main/java/io/opentelemetry/api/common/Attributes.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,58 @@
3333
@Immutable
3434
public interface Attributes {
3535

36-
/** Returns the value for the given {@link AttributeKey}, or {@code null} if not found. */
36+
/**
37+
* Returns the value for the given {@link AttributeKey}, or {@code null} if not found.
38+
*
39+
* <p>Note: this method will automatically return the corresponding {@link
40+
* io.opentelemetry.api.common.Value} instance when passed a key of type {@link
41+
* AttributeType#VALUE} and a simple attribute is found. This is the inverse of {@link
42+
* AttributesBuilder#put(AttributeKey, Object)} when the key is {@link AttributeType#VALUE}.
43+
*
44+
* <ul>
45+
* <li>If {@code put(AttributeKey.stringKey("key"), "a")} was called, then {@code
46+
* get(AttributeKey.valueKey("key"))} returns {@code Value.of("a")}.
47+
* <li>If {@code put(AttributeKey.longKey("key"), 1L)} was called, then {@code
48+
* get(AttributeKey.valueKey("key"))} returns {@code Value.of(1L)}.
49+
* <li>If {@code put(AttributeKey.doubleKey("key"), 1.0)} was called, then {@code
50+
* get(AttributeKey.valueKey("key"))} returns {@code Value.of(1.0)}.
51+
* <li>If {@code put(AttributeKey.booleanKey("key"), true)} was called, then {@code
52+
* get(AttributeKey.valueKey("key"))} returns {@code Value.of(true)}.
53+
* <li>If {@code put(AttributeKey.stringArrayKey("key"), Arrays.asList("a", "b"))} was called,
54+
* then {@code get(AttributeKey.valueKey("key"))} returns {@code Value.of(Value.of("a"),
55+
* Value.of("b"))}.
56+
* <li>If {@code put(AttributeKey.longArrayKey("key"), Arrays.asList(1L, 2L))} was called, then
57+
* {@code get(AttributeKey.valueKey("key"))} returns {@code Value.of(Value.of(1L),
58+
* Value.of(2L))}.
59+
* <li>If {@code put(AttributeKey.doubleArrayKey("key"), Arrays.asList(1.0, 2.0))} was called,
60+
* then {@code get(AttributeKey.valueKey("key"))} returns {@code Value.of(Value.of(1.0),
61+
* Value.of(2.0))}.
62+
* <li>If {@code put(AttributeKey.booleanArrayKey("key"), Arrays.asList(true, false))} was
63+
* called, then {@code get(AttributeKey.valueKey("key"))} returns {@code
64+
* Value.of(Value.of(true), Value.of(false))}.
65+
* </ul>
66+
*
67+
* <p>Further, if {@code put(AttributeKey.valueKey("key"), Value.of(emptyList()))} was called,
68+
* then
69+
*
70+
* <ul>
71+
* <li>{@code get(AttributeKey.stringArrayKey("key"))}
72+
* <li>{@code get(AttributeKey.longArrayKey("key"))}
73+
* <li>{@code get(AttributeKey.booleanArrayKey("key"))}
74+
* <li>{@code get(AttributeKey.doubleArrayKey("key"))}
75+
* </ul>
76+
*
77+
* <p>all return an empty list (as opposed to {@code null}).
78+
*/
3779
@Nullable
3880
<T> T get(AttributeKey<T> key);
3981

40-
/** Iterates over all the key-value pairs of attributes contained by this instance. */
82+
/**
83+
* Iterates over all the key-value pairs of attributes contained by this instance.
84+
*
85+
* <p>Note: {@link AttributeType#VALUE} attributes will be represented as simple attributes if
86+
* possible. See {@link AttributesBuilder#put(AttributeKey, Object)} for more details.
87+
*/
4188
void forEach(BiConsumer<? super AttributeKey<?>, ? super Object> consumer);
4289

4390
/** The number of attributes contained in this. */
@@ -46,7 +93,12 @@ public interface Attributes {
4693
/** Whether there are any attributes contained in this. */
4794
boolean isEmpty();
4895

49-
/** Returns a read-only view of this {@link Attributes} as a {@link Map}. */
96+
/**
97+
* Returns a read-only view of this {@link Attributes} as a {@link Map}.
98+
*
99+
* <p>Note: {@link AttributeType#VALUE} attributes will be represented as simple attributes in
100+
* this map if possible. See {@link AttributesBuilder#put(AttributeKey, Object)} for more details.
101+
*/
50102
Map<AttributeKey<?>, Object> asMap();
51103

52104
/** Returns a {@link Attributes} instance with no attributes. */

0 commit comments

Comments
 (0)