From ab1d8c612ecc3200360371bf03fc38f543ed0dcf Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 16 Sep 2025 10:20:11 -0700 Subject: [PATCH 1/5] POC: Complex attributes (Option A) --- .../io/opentelemetry/api/common/AttributeKey.java | 12 ++++++++++++ .../io/opentelemetry/api/common/AttributeType.java | 5 ++++- .../main/java/io/opentelemetry/api/common/Value.java | 4 ++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/common/AttributeKey.java b/api/all/src/main/java/io/opentelemetry/api/common/AttributeKey.java index 7d012aa14ca..ca47e8c3270 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/AttributeKey.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/AttributeKey.java @@ -70,4 +70,16 @@ static AttributeKey> longArrayKey(String key) { static AttributeKey> doubleArrayKey(String key) { return InternalAttributeKeyImpl.create(key, AttributeType.DOUBLE_ARRAY); } + + static AttributeKey byteArrayKey(String key) { + return InternalAttributeKeyImpl.create(key, AttributeType.BYTE_ARRAY); + } + + static AttributeKey>> valueArrayKey(String key) { + return InternalAttributeKeyImpl.create(key, AttributeType.VALUE_ARRAY); + } + + static AttributeKey mapKey(String key) { + return InternalAttributeKeyImpl.create(key, AttributeType.MAP); + } } diff --git a/api/all/src/main/java/io/opentelemetry/api/common/AttributeType.java b/api/all/src/main/java/io/opentelemetry/api/common/AttributeType.java index 1c51e36d644..eb4d2739764 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/AttributeType.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/AttributeType.java @@ -17,5 +17,8 @@ public enum AttributeType { STRING_ARRAY, BOOLEAN_ARRAY, LONG_ARRAY, - DOUBLE_ARRAY + DOUBLE_ARRAY, + BYTE_ARRAY, + VALUE_ARRAY, + MAP } diff --git a/api/all/src/main/java/io/opentelemetry/api/common/Value.java b/api/all/src/main/java/io/opentelemetry/api/common/Value.java index a29be801e27..084fcb19479 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/Value.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/Value.java @@ -84,6 +84,10 @@ static Value> of(Map> value) { return KeyValueList.createFromMap(value); } + static Value> of(Attributes attributes) { + // TODO + } + /** Returns the type of this {@link Value}. Useful for building switch statements. */ ValueType getType(); From e2bf72d321e000be6fe986bb8f583dbe536e5a92 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 1 Nov 2025 16:57:26 -0700 Subject: [PATCH 2/5] Implementation --- .../api/common/AttributesBuilder.java | 16 +++++ .../io/opentelemetry/api/common/Value.java | 4 -- .../common/ExtendedAttributeType.java | 3 + .../common/ExtendedAttributesBuilder.java | 16 +++++ .../InternalExtendedAttributeKeyImpl.java | 17 ++++++ .../common/ExtendedAttributeKeyTest.java | 15 +++++ .../common/ExtendedAttributesTest.java | 37 ++++++++++++ .../current_vs_latest/opentelemetry-api.txt | 20 ++++++- .../AttributeKeyValueStatelessMarshaler.java | 35 +++++++++++ .../internal/otlp/BytesAnyValueMarshaler.java | 4 ++ ...edAttributeKeyValueStatelessMarshaler.java | 34 +++++++++++ .../internal/otlp/IncubatingUtil.java | 9 +++ .../internal/otlp/KeyValueMarshaler.java | 8 +++ .../internal/otlp/MapAnyValueMarshaler.java | 59 +++++++++++++++++++ .../otlp/MapAnyValueStatelessMarshaler.java | 35 +++++++++++ .../prometheus/Otel2PrometheusConverter.java | 1 + .../zipkin/OtelToZipkinSpanTransformer.java | 1 + .../sdk/internal/AttributeUtil.java | 18 +++++- .../testing/assertj/AttributeAssertion.java | 7 +++ .../testing/assertj/LogRecordDataAssert.java | 6 ++ 20 files changed, 339 insertions(+), 6 deletions(-) create mode 100644 exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueMarshaler.java create mode 100644 exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueStatelessMarshaler.java diff --git a/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java b/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java index 6623d470137..00978f6cda5 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java @@ -164,6 +164,22 @@ default AttributesBuilder put(String key, boolean... value) { return put(booleanArrayKey(key), toList(value)); } + /** Puts a byte array attribute into this. */ + default AttributesBuilder put(String key, byte[] value) { + if (value == null) { + return this; + } + return put(AttributeKey.byteArrayKey(key), value); + } + + /** Puts an {@link Attributes} attribute into this. */ + default AttributesBuilder put(String key, Attributes value) { + if (value == null) { + return this; + } + return put(AttributeKey.mapKey(key), value); + } + /** * Puts all the provided attributes into this Builder. * diff --git a/api/all/src/main/java/io/opentelemetry/api/common/Value.java b/api/all/src/main/java/io/opentelemetry/api/common/Value.java index 084fcb19479..a29be801e27 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/Value.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/Value.java @@ -84,10 +84,6 @@ static Value> of(Map> value) { return KeyValueList.createFromMap(value); } - static Value> of(Attributes attributes) { - // TODO - } - /** Returns the type of this {@link Value}. Useful for building switch statements. */ ValueType getType(); diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java index 8d2c67181b6..cba706264eb 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java @@ -21,6 +21,9 @@ public enum ExtendedAttributeType { BOOLEAN_ARRAY, LONG_ARRAY, DOUBLE_ARRAY, + BYTE_ARRAY, + VALUE_ARRAY, + MAP, // Extended types unique to ExtendedAttributes EXTENDED_ATTRIBUTES; } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java index 1e0de3b4c38..1f6af694b74 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java @@ -97,6 +97,22 @@ default ExtendedAttributesBuilder put(String key, ExtendedAttributes value) return put(ExtendedAttributeKey.extendedAttributesKey(key), value); } + /** Puts a byte array attribute into this. */ + default ExtendedAttributesBuilder put(String key, byte[] value) { + if (value == null) { + return this; + } + return put(ExtendedAttributeKey.fromAttributeKey(AttributeKey.byteArrayKey(key)), value); + } + + /** Puts an {@link Attributes} attribute into this. */ + default ExtendedAttributesBuilder put(String key, Attributes value) { + if (value == null) { + return this; + } + return put(ExtendedAttributeKey.fromAttributeKey(AttributeKey.mapKey(key)), value); + } + /** * Puts a String array attribute into this. * diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java index e07f72f0121..f55aeea98a2 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java @@ -138,6 +138,14 @@ public static AttributeKey toAttributeKey(ExtendedAttributeKey extende case DOUBLE_ARRAY: return InternalAttributeKeyImpl.create( extendedAttributeKey.getKey(), AttributeType.DOUBLE_ARRAY); + case BYTE_ARRAY: + return InternalAttributeKeyImpl.create( + extendedAttributeKey.getKey(), AttributeType.BYTE_ARRAY); + case VALUE_ARRAY: + return InternalAttributeKeyImpl.create( + extendedAttributeKey.getKey(), AttributeType.VALUE_ARRAY); + case MAP: + return InternalAttributeKeyImpl.create(extendedAttributeKey.getKey(), AttributeType.MAP); case EXTENDED_ATTRIBUTES: return null; } @@ -172,6 +180,15 @@ public static ExtendedAttributeKey toExtendedAttributeKey(AttributeKey case DOUBLE_ARRAY: return InternalExtendedAttributeKeyImpl.create( attributeKey.getKey(), ExtendedAttributeType.DOUBLE_ARRAY); + case BYTE_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.BYTE_ARRAY); + case VALUE_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.VALUE_ARRAY); + case MAP: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.MAP); } throw new IllegalArgumentException("Unrecognized attributeKey type: " + attributeKey.getType()); } diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java index d2f77625d1f..e9fdefb6ccc 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java @@ -76,6 +76,21 @@ private static Stream attributeKeyArgs() { "key", ExtendedAttributeType.DOUBLE_ARRAY, AttributeKey.doubleArrayKey("key")), + Arguments.of( + ExtendedAttributeKey.fromAttributeKey(AttributeKey.byteArrayKey("key")), + "key", + ExtendedAttributeType.BYTE_ARRAY, + AttributeKey.byteArrayKey("key")), + Arguments.of( + ExtendedAttributeKey.fromAttributeKey(AttributeKey.valueArrayKey("key")), + "key", + ExtendedAttributeType.VALUE_ARRAY, + AttributeKey.valueArrayKey("key")), + Arguments.of( + ExtendedAttributeKey.fromAttributeKey(AttributeKey.mapKey("key")), + "key", + ExtendedAttributeType.MAP, + AttributeKey.mapKey("key")), Arguments.of( ExtendedAttributeKey.extendedAttributesKey("key"), "key", diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java index 4481cead3d0..bf9ef7a35d2 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java @@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -246,6 +247,27 @@ private static Stream attributesArgs() { .put(ExtendedAttributeKey.doubleArrayKey("key"), Arrays.asList(1.1, 2.2)) .build(), ImmutableMap.builder().put("key", Arrays.asList(1.1, 2.2)).build()), + Arguments.of( + ExtendedAttributes.builder().put("key", new byte[] {1, 2}).build(), + ImmutableMap.builder().put("key", new byte[] {1, 2}).build()), + Arguments.of( + ExtendedAttributes.builder() + .put( + ExtendedAttributeKey.fromAttributeKey(AttributeKey.valueArrayKey("key")), + Arrays.asList(Value.of("one"), Value.of(2L))) + .build(), + ImmutableMap.builder() + .put("key", Arrays.asList(Value.of("one"), Value.of(2L))) + .build()), + Arguments.of( + ExtendedAttributes.builder() + .put( + ExtendedAttributeKey.fromAttributeKey(AttributeKey.mapKey("key")), + Attributes.builder().put("child", "value").build()) + .build(), + ImmutableMap.builder() + .put("key", Attributes.builder().put("child", "value").build()) + .build()), Arguments.of( ExtendedAttributes.builder() .put( @@ -314,6 +336,12 @@ private static ExtendedAttributeKey getKey(String key, Object value) { return ExtendedAttributeKey.longArrayKey(key); case DOUBLE_ARRAY: return ExtendedAttributeKey.doubleArrayKey(key); + case BYTE_ARRAY: + return ExtendedAttributeKey.fromAttributeKey(AttributeKey.byteArrayKey(key)); + case VALUE_ARRAY: + return ExtendedAttributeKey.fromAttributeKey(AttributeKey.valueArrayKey(key)); + case MAP: + return ExtendedAttributeKey.fromAttributeKey(AttributeKey.mapKey(key)); case EXTENDED_ATTRIBUTES: return ExtendedAttributeKey.extendedAttributesKey(key); } @@ -334,11 +362,17 @@ private static ExtendedAttributeType getType(Object value) { if ((value instanceof Double) || (value instanceof Float)) { return ExtendedAttributeType.DOUBLE; } + if (value instanceof byte[]) { + return ExtendedAttributeType.BYTE_ARRAY; + } if (value instanceof List) { List list = (List) value; if (list.isEmpty()) { throw new IllegalArgumentException("Empty list"); } + if (list.get(0) instanceof Value) { + return ExtendedAttributeType.VALUE_ARRAY; + } if (list.get(0) instanceof String) { return ExtendedAttributeType.STRING_ARRAY; } @@ -352,6 +386,9 @@ private static ExtendedAttributeType getType(Object value) { return ExtendedAttributeType.DOUBLE_ARRAY; } } + if (value instanceof Attributes) { + return ExtendedAttributeType.MAP; + } if ((value instanceof Map)) { return ExtendedAttributeType.EXTENDED_ATTRIBUTES; } diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index ba34d454891..32edaf1e866 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,2 +1,20 @@ Comparing source compatibility of opentelemetry-api-1.56.0-SNAPSHOT.jar against opentelemetry-api-1.55.0.jar -No changes. \ No newline at end of file +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.common.AttributeKey (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + GENERIC TEMPLATES: === T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.AttributeKey byteArrayKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.AttributeKey mapKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.AttributeKey>> valueArrayKey(java.lang.String) +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.common.AttributesBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributesBuilder put(java.lang.String, byte[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributesBuilder put(java.lang.String, io.opentelemetry.api.common.Attributes) +*** MODIFIED ENUM: PUBLIC FINAL io.opentelemetry.api.common.AttributeType (compatible) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.AttributeType BYTE_ARRAY + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.AttributeType VALUE_ARRAY + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.AttributeType MAP +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.common.Value (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + GENERIC TEMPLATES: === T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.Value> of(io.opentelemetry.api.common.Attributes) diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/AttributeKeyValueStatelessMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/AttributeKeyValueStatelessMarshaler.java index 3fb1f7c25f6..1045cbbbdce 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/AttributeKeyValueStatelessMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/AttributeKeyValueStatelessMarshaler.java @@ -7,7 +7,10 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; import io.opentelemetry.api.internal.InternalAttributeKeyImpl; +import io.opentelemetry.exporter.internal.marshal.CodedOutputStream; import io.opentelemetry.exporter.internal.marshal.MarshalerContext; import io.opentelemetry.exporter.internal.marshal.MarshalerUtil; import io.opentelemetry.exporter.internal.marshal.Serializer; @@ -100,6 +103,21 @@ public int getBinarySerializedSize( (List) value, AttributeArrayAnyValueStatelessMarshaler.INSTANCE, context); + case BYTE_ARRAY: + return AnyValue.BYTES_VALUE.getTagSize() + + CodedOutputStream.computeByteArraySizeNoTag((byte[]) value); + case VALUE_ARRAY: + return StatelessMarshalerUtil.sizeMessageWithContext( + AnyValue.ARRAY_VALUE, + (List>) value, + ArrayAnyValueStatelessMarshaler.INSTANCE, + context); + case MAP: + return StatelessMarshalerUtil.sizeMessageWithContext( + AnyValue.KVLIST_VALUE, + (Attributes) value, + MapAnyValueStatelessMarshaler.INSTANCE, + context); } // Error prone ensures the switch statement is complete, otherwise only can happen with // unaligned versions which are not supported. @@ -136,6 +154,23 @@ public void writeTo( AttributeArrayAnyValueStatelessMarshaler.INSTANCE, context); return; + case BYTE_ARRAY: + output.writeBytes(AnyValue.BYTES_VALUE, (byte[]) value); + return; + case VALUE_ARRAY: + output.serializeMessageWithContext( + AnyValue.ARRAY_VALUE, + (List>) value, + ArrayAnyValueStatelessMarshaler.INSTANCE, + context); + return; + case MAP: + output.serializeMessageWithContext( + AnyValue.KVLIST_VALUE, + (Attributes) value, + MapAnyValueStatelessMarshaler.INSTANCE, + context); + return; } // Error prone ensures the switch statement is complete, otherwise only can happen with // unaligned versions which are not supported. diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/BytesAnyValueMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/BytesAnyValueMarshaler.java index d0a781039be..a7a218cd1ac 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/BytesAnyValueMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/BytesAnyValueMarshaler.java @@ -27,6 +27,10 @@ static MarshalerWithSize create(ByteBuffer value) { return new BytesAnyValueMarshaler(bytes); } + static MarshalerWithSize create(byte[] value) { + return new BytesAnyValueMarshaler(value); + } + @Override public void writeTo(Serializer output) throws IOException { // Do not call serialize* method because we always have to write the message tag even if the diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java index de31139abcb..241002b9cd4 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java @@ -5,6 +5,8 @@ package io.opentelemetry.exporter.internal.otlp; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.common.ExtendedAttributeType; import io.opentelemetry.api.incubator.common.ExtendedAttributes; @@ -168,6 +170,21 @@ public int getBinarySerializedSize( (List) value, AttributeArrayAnyValueStatelessMarshaler.INSTANCE, context); + case VALUE_ARRAY: + return StatelessMarshalerUtil.sizeMessageWithContext( + AnyValue.ARRAY_VALUE, + (List>) value, + ArrayAnyValueStatelessMarshaler.INSTANCE, + context); + case BYTE_ARRAY: + return AnyValue.BYTES_VALUE.getTagSize() + + CodedOutputStream.computeByteArraySizeNoTag((byte[]) value); + case MAP: + return StatelessMarshalerUtil.sizeMessageWithContext( + AnyValue.KVLIST_VALUE, + (Attributes) value, + MapAnyValueStatelessMarshaler.INSTANCE, + context); case EXTENDED_ATTRIBUTES: return StatelessMarshalerUtil.sizeMessageWithContext( AnyValue.KVLIST_VALUE, @@ -213,6 +230,23 @@ public void writeTo( AttributeArrayAnyValueStatelessMarshaler.INSTANCE, context); return; + case VALUE_ARRAY: + output.serializeMessageWithContext( + AnyValue.ARRAY_VALUE, + (List>) value, + ArrayAnyValueStatelessMarshaler.INSTANCE, + context); + return; + case BYTE_ARRAY: + output.writeBytes(AnyValue.BYTES_VALUE, (byte[]) value); + return; + case MAP: + output.serializeMessageWithContext( + AnyValue.KVLIST_VALUE, + (Attributes) value, + MapAnyValueStatelessMarshaler.INSTANCE, + context); + return; case EXTENDED_ATTRIBUTES: output.serializeMessageWithContext( AnyValue.KVLIST_VALUE, diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java index 90660fd5a85..e68104f8975 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java @@ -5,6 +5,8 @@ package io.opentelemetry.exporter.internal.otlp; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.incubator.internal.InternalExtendedAttributeKeyImpl; @@ -110,6 +112,13 @@ private static KeyValueMarshaler create(ExtendedAttributeKey attributeKey, Ob case DOUBLE_ARRAY: return new KeyValueMarshaler( keyUtf8, ArrayAnyValueMarshaler.createDouble((List) value)); + case BYTE_ARRAY: + return new KeyValueMarshaler(keyUtf8, BytesAnyValueMarshaler.create((byte[]) value)); + case VALUE_ARRAY: + return new KeyValueMarshaler( + keyUtf8, ArrayAnyValueMarshaler.createAnyValue((List>) value)); + case MAP: + return new KeyValueMarshaler(keyUtf8, MapAnyValueMarshaler.create((Attributes) value)); case EXTENDED_ATTRIBUTES: return new KeyValueMarshaler( keyUtf8, diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueMarshaler.java index ec7dd47f10b..6f1bdb483b1 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueMarshaler.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.KeyValue; +import io.opentelemetry.api.common.Value; import io.opentelemetry.api.internal.InternalAttributeKeyImpl; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.exporter.internal.marshal.MarshalerUtil; @@ -118,6 +119,13 @@ private static KeyValueMarshaler create(AttributeKey attributeKey, Object val case DOUBLE_ARRAY: return new KeyValueMarshaler( keyUtf8, ArrayAnyValueMarshaler.createDouble((List) value)); + case BYTE_ARRAY: + return new KeyValueMarshaler(keyUtf8, BytesAnyValueMarshaler.create((byte[]) value)); + case VALUE_ARRAY: + return new KeyValueMarshaler( + keyUtf8, ArrayAnyValueMarshaler.createAnyValue((List>) value)); + case MAP: + return new KeyValueMarshaler(keyUtf8, MapAnyValueMarshaler.create((Attributes) value)); } // Error prone ensures the switch statement is complete, otherwise only can happen with // unaligned versions which are not supported. diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueMarshaler.java new file mode 100644 index 00000000000..f8c889b1cf2 --- /dev/null +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueMarshaler.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal.otlp; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.exporter.internal.marshal.Marshaler; +import io.opentelemetry.exporter.internal.marshal.MarshalerUtil; +import io.opentelemetry.exporter.internal.marshal.MarshalerWithSize; +import io.opentelemetry.exporter.internal.marshal.Serializer; +import io.opentelemetry.proto.common.v1.internal.AnyValue; +import io.opentelemetry.proto.common.v1.internal.KeyValueList; +import java.io.IOException; + +/** Marshals {@link Attributes} into OTLP {@link AnyValue#KVLIST_VALUE} messages. */ +final class MapAnyValueMarshaler extends MarshalerWithSize { + + private final Marshaler value; + + private MapAnyValueMarshaler(MapValueMarshaler value) { + super(calculateSize(value)); + this.value = value; + } + + static MarshalerWithSize create(Attributes attributes) { + KeyValueMarshaler[] marshalers = KeyValueMarshaler.createForAttributes(attributes); + return new MapAnyValueMarshaler(new MapValueMarshaler(marshalers)); + } + + @Override + public void writeTo(Serializer output) throws IOException { + output.serializeMessage(AnyValue.KVLIST_VALUE, value); + } + + private static int calculateSize(Marshaler value) { + return MarshalerUtil.sizeMessage(AnyValue.KVLIST_VALUE, value); + } + + private static class MapValueMarshaler extends MarshalerWithSize { + + private final Marshaler[] values; + + private MapValueMarshaler(KeyValueMarshaler[] values) { + super(calculateSize(values)); + this.values = values; + } + + @Override + public void writeTo(Serializer output) throws IOException { + output.serializeRepeatedMessage(KeyValueList.VALUES, values); + } + + private static int calculateSize(Marshaler[] values) { + return MarshalerUtil.sizeRepeatedMessage(KeyValueList.VALUES, values); + } + } +} diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueStatelessMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueStatelessMarshaler.java new file mode 100644 index 00000000000..f3e252acd9f --- /dev/null +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/MapAnyValueStatelessMarshaler.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal.otlp; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.exporter.internal.marshal.MarshalerContext; +import io.opentelemetry.exporter.internal.marshal.Serializer; +import io.opentelemetry.exporter.internal.marshal.StatelessMarshaler; +import io.opentelemetry.exporter.internal.marshal.StatelessMarshalerUtil; +import io.opentelemetry.proto.common.v1.internal.KeyValueList; +import java.io.IOException; + +/** Marshals {@link Attributes} into OTLP {@link KeyValueList} instances. */ +final class MapAnyValueStatelessMarshaler implements StatelessMarshaler { + + static final MapAnyValueStatelessMarshaler INSTANCE = new MapAnyValueStatelessMarshaler(); + + private MapAnyValueStatelessMarshaler() {} + + @Override + public void writeTo(Serializer output, Attributes attributes, MarshalerContext context) + throws IOException { + output.serializeRepeatedMessageWithContext( + KeyValueList.VALUES, attributes, AttributeKeyValueStatelessMarshaler.INSTANCE, context); + } + + @Override + public int getBinarySerializedSize(Attributes attributes, MarshalerContext context) { + return StatelessMarshalerUtil.sizeRepeatedMessageWithContext( + KeyValueList.VALUES, attributes, AttributeKeyValueStatelessMarshaler.INSTANCE, context); + } +} diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java index 1e952b36a09..9ebcd8fc8e4 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java @@ -650,6 +650,7 @@ private static String toLabelValue(AttributeType type, Object attributeValue) { "Unexpected label value of %s for %s", attributeValue.getClass().getName(), type.name())); } + // TODO: BYTE_ARRAY, VALUE_ARRAY, MAP } throw new IllegalStateException("Unrecognized AttributeType: " + type); } diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java index 7728d88463f..ce5c3f07959 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java @@ -223,6 +223,7 @@ private static String valueToString(AttributeKey key, Object attributeValue) case LONG_ARRAY: case DOUBLE_ARRAY: return commaSeparated((List) attributeValue); + // TODO: BYTE_ARRAY, VALUE_ARRAY, MAP } throw new IllegalStateException("Unknown attribute type: " + type); } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java index de7fac88eba..bdeebff355e 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java @@ -56,9 +56,14 @@ public static Attributes applyAttributesLimit( } private static boolean isValidLength(Object value, int lengthLimit) { + if (value instanceof Attributes) { + return allMatch( + ((Attributes) value).asMap().values(), entry -> isValidLength(entry, lengthLimit)); + } if (value instanceof List) { return allMatch((List) value, entry -> isValidLength(entry, lengthLimit)); - } else if (value instanceof String) { + } + if (value instanceof String) { return ((String) value).length() < lengthLimit; } return true; @@ -81,6 +86,17 @@ public static Object applyAttributeLengthLimit(Object value, int lengthLimit) { if (lengthLimit == Integer.MAX_VALUE) { return value; } + if (value instanceof Attributes) { + Attributes attributes = (Attributes) value; + AttributesBuilder builder = Attributes.builder(); + attributes.forEach( + (key, attributeValue) -> { + @SuppressWarnings("unchecked") + AttributeKey castKey = (AttributeKey) key; + builder.put(castKey, applyAttributeLengthLimit(attributeValue, lengthLimit)); + }); + return builder.build(); + } if (value instanceof List) { List values = (List) value; List response = new ArrayList<>(values.size()); diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AttributeAssertion.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AttributeAssertion.java index 971ff9446ff..a6ad95792f5 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AttributeAssertion.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/AttributeAssertion.java @@ -9,6 +9,7 @@ import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import java.util.List; import java.util.function.Consumer; import javax.annotation.Nullable; @@ -58,6 +59,12 @@ static AttributeAssertion create( case LONG_ARRAY: case DOUBLE_ARRAY: return assertThat((List) value); + case BYTE_ARRAY: + return assertThat((byte[]) value); + case VALUE_ARRAY: + return assertThat((List) value); + case MAP: + return assertThat((Attributes) value); } throw new IllegalArgumentException("Unknown type for key " + key); } diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java index d2f621629d7..ee01484ba9a 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/LogRecordDataAssert.java @@ -344,6 +344,12 @@ public LogRecordDataAssert hasBodyField(AttributeKey key, T value) { return hasBodyField( key.getKey(), Value.of(((List) value).stream().map(Value::of).collect(toList()))); + case BYTE_ARRAY: + return hasBodyField(key.getKey(), Value.of((byte[]) value)); + case VALUE_ARRAY: + return hasBodyField(key.getKey(), Value.of((List>) value)); + case MAP: + return hasBodyField(key.getKey(), Value.of((Attributes) value)); } return this; } From 43b668262fc35d7c6103f6d5416e668fa1ba3eaa Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 3 Nov 2025 19:18:41 -0800 Subject: [PATCH 3/5] Spans --- .../trace/ExtendedDefaultTracer.java | 6 ++ .../incubator/trace/ExtendedSpanBuilder.java | 12 +++ .../sdk/trace/ExtendedSdkSpanBuilder.java | 14 ++++ .../sdk/trace/ExtendedSpanBuilderTest.java | 78 +++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedDefaultTracer.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedDefaultTracer.java index fe35d64666e..23c0161a984 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedDefaultTracer.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedDefaultTracer.java @@ -7,6 +7,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.propagation.ExtendedContextPropagators; import io.opentelemetry.api.internal.ApiUsageLogger; import io.opentelemetry.api.trace.Span; @@ -118,6 +119,11 @@ public NoopSpanBuilder setAttribute(AttributeKey key, T value) { return this; } + @Override + public NoopSpanBuilder setAttribute(ExtendedAttributeKey key, T value) { + return this; + } + @Override public NoopSpanBuilder setAllAttributes(Attributes attributes) { return this; diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java index 8474095f734..867be81f09c 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; @@ -127,6 +128,17 @@ void startAndRun( @Override ExtendedSpanBuilder setAttribute(AttributeKey key, T value); + /** + * Set an attribute. + * + *

NOTE: all standard {@link AttributeKey}-value pairs can also be represented as {@link + * ExtendedAttributeKey}-value pairs, but not all {@link ExtendedAttributeKey}-value pairs can be + * represented as standard {@link AttributeKey}-value pairs. From the standpoint of the emitted + * span, there is no difference between adding attributes using the standard or extended + * attribute APIs. + */ + ExtendedSpanBuilder setAttribute(ExtendedAttributeKey key, T value); + /** {@inheritDoc} */ @Override default ExtendedSpanBuilder setAllAttributes(Attributes attributes) { diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java index b68030202ce..984791ce980 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/ExtendedSdkSpanBuilder.java @@ -7,6 +7,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.propagation.ExtendedContextPropagators; import io.opentelemetry.api.incubator.trace.ExtendedSpanBuilder; import io.opentelemetry.api.incubator.trace.SpanCallable; @@ -94,6 +95,19 @@ public ExtendedSpanBuilder setAttribute(AttributeKey key, T value) { return this; } + @Override + public ExtendedSpanBuilder setAttribute(ExtendedAttributeKey key, T value) { + if (key == null || value == null) { + return this; + } + AttributeKey attributeKey = key.asAttributeKey(); + if (attributeKey == null || attributeKey.getKey().isEmpty()) { + return this; + } + super.setAttribute(attributeKey, value); + return this; + } + @Override public ExtendedSpanBuilder setStartTimestamp(long startTimestamp, TimeUnit unit) { super.setStartTimestamp(startTimestamp, unit); diff --git a/sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java b/sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java new file mode 100644 index 00000000000..3ec19118aa7 --- /dev/null +++ b/sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.trace.ExtendedSpanBuilder; +import io.opentelemetry.api.incubator.trace.ExtendedTracer; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ExtendedSpanBuilderTest { + + private InMemorySpanExporter spanExporter; + private SdkTracerProvider tracerProvider; + private ExtendedTracer tracer; + + @BeforeEach + void setUp() { + spanExporter = InMemorySpanExporter.create(); + tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) + .build(); + tracer = (ExtendedTracer) tracerProvider.get("test"); + } + + @AfterEach + void tearDown() throws Exception { + tracerProvider.close(); + } + + @Test + void setAttribute_withExtendedAttributeKeyDelegatesToSdk() { + ExtendedSpanBuilder spanBuilder = tracer.spanBuilder("span"); + + spanBuilder.setAttribute(ExtendedAttributeKey.stringKey("extended.string"), "value"); + spanBuilder.startSpan().end(); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans) + .hasSize(1) + .first() + .satisfies( + spanData -> + assertThat(spanData.getAttributes()) + .containsEntry(AttributeKey.stringKey("extended.string"), "value")); + } + + @Test + void setAttribute_withUnsupportedExtendedAttributeKeyIsNoop() { + ExtendedSpanBuilder spanBuilder = tracer.spanBuilder("span"); + + spanBuilder.setAttribute( + ExtendedAttributeKey.extendedAttributesKey("extended.map"), ExtendedAttributes.empty()); + spanBuilder.startSpan().end(); + + List spans = spanExporter.getFinishedSpanItems(); + assertThat(spans) + .hasSize(1) + .first() + .satisfies( + spanData -> + assertThat(spanData.getAttributes().asMap()) + .doesNotContainKey(AttributeKey.stringKey("extended.map"))); + } +} From 4c8c5ea7ade9407ca5e3063ca32f113c9219959b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 4 Nov 2025 08:02:11 -0800 Subject: [PATCH 4/5] spans --- .../common/ArrayBackedExtendedAttributes.java | 7 ++- .../common/ExtendedAttributeKey.java | 17 +++++-- .../common/ExtendedAttributeType.java | 5 +-- .../incubator/common/ExtendedAttributes.java | 17 +++++-- .../common/ExtendedAttributesBuilder.java | 9 ++-- .../InternalExtendedAttributeKeyImpl.java | 2 - .../incubator/trace/ExtendedSpanBuilder.java | 4 +- .../common/ExtendedAttributeKeyTest.java | 10 ++--- .../common/ExtendedAttributesTest.java | 45 ++++++++++++------- .../logs/ExtendedLogsBridgeApiUsageTest.java | 24 ++++++++-- ...edAttributeKeyValueStatelessMarshaler.java | 28 ++++++------ .../internal/otlp/IncubatingUtil.java | 13 +++--- .../prometheus/Otel2PrometheusConverter.java | 2 +- .../zipkin/OtelToZipkinSpanTransformer.java | 2 +- .../sdk/internal/AttributeUtil.java | 19 ++++++++ .../sdk/trace/ExtendedSpanBuilderTest.java | 4 +- 16 files changed, 144 insertions(+), 64 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributes.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributes.java index a864a1d9585..c0ffa8943b5 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributes.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributes.java @@ -64,7 +64,12 @@ public Attributes asAttributes() { AttributeKey attributeKey = (AttributeKey) extendedAttributeKey.asAttributeKey(); if (attributeKey != null) { - builder.put(attributeKey, value); + if (attributeKey.getType() == io.opentelemetry.api.common.AttributeType.MAP + && value instanceof ExtendedAttributes) { + builder.put(attributeKey, ((ExtendedAttributes) value).asAttributes()); + } else { + builder.put(attributeKey, value); + } } }); attributes = builder.build(); diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKey.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKey.java index 13357a31a56..276cb28cbdf 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKey.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKey.java @@ -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; @@ -93,8 +94,18 @@ static ExtendedAttributeKey> doubleArrayKey(String key) { return fromAttributeKey(AttributeKey.doubleArrayKey(key)); } - /** Returns a new ExtendedAttributeKey for Map valued attributes. */ - static ExtendedAttributeKey extendedAttributesKey(String key) { - return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES); + /** Returns a new ExtendedAttributeKey for byte array valued attributes. */ + static ExtendedAttributeKey byteArrayKey(String key) { + return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.BYTE_ARRAY); + } + + /** Returns a new ExtendedAttributeKey for List<Value<?>> valued attributes. */ + static ExtendedAttributeKey>> valueArrayKey(String key) { + return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.VALUE_ARRAY); + } + + /** Returns a new ExtendedAttributeKey for {@link ExtendedAttributes} valued attributes. */ + static ExtendedAttributeKey mapKey(String key) { + return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.MAP); } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java index cba706264eb..8ee30f418a8 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java @@ -21,9 +21,8 @@ public enum ExtendedAttributeType { BOOLEAN_ARRAY, LONG_ARRAY, DOUBLE_ARRAY, + // Extended types unique to ExtendedAttributes BYTE_ARRAY, VALUE_ARRAY, - MAP, - // Extended types unique to ExtendedAttributes - EXTENDED_ATTRIBUTES; + MAP; } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributes.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributes.java index 0fc88a2ea49..8ed2d634a04 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributes.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributes.java @@ -6,6 +6,7 @@ package io.opentelemetry.api.incubator.common; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributeType; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; import java.util.Map; @@ -17,9 +18,8 @@ * An immutable container for extended attributes. * *

"extended" refers an extended set of allowed value types compared to standard {@link - * Attributes}. Notably, {@link ExtendedAttributes} values can be of type {@link - * ExtendedAttributeType#EXTENDED_ATTRIBUTES}, allowing nested {@link ExtendedAttributes} of - * arbitrary depth. + * Attributes}. Notably, {@link ExtendedAttributes} values can be nested {@link ExtendedAttributes} + * via the {@link ExtendedAttributeType#MAP} type, allowing arbitrary depth. * *

Where standard {@link Attributes} are accepted everyone that OpenTelemetry represents key / * value pairs, {@link ExtendedAttributes} are only accepted in select places, such as log records @@ -53,11 +53,20 @@ public interface ExtendedAttributes { /** Returns the value for the given {@link AttributeKey}, or {@code null} if not found. */ @Nullable + @SuppressWarnings("unchecked") default T get(AttributeKey key) { if (key == null) { return null; } - return get(ExtendedAttributeKey.fromAttributeKey(key)); + ExtendedAttributeKey extendedAttributeKey = ExtendedAttributeKey.fromAttributeKey(key); + Object value = get(extendedAttributeKey); + if (value == null) { + return null; + } + if (key.getType() == AttributeType.MAP && value instanceof ExtendedAttributes) { + return (T) ((ExtendedAttributes) value).asAttributes(); + } + return (T) value; } /** Returns the value for the given {@link ExtendedAttributeKey}, or {@code null} if not found. */ diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java index 1f6af694b74..68968fb38ee 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java @@ -93,8 +93,11 @@ default ExtendedAttributesBuilder put(String key, boolean value) { * * @return this Builder */ - default ExtendedAttributesBuilder put(String key, ExtendedAttributes value) { - return put(ExtendedAttributeKey.extendedAttributesKey(key), value); + default ExtendedAttributesBuilder put(String key, ExtendedAttributes value) { + if (value == null) { + return this; + } + return put(ExtendedAttributeKey.mapKey(key), value); } /** Puts a byte array attribute into this. */ @@ -102,7 +105,7 @@ default ExtendedAttributesBuilder put(String key, byte[] value) { if (value == null) { return this; } - return put(ExtendedAttributeKey.fromAttributeKey(AttributeKey.byteArrayKey(key)), value); + return put(ExtendedAttributeKey.byteArrayKey(key), value); } /** Puts an {@link Attributes} attribute into this. */ diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java index f55aeea98a2..701c6385790 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java @@ -146,8 +146,6 @@ public static AttributeKey toAttributeKey(ExtendedAttributeKey extende extendedAttributeKey.getKey(), AttributeType.VALUE_ARRAY); case MAP: return InternalAttributeKeyImpl.create(extendedAttributeKey.getKey(), AttributeType.MAP); - case EXTENDED_ATTRIBUTES: - return null; } throw new IllegalArgumentException( "Unrecognized extendedAttributeKey type: " + extendedAttributeKey.getType()); diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java index 867be81f09c..5ea318a731e 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/trace/ExtendedSpanBuilder.java @@ -134,8 +134,8 @@ void startAndRun( *

NOTE: all standard {@link AttributeKey}-value pairs can also be represented as {@link * ExtendedAttributeKey}-value pairs, but not all {@link ExtendedAttributeKey}-value pairs can be * represented as standard {@link AttributeKey}-value pairs. From the standpoint of the emitted - * span, there is no difference between adding attributes using the standard or extended - * attribute APIs. + * span, there is no difference between adding attributes using the standard or extended attribute + * APIs. */ ExtendedSpanBuilder setAttribute(ExtendedAttributeKey key, T value); diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java index e9fdefb6ccc..426319640b0 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java @@ -77,12 +77,12 @@ private static Stream attributeKeyArgs() { ExtendedAttributeType.DOUBLE_ARRAY, AttributeKey.doubleArrayKey("key")), Arguments.of( - ExtendedAttributeKey.fromAttributeKey(AttributeKey.byteArrayKey("key")), + ExtendedAttributeKey.byteArrayKey("key"), "key", ExtendedAttributeType.BYTE_ARRAY, AttributeKey.byteArrayKey("key")), Arguments.of( - ExtendedAttributeKey.fromAttributeKey(AttributeKey.valueArrayKey("key")), + ExtendedAttributeKey.valueArrayKey("key"), "key", ExtendedAttributeType.VALUE_ARRAY, AttributeKey.valueArrayKey("key")), @@ -92,9 +92,9 @@ private static Stream attributeKeyArgs() { ExtendedAttributeType.MAP, AttributeKey.mapKey("key")), Arguments.of( - ExtendedAttributeKey.extendedAttributesKey("key"), + ExtendedAttributeKey.mapKey("key"), "key", - ExtendedAttributeType.EXTENDED_ATTRIBUTES, - null)); + ExtendedAttributeType.MAP, + AttributeKey.mapKey("key"))); } } diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java index bf9ef7a35d2..7a27d296a4f 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java @@ -57,6 +57,10 @@ void get_AttributeKey(ExtendedAttributes extendedAttributes, Map Object actualValue = extendedAttributes.get(attributeKey); + if (actualValue instanceof Attributes && value instanceof Map) { + actualValue = toSimpleMap((Attributes) actualValue); + } + assertThat(actualValue) .describedAs(key + "(" + attributeKey.getType() + ")") .isEqualTo(value); @@ -96,9 +100,9 @@ private static void assertEquals( assertThat(actual.size()).isEqualTo(expected.size()); actual.forEach( (key, value) -> { - if (key.getType() == ExtendedAttributeType.EXTENDED_ATTRIBUTES) { + if (value instanceof ExtendedAttributes) { assertEquals( - ((ExtendedAttributes) value).asMap(), + toMap((ExtendedAttributes) value), (Map) expected.get(key.getKey())); return; } @@ -113,12 +117,15 @@ void asAttributes(ExtendedAttributes extendedAttributes, Map exp attributes.forEach( (key, value) -> { - assertThat(value).isEqualTo(expectedMap.get(key.getKey())); + Object expectedValue = expectedMap.get(key.getKey()); + if (value instanceof Attributes && expectedValue instanceof Map) { + assertThat(toSimpleMap((Attributes) value)).isEqualTo(expectedValue); + return; + } + assertThat(value).isEqualTo(expectedValue); }); - long expectedSize = - expectedMap.values().stream().filter(value -> !(value instanceof Map)).count(); - assertThat(attributes.size()).isEqualTo(expectedSize); + assertThat(attributes.size()).isEqualTo(expectedMap.size()); } @ParameterizedTest @@ -162,7 +169,7 @@ private static ExtendedAttributes fromMap(Map map) { map.forEach( (key, value) -> { ExtendedAttributeKey extendedAttributeKey = getKey(key, value); - if (extendedAttributeKey.getType() == ExtendedAttributeType.EXTENDED_ATTRIBUTES) { + if (value instanceof Map) { builder.put( (ExtendedAttributeKey) extendedAttributeKey, fromMap((Map) value)); @@ -271,7 +278,7 @@ private static Stream attributesArgs() { Arguments.of( ExtendedAttributes.builder() .put( - ExtendedAttributeKey.extendedAttributesKey("key"), + ExtendedAttributeKey.mapKey("key"), ExtendedAttributes.builder().put("child", "value").build()) .build(), ImmutableMap.builder() @@ -309,7 +316,7 @@ private static Map toMap(ExtendedAttributes extendedAttributes) Map map = new HashMap<>(); extendedAttributes.forEach( (key, value) -> { - if (key.getType() == ExtendedAttributeType.EXTENDED_ATTRIBUTES) { + if (value instanceof ExtendedAttributes) { map.put(key.getKey(), toMap((ExtendedAttributes) value)); return; } @@ -318,6 +325,13 @@ private static Map toMap(ExtendedAttributes extendedAttributes) return map; } + private static Map toSimpleMap(Attributes attributes) { + Map map = new HashMap<>(); + attributes.forEach( + (attributeKey, attributeValue) -> map.put(attributeKey.getKey(), attributeValue)); + return map; + } + private static ExtendedAttributeKey getKey(String key, Object value) { switch (getType(value)) { case STRING: @@ -337,13 +351,14 @@ private static ExtendedAttributeKey getKey(String key, Object value) { case DOUBLE_ARRAY: return ExtendedAttributeKey.doubleArrayKey(key); case BYTE_ARRAY: - return ExtendedAttributeKey.fromAttributeKey(AttributeKey.byteArrayKey(key)); + return ExtendedAttributeKey.byteArrayKey(key); case VALUE_ARRAY: - return ExtendedAttributeKey.fromAttributeKey(AttributeKey.valueArrayKey(key)); + return ExtendedAttributeKey.valueArrayKey(key); case MAP: - return ExtendedAttributeKey.fromAttributeKey(AttributeKey.mapKey(key)); - case EXTENDED_ATTRIBUTES: - return ExtendedAttributeKey.extendedAttributesKey(key); + if (value instanceof Attributes) { + return ExtendedAttributeKey.fromAttributeKey(AttributeKey.mapKey(key)); + } + return ExtendedAttributeKey.mapKey(key); } throw new IllegalArgumentException(); } @@ -390,7 +405,7 @@ private static ExtendedAttributeType getType(Object value) { return ExtendedAttributeType.MAP; } if ((value instanceof Map)) { - return ExtendedAttributeType.EXTENDED_ATTRIBUTES; + return ExtendedAttributeType.MAP; } throw new IllegalArgumentException("Unrecognized value type: " + value); } diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java index 05c3e65f859..4bdd19a6e74 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.logs.Logger; @@ -104,14 +105,18 @@ private static String flipCoin() { AttributeKey> doubleArrKey = AttributeKey.doubleArrayKey("acme.double_array"); // Extended keys - ExtendedAttributeKey mapKey = - ExtendedAttributeKey.extendedAttributesKey("acme.map"); + ExtendedAttributeKey mapKey = ExtendedAttributeKey.mapKey("acme.map"); + ExtendedAttributeKey bytesKey = ExtendedAttributeKey.byteArrayKey("acme.bytes"); + ExtendedAttributeKey>> extendedValueArrayKey = + ExtendedAttributeKey.valueArrayKey("acme.value_array"); @Test @SuppressLogger(ExtendedLogsBridgeApiUsageTest.class) void extendedAttributesUsage() { // Initialize from builder. Varargs style initialization (ExtendedAttributes.of(...) not // supported. + List> heterogeneousValues = + Arrays.asList(Value.of("value"), Value.of(1L), Value.of(true)); ExtendedAttributes extendedAttributes = ExtendedAttributes.builder() .put(strKey, "value") @@ -122,6 +127,8 @@ void extendedAttributesUsage() { .put(longArrKey, Arrays.asList(1L, 2L)) .put(booleanArrKey, Arrays.asList(true, false)) .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put(bytesKey, new byte[] {(byte) 1, (byte) 2}) + .put(extendedValueArrayKey, heterogeneousValues) .put( mapKey, ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) @@ -136,6 +143,9 @@ void extendedAttributesUsage() { assertThat(extendedAttributes.get(longArrKey)).isEqualTo(Arrays.asList(1L, 2L)); assertThat(extendedAttributes.get(booleanArrKey)).isEqualTo(Arrays.asList(true, false)); assertThat(extendedAttributes.get(doubleArrKey)).isEqualTo(Arrays.asList(1.1, 2.2)); + assertThat((byte[]) extendedAttributes.get(bytesKey)) + .containsExactly((byte) 1, (byte) 2); + assertThat(extendedAttributes.get(extendedValueArrayKey)).isEqualTo(heterogeneousValues); assertThat(extendedAttributes.get(mapKey)) .isEqualTo( ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()); @@ -148,7 +158,9 @@ void extendedAttributesUsage() { // acme.double_array(DOUBLE_ARRAY): [1.1, 2.2] // acme.long(LONG): 1 // acme.long_array(LONG_ARRAY): [1, 2] - // acme.map(EXTENDED_ATTRIBUTES): {childLong=1, childStr="value"} + // acme.bytes(BYTE_ARRAY): [1, 2] + // acme.value_array(VALUE_ARRAY): ["value", 1, true] + // acme.map(MAP): {childLong=1, childStr="value"} // acme.string(STRING): value // acme.string_array(STRING_ARRAY): [value1, value2] extendedAttributes.forEach( @@ -169,6 +181,8 @@ void logRecordBuilder_ExtendedAttributes() { .build(); Logger logger = loggerProvider.get("logger"); + List> heterogeneousValues = + Arrays.asList(Value.of("value"), Value.of(1L), Value.of(true)); // Can set either standard or extended attributes on ((ExtendedLogRecordBuilder) logger.logRecordBuilder()) @@ -181,6 +195,8 @@ void logRecordBuilder_ExtendedAttributes() { .setAttribute(longArrKey, Arrays.asList(1L, 2L)) .setAttribute(booleanArrKey, Arrays.asList(true, false)) .setAttribute(doubleArrKey, Arrays.asList(1.1, 2.2)) + .setAttribute(bytesKey, new byte[] {(byte) 1, (byte) 2}) + .setAttribute(extendedValueArrayKey, heterogeneousValues) .setAttribute( mapKey, ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) @@ -223,6 +239,8 @@ void logRecordBuilder_ExtendedAttributes() { .put(longArrKey, Arrays.asList(1L, 2L)) .put(booleanArrKey, Arrays.asList(true, false)) .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put(bytesKey, new byte[] {(byte) 1, (byte) 2}) + .put(extendedValueArrayKey, heterogeneousValues) .put( mapKey, ExtendedAttributes.builder() diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java index 241002b9cd4..00bcf01dcf8 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java @@ -180,17 +180,18 @@ public int getBinarySerializedSize( return AnyValue.BYTES_VALUE.getTagSize() + CodedOutputStream.computeByteArraySizeNoTag((byte[]) value); case MAP: + if (value instanceof ExtendedAttributes) { + return StatelessMarshalerUtil.sizeMessageWithContext( + AnyValue.KVLIST_VALUE, + (ExtendedAttributes) value, + ExtendedAttributesKeyValueListStatelessMarshaler.INSTANCE, + context); + } return StatelessMarshalerUtil.sizeMessageWithContext( AnyValue.KVLIST_VALUE, (Attributes) value, MapAnyValueStatelessMarshaler.INSTANCE, context); - case EXTENDED_ATTRIBUTES: - return StatelessMarshalerUtil.sizeMessageWithContext( - AnyValue.KVLIST_VALUE, - (ExtendedAttributes) value, - ExtendedAttributesKeyValueListStatelessMarshaler.INSTANCE, - context); } // Error prone ensures the switch statement is complete, otherwise only can happen with // unaligned versions which are not supported. @@ -241,19 +242,20 @@ public void writeTo( output.writeBytes(AnyValue.BYTES_VALUE, (byte[]) value); return; case MAP: + if (value instanceof ExtendedAttributes) { + output.serializeMessageWithContext( + AnyValue.KVLIST_VALUE, + (ExtendedAttributes) value, + ExtendedAttributesKeyValueListStatelessMarshaler.INSTANCE, + context); + return; + } output.serializeMessageWithContext( AnyValue.KVLIST_VALUE, (Attributes) value, MapAnyValueStatelessMarshaler.INSTANCE, context); return; - case EXTENDED_ATTRIBUTES: - output.serializeMessageWithContext( - AnyValue.KVLIST_VALUE, - (ExtendedAttributes) value, - ExtendedAttributesKeyValueListStatelessMarshaler.INSTANCE, - context); - return; } // Error prone ensures the switch statement is complete, otherwise only can happen with // unaligned versions which are not supported. diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java index e68104f8975..2e3d8c49454 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java @@ -118,13 +118,14 @@ private static KeyValueMarshaler create(ExtendedAttributeKey attributeKey, Ob return new KeyValueMarshaler( keyUtf8, ArrayAnyValueMarshaler.createAnyValue((List>) value)); case MAP: + if (value instanceof ExtendedAttributes) { + return new KeyValueMarshaler( + keyUtf8, + new KeyValueListAnyValueMarshaler( + new KeyValueListAnyValueMarshaler.KeyValueListMarshaler( + createForExtendedAttributes((ExtendedAttributes) value)))); + } return new KeyValueMarshaler(keyUtf8, MapAnyValueMarshaler.create((Attributes) value)); - case EXTENDED_ATTRIBUTES: - return new KeyValueMarshaler( - keyUtf8, - new KeyValueListAnyValueMarshaler( - new KeyValueListAnyValueMarshaler.KeyValueListMarshaler( - createForExtendedAttributes((ExtendedAttributes) value)))); } // Error prone ensures the switch statement is complete, otherwise only can happen with // unaligned versions which are not supported. diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java index 9ebcd8fc8e4..4146d44fe74 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java @@ -650,7 +650,7 @@ private static String toLabelValue(AttributeType type, Object attributeValue) { "Unexpected label value of %s for %s", attributeValue.getClass().getName(), type.name())); } - // TODO: BYTE_ARRAY, VALUE_ARRAY, MAP + // TODO: BYTE_ARRAY, VALUE_ARRAY, MAP } throw new IllegalStateException("Unrecognized AttributeType: " + type); } diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java index ce5c3f07959..c1408698647 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java @@ -223,7 +223,7 @@ private static String valueToString(AttributeKey key, Object attributeValue) case LONG_ARRAY: case DOUBLE_ARRAY: return commaSeparated((List) attributeValue); - // TODO: BYTE_ARRAY, VALUE_ARRAY, MAP + // TODO: BYTE_ARRAY, VALUE_ARRAY, MAP } throw new IllegalStateException("Unknown attribute type: " + type); } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java index bdeebff355e..ed95fcdb263 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java @@ -8,6 +8,9 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -56,6 +59,11 @@ public static Attributes applyAttributesLimit( } private static boolean isValidLength(Object value, int lengthLimit) { + if (value instanceof ExtendedAttributes) { + return allMatch( + ((ExtendedAttributes) value).asMap().values(), + entry -> isValidLength(entry, lengthLimit)); + } if (value instanceof Attributes) { return allMatch( ((Attributes) value).asMap().values(), entry -> isValidLength(entry, lengthLimit)); @@ -86,6 +94,17 @@ public static Object applyAttributeLengthLimit(Object value, int lengthLimit) { if (lengthLimit == Integer.MAX_VALUE) { return value; } + if (value instanceof ExtendedAttributes) { + ExtendedAttributes extendedAttributes = (ExtendedAttributes) value; + ExtendedAttributesBuilder builder = ExtendedAttributes.builder(); + extendedAttributes.forEach( + (key, attributeValue) -> { + @SuppressWarnings("unchecked") + ExtendedAttributeKey castKey = (ExtendedAttributeKey) key; + builder.put(castKey, applyAttributeLengthLimit(attributeValue, lengthLimit)); + }); + return builder.build(); + } if (value instanceof Attributes) { Attributes attributes = (Attributes) value; AttributesBuilder builder = Attributes.builder(); diff --git a/sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java b/sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java index 3ec19118aa7..94217bd9b8a 100644 --- a/sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java +++ b/sdk/trace/src/testIncubating/java/io/opentelemetry/sdk/trace/ExtendedSpanBuilderTest.java @@ -13,8 +13,8 @@ import io.opentelemetry.api.incubator.trace.ExtendedSpanBuilder; import io.opentelemetry.api.incubator.trace.ExtendedTracer; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -63,7 +63,7 @@ void setAttribute_withUnsupportedExtendedAttributeKeyIsNoop() { ExtendedSpanBuilder spanBuilder = tracer.spanBuilder("span"); spanBuilder.setAttribute( - ExtendedAttributeKey.extendedAttributesKey("extended.map"), ExtendedAttributes.empty()); + ExtendedAttributeKey.mapKey("extended.map"), ExtendedAttributes.empty()); spanBuilder.startSpan().end(); List spans = spanExporter.getFinishedSpanItems(); From 8a22afa6d13f549bb08fbd1c8331a4f1883d3ac5 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 4 Nov 2025 09:34:34 -0800 Subject: [PATCH 5/5] up --- .../api/common/AttributesBuilder.java | 15 +++++++++++++-- .../common/ExtendedAttributesBuilder.java | 6 ++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java b/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java index 00978f6cda5..7e12c07911d 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/AttributesBuilder.java @@ -8,12 +8,15 @@ import static io.opentelemetry.api.common.ArrayBackedAttributesBuilder.toList; import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; import static io.opentelemetry.api.common.AttributeKey.booleanKey; +import static io.opentelemetry.api.common.AttributeKey.byteArrayKey; import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; import static io.opentelemetry.api.common.AttributeKey.doubleKey; import static io.opentelemetry.api.common.AttributeKey.longArrayKey; import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.mapKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.api.common.AttributeKey.valueArrayKey; import java.util.Arrays; import java.util.List; @@ -169,7 +172,15 @@ default AttributesBuilder put(String key, byte[] value) { if (value == null) { return this; } - return put(AttributeKey.byteArrayKey(key), value); + return put(byteArrayKey(key), value); + } + + /** Puts a {@link Value} array attribute into this. */ + default AttributesBuilder put(String key, Value... value) { + if (value == null) { + return this; + } + return put(valueArrayKey(key), Arrays.asList(value)); } /** Puts an {@link Attributes} attribute into this. */ @@ -177,7 +188,7 @@ default AttributesBuilder put(String key, Attributes value) { if (value == null) { return this; } - return put(AttributeKey.mapKey(key), value); + return put(mapKey(key), value); } /** diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java index 68968fb38ee..6c7d8715ef7 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java @@ -8,10 +8,12 @@ import static io.opentelemetry.api.incubator.common.ArrayBackedExtendedAttributesBuilder.toList; 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.byteArrayKey; 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.mapKey; import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.stringArrayKey; import static io.opentelemetry.api.incubator.common.ExtendedAttributeKey.stringKey; @@ -97,7 +99,7 @@ default ExtendedAttributesBuilder put(String key, ExtendedAttributes value) { if (value == null) { return this; } - return put(ExtendedAttributeKey.mapKey(key), value); + return put(mapKey(key), value); } /** Puts a byte array attribute into this. */ @@ -105,7 +107,7 @@ default ExtendedAttributesBuilder put(String key, byte[] value) { if (value == null) { return this; } - return put(ExtendedAttributeKey.byteArrayKey(key), value); + return put(byteArrayKey(key), value); } /** Puts an {@link Attributes} attribute into this. */