Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5e889a8
Prototype: Complex attributes (Option C - minimal)
trask Nov 5, 2025
4a2c048
Remove String, Value<?> overload
trask Nov 6, 2025
3b8e35f
Add javadoc explaining coersion
trask Nov 6, 2025
04937c0
more javadoc
trask Nov 7, 2025
73466c8
more javadoc
trask Nov 7, 2025
1958768
more
trask Nov 7, 2025
e4308cf
Complex Attributes - incubating implementation
trask Nov 5, 2025
5f342e2
Deprecation
trask Nov 5, 2025
8c40374
More implementation
trask Nov 7, 2025
7d49b5e
updates
trask Nov 7, 2025
d3f9dbe
javadoc
trask Nov 7, 2025
08e6e0c
tests
trask Nov 8, 2025
c7824e1
javadoc
trask Nov 8, 2025
26a3413
fix
trask Nov 13, 2025
d83ff5d
Add a missing test
trask Nov 13, 2025
10d6e02
Apply suggestion from @trask
trask Nov 13, 2025
198faa2
codecov
trask Nov 13, 2025
4386c8c
Implement limits
trask Nov 18, 2025
575535d
simplify putValue implementation
trask Nov 18, 2025
f1ffad8
initialize array lists with correct size
trask Nov 18, 2025
6480d0e
Add test for non-existent array type to improve code coverage
trask Nov 18, 2025
22e857d
spotless
trask Nov 18, 2025
b91d3bb
Move warning to where users will see it
trask Nov 18, 2025
59ab595
Merge remote-tracking branch 'upstream/main' into complex-attrs-c-impl
trask Nov 25, 2025
d44e727
Don't allocate when already valid length
trask Nov 25, 2025
c6b9858
Remove unnecessary suppress warnings
trask Nov 25, 2025
28fedf2
Remove test of deprecated feature
trask Nov 25, 2025
587e7e3
spotless
trask Nov 25, 2025
9b9ea16
Don't call deprecated method
trask Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.common.ValueType;
import io.opentelemetry.api.internal.ImmutableKeyValuePairs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

Expand Down Expand Up @@ -51,9 +55,121 @@ public ExtendedAttributesBuilder toBuilder() {
@Override
@Nullable
public <T> T get(ExtendedAttributeKey<T> key) {
if (key == null) {
return null;
}
if (key.getType() == ExtendedAttributeType.VALUE) {
return (T) getAsValue(key.getKey());
}
// Check if we're looking for an array type but have a VALUE with empty array
if (isArrayType(key.getType())) {
T value = (T) super.get(key);
if (value == null) {
// Check if there's a VALUE with the same key that contains an empty array
Value<?> valueAttr = getValueAttribute(key.getKey());
if (valueAttr != null && isEmptyArray(valueAttr)) {
return (T) Collections.emptyList();
}
}
return value;
}
return (T) super.get(key);
}

private static boolean isArrayType(ExtendedAttributeType type) {
return type == ExtendedAttributeType.STRING_ARRAY
|| type == ExtendedAttributeType.LONG_ARRAY
|| type == ExtendedAttributeType.DOUBLE_ARRAY
|| type == ExtendedAttributeType.BOOLEAN_ARRAY;
}

@Nullable
private Value<?> getValueAttribute(String keyName) {
List<Object> data = data();
for (int i = 0; i < data.size(); i += 2) {
ExtendedAttributeKey<?> currentKey = (ExtendedAttributeKey<?>) data.get(i);
if (currentKey.getKey().equals(keyName)
&& currentKey.getType() == ExtendedAttributeType.VALUE) {
return (Value<?>) data.get(i + 1);
}
}
return null;
}

private static boolean isEmptyArray(Value<?> value) {
if (value.getType() != ValueType.ARRAY) {
return false;
}
@SuppressWarnings("unchecked")
List<Value<?>> arrayValues = (List<Value<?>>) value.getValue();
return arrayValues.isEmpty();
}

@Nullable
private Value<?> getAsValue(String keyName) {
// Find any attribute with the same key name and convert it to Value
List<Object> data = data();
for (int i = 0; i < data.size(); i += 2) {
ExtendedAttributeKey<?> currentKey = (ExtendedAttributeKey<?>) data.get(i);
if (currentKey.getKey().equals(keyName)) {
Object value = data.get(i + 1);
return asValue(currentKey.getType(), value);
}
}
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this implementation benefit from #7076?

}

@SuppressWarnings("unchecked")
@Nullable
private static Value<?> asValue(ExtendedAttributeType type, Object value) {
switch (type) {
case STRING:
return Value.of((String) value);
case LONG:
return Value.of((Long) value);
case DOUBLE:
return Value.of((Double) value);
case BOOLEAN:
return Value.of((Boolean) value);
case STRING_ARRAY:
List<String> stringList = (List<String>) value;
Value<?>[] stringValues = new Value<?>[stringList.size()];
for (int i = 0; i < stringList.size(); i++) {
stringValues[i] = Value.of(stringList.get(i));
}
return Value.of(stringValues);
case LONG_ARRAY:
List<Long> longList = (List<Long>) value;
Value<?>[] longValues = new Value<?>[longList.size()];
for (int i = 0; i < longList.size(); i++) {
longValues[i] = Value.of(longList.get(i));
}
return Value.of(longValues);
case DOUBLE_ARRAY:
List<Double> doubleList = (List<Double>) value;
Value<?>[] doubleValues = new Value<?>[doubleList.size()];
for (int i = 0; i < doubleList.size(); i++) {
doubleValues[i] = Value.of(doubleList.get(i));
}
return Value.of(doubleValues);
case BOOLEAN_ARRAY:
List<Boolean> booleanList = (List<Boolean>) value;
Value<?>[] booleanValues = new Value<?>[booleanList.size()];
for (int i = 0; i < booleanList.size(); i++) {
booleanValues[i] = Value.of(booleanList.get(i));
}
return Value.of(booleanValues);
case VALUE:
// Already a Value
return (Value<?>) value;
case EXTENDED_ATTRIBUTES:
// Cannot convert EXTENDED_ATTRIBUTES to Value
return null;
}
// Should not reach here
return null;
}

@SuppressWarnings("unchecked")
@Override
public Attributes asAttributes() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

package io.opentelemetry.api.incubator.common;

import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.booleanArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.booleanKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.doubleArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.doubleKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.longArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.longKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.stringArrayKey;
import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.stringKey;

import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.common.ValueType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -36,11 +47,118 @@ public <T> ExtendedAttributesBuilder put(ExtendedAttributeKey<T> key, T value) {
if (key == null || key.getKey().isEmpty() || value == null) {
return this;
}
if (key.getType() == ExtendedAttributeType.VALUE && value instanceof Value) {
putValue(key, (Value<?>) value);
return this;
}
data.add(key);
data.add(value);
return this;
}

@SuppressWarnings("unchecked")
private void putValue(ExtendedAttributeKey<?> key, Value<?> valueObj) {
// Convert VALUE type to narrower type when possible
String keyName = key.getKey();
switch (valueObj.getType()) {
case STRING:
put(stringKey(keyName), ((Value<String>) valueObj).getValue());
return;
case LONG:
put(longKey(keyName), ((Value<Long>) valueObj).getValue());
return;
case DOUBLE:
put(doubleKey(keyName), ((Value<Double>) valueObj).getValue());
return;
case BOOLEAN:
put(booleanKey(keyName), ((Value<Boolean>) valueObj).getValue());
return;
case ARRAY:
List<Value<?>> arrayValues = (List<Value<?>>) valueObj.getValue();
ExtendedAttributeType attributeType = attributeType(arrayValues);
switch (attributeType) {
case STRING_ARRAY:
List<String> strings = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
strings.add((String) v.getValue());
}
put(stringArrayKey(keyName), strings);
return;
case LONG_ARRAY:
List<Long> longs = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
longs.add((Long) v.getValue());
}
put(longArrayKey(keyName), longs);
return;
case DOUBLE_ARRAY:
List<Double> doubles = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
doubles.add((Double) v.getValue());
}
put(doubleArrayKey(keyName), doubles);
return;
case BOOLEAN_ARRAY:
List<Boolean> booleans = new ArrayList<>(arrayValues.size());
for (Value<?> v : arrayValues) {
booleans.add((Boolean) v.getValue());
}
put(booleanArrayKey(keyName), booleans);
return;
case VALUE:
// Not coercible (empty, non-homogeneous, or unsupported element type)
data.add(key);
data.add(valueObj);
return;
case EXTENDED_ATTRIBUTES:
// Not coercible
data.add(key);
data.add(valueObj);
return;
default:
throw new IllegalArgumentException("Unexpected array attribute type: " + attributeType);
}
case KEY_VALUE_LIST:
case BYTES:
// Keep as VALUE type
data.add(key);
data.add(valueObj);
return;
}
}

/**
* Returns the ExtendedAttributeType for a homogeneous array (STRING_ARRAY, LONG_ARRAY,
* DOUBLE_ARRAY, or BOOLEAN_ARRAY), or VALUE if the array is empty, non-homogeneous, or contains
* unsupported element types.
*/
private static ExtendedAttributeType attributeType(List<Value<?>> arrayValues) {
if (arrayValues.isEmpty()) {
return ExtendedAttributeType.VALUE;
}
ValueType elementType = arrayValues.get(0).getType();
for (Value<?> v : arrayValues) {
if (v.getType() != elementType) {
return ExtendedAttributeType.VALUE;
}
}
switch (elementType) {
case STRING:
return ExtendedAttributeType.STRING_ARRAY;
case LONG:
return ExtendedAttributeType.LONG_ARRAY;
case DOUBLE:
return ExtendedAttributeType.DOUBLE_ARRAY;
case BOOLEAN:
return ExtendedAttributeType.BOOLEAN_ARRAY;
case ARRAY:
case KEY_VALUE_LIST:
case BYTES:
return ExtendedAttributeType.VALUE;
}
throw new IllegalArgumentException("Unsupported element type: " + elementType);
}

@Override
public ExtendedAttributesBuilder removeIf(Predicate<ExtendedAttributeKey<?>> predicate) {
if (predicate == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.opentelemetry.api.incubator.common;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.incubator.internal.InternalExtendedAttributeKeyImpl;
import java.util.List;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -93,8 +94,31 @@ static ExtendedAttributeKey<List<Double>> doubleArrayKey(String key) {
return fromAttributeKey(AttributeKey.doubleArrayKey(key));
}

/** Returns a new ExtendedAttributeKey for Map valued attributes. */
/**
* Returns a new ExtendedAttributeKey for {@link ExtendedAttributes} valued attributes.
*
* @deprecated Use {@link #valueKey(String)} in combination with {@link Value#of(java.util.Map)}
* instead.
*/
@Deprecated
@SuppressWarnings("deprecation")
static ExtendedAttributeKey<ExtendedAttributes> extendedAttributesKey(String key) {
return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES);
}

/**
* Returns a new ExtendedAttributeKey for {@link Value} valued attributes.
*
* <p>Simple attributes ({@link ExtendedAttributeType#STRING}, {@link ExtendedAttributeType#LONG},
* {@link ExtendedAttributeType#DOUBLE}, {@link ExtendedAttributeType#BOOLEAN}, {@link
* ExtendedAttributeType#STRING_ARRAY}, {@link ExtendedAttributeType#LONG_ARRAY}, {@link
* ExtendedAttributeType#DOUBLE_ARRAY}, {@link ExtendedAttributeType#BOOLEAN_ARRAY}) SHOULD be
* used whenever possible. Instrumentations SHOULD assume that backends do not index individual
* properties of complex attributes, that querying or aggregating on such properties is
* inefficient and complicated, and that reporting complex attributes carries higher performance
* overhead.
*/
static ExtendedAttributeKey<Value<?>> valueKey(String key) {
return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.VALUE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@ public enum ExtendedAttributeType {
LONG_ARRAY,
DOUBLE_ARRAY,
// Extended types unique to ExtendedAttributes
EXTENDED_ATTRIBUTES;
/**
* Complex attribute type for {@link io.opentelemetry.api.common.Value}-based maps.
*
* @deprecated Use {@link #VALUE} with {@link io.opentelemetry.api.common.Value}-based maps
* instead.
*/
@Deprecated
EXTENDED_ATTRIBUTES,
VALUE;
}
Loading
Loading