From a8085a543099866fb12010e751399d895bf2efc9 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 4 Nov 2025 16:02:38 -0800 Subject: [PATCH 1/6] Prototype: Complex attributes (Option C - minimal) --- .../opentelemetry/api/common/AttributeKey.java | 5 +++++ .../opentelemetry/api/common/AttributeType.java | 3 ++- .../api/common/AttributesBuilder.java | 16 ++++++++++++++++ 3 files changed, 23 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..64b101975ab 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,9 @@ static AttributeKey> longArrayKey(String key) { static AttributeKey> doubleArrayKey(String key) { return InternalAttributeKeyImpl.create(key, AttributeType.DOUBLE_ARRAY); } + + /** Returns a new AttributeKey for generic {@link Value} valued attributes. */ + static AttributeKey> valueKey(String key) { + return InternalAttributeKeyImpl.create(key, AttributeType.VALUE); + } } 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..8ed5baa94bf 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,6 @@ public enum AttributeType { STRING_ARRAY, BOOLEAN_ARRAY, LONG_ARRAY, - DOUBLE_ARRAY + DOUBLE_ARRAY, + VALUE } 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..683a0a7d4eb 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 @@ -14,6 +14,7 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.api.common.AttributeKey.valueKey; import java.util.Arrays; import java.util.List; @@ -164,6 +165,21 @@ default AttributesBuilder put(String key, boolean... value) { return put(booleanArrayKey(key), toList(value)); } + /** + * Puts a generic ({@link Value}) attribute into this. + * + *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate + * your keys, if possible. + * + * @return this Builder + */ + default AttributesBuilder put(String key, Value value) { + if (value == null) { + return this; + } + return put(valueKey(key), value); + } + /** * Puts all the provided attributes into this Builder. * From d9ed983c8f4f7c1c0840be4b1f0c6f4eb85e202c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 5 Nov 2025 16:45:01 -0800 Subject: [PATCH 2/6] Remove String, Value overload --- .../api/common/AttributesBuilder.java | 16 ---------------- 1 file changed, 16 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 683a0a7d4eb..6623d470137 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 @@ -14,7 +14,6 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static io.opentelemetry.api.common.AttributeKey.valueKey; import java.util.Arrays; import java.util.List; @@ -165,21 +164,6 @@ default AttributesBuilder put(String key, boolean... value) { return put(booleanArrayKey(key), toList(value)); } - /** - * Puts a generic ({@link Value}) attribute into this. - * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. - * - * @return this Builder - */ - default AttributesBuilder put(String key, Value value) { - if (value == null) { - return this; - } - return put(valueKey(key), value); - } - /** * Puts all the provided attributes into this Builder. * From fa87c477b0bfe91dcf3191b81889ab89756d9085 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 5 Nov 2025 16:50:30 -0800 Subject: [PATCH 3/6] Add javadoc explaining coersion --- .../api/common/AttributesBuilder.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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..d3abb713885 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 @@ -39,6 +39,32 @@ public interface AttributesBuilder { /** * Puts an {@link AttributeKey} with an associated value into this if the value is non-null. * Providing a null value does not remove or unset previously set values. + * + *

Note: when passing a key of type {@link AttributeType#VALUE}, the call will be coerced into + * a narrower type if possible. + * + *

    + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of("a"))} is equivalent to calling + * {@code put(AttributeKey.stringKey("key"), "a")}. + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of(1L))} is equivalent to calling + * {@code put(AttributeKey.longKey("key"), 1L)}. + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of(1.0))} is equivalent to calling + * {@code put(AttributeKey.doubleKey("key"), 1.0)}. + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of(true))} is equivalent to + * calling {@code put(AttributeKey.booleanKey("key"), true)}. + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of(Value.of("a"), Value.of("b")))} + * is equivalent to calling {@code put(AttributeKey.stringArrayKey("key"), + * Arrays.asList("a", "b"))}. + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of(Value.of(1L), Value.of(2L)))} + * is equivalent to calling {@code put(AttributeKey.longArrayKey("key"), Arrays.asList(1L, + * 2L))}. + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of(Value.of(1.0), Value.of(2.0)))} + * is equivalent to calling {@code put(AttributeKey.doubleArrayKey("key"), + * Arrays.asList(1.0, 2.0))}. + *
  • Calling {@code put(AttributeKey.valueKey("key"), Value.of(Value.of(true), + * Value.of(false)))} is equivalent to calling {@code + * put(AttributeKey.booleanArrayKey("key"), Arrays.asList(true, false))}. + *
*/ AttributesBuilder put(AttributeKey key, @Nullable T value); From 5cfe4ab9a7e139551117f78f4b28c6d7d6c511c8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 6 Nov 2025 19:41:00 -0800 Subject: [PATCH 4/6] more javadoc --- .../api/common/AttributeType.java | 6 +++ .../opentelemetry/api/common/Attributes.java | 37 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) 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 8ed5baa94bf..a174f0dcdda 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 @@ -18,5 +18,11 @@ public enum AttributeType { BOOLEAN_ARRAY, LONG_ARRAY, DOUBLE_ARRAY, + /** + * Simple attributes (i.e. anything other than {@code VALUE} attributes) 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. + */ VALUE } diff --git a/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java b/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java index 2a9d43793a7..ac00c404e07 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java @@ -33,7 +33,42 @@ @Immutable public interface Attributes { - /** Returns the value for the given {@link AttributeKey}, or {@code null} if not found. */ + /** + * Returns the value for the given {@link AttributeKey}, or {@code null} if not found. + * + *

Simple attributes (i.e. anything other than {@link Value} attributes) 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. + * + *

Note: when passing a key of type {@link AttributeType#VALUE}, the returned value will match + * a narrower type with the given key if one exists. This is the inverse of {@link + * AttributesBuilder#put(AttributeKey, Object)} when the key is {@link AttributeType#VALUE}. + * + *

    + *
  • If {@code put(AttributeKey.stringKey("key"), "a")} was called, then {@code + * get(AttributeKey.valueKey("key"))} returns {@code Value.of("a")}. + *
  • If {@code put(AttributeKey.longKey("key"), 1L)} was called, then {@code + * get(AttributeKey.valueKey("key"))} returns {@code Value.of(1L)}. + *
  • If {@code put(AttributeKey.doubleKey("key"), 1.0)} was called, then {@code + * get(AttributeKey.valueKey("key"))} returns {@code Value.of(1.0)}. + *
  • If {@code put(AttributeKey.booleanKey("key"), true)} was called, then {@code + * get(AttributeKey.valueKey("key"))} returns {@code Value.of(true)}. + *
  • If {@code put(AttributeKey.stringArrayKey("key"), Arrays.asList("a", "b"))} was called, + * then {@code get(AttributeKey.valueKey("key"))} returns {@code Value.of(Value.of("a"), + * Value.of("b"))}. + *
  • If {@code put(AttributeKey.longArrayKey("key"), Arrays.asList(1L, 2L))} was called, then + * {@code get(AttributeKey.valueKey("key"))} returns {@code Value.of(Value.of(1L), + * Value.of(2L))}. + *
  • If {@code put(AttributeKey.doubleArrayKey("key"), Arrays.asList(1.0, 2.0))} was called, + * then {@code get(AttributeKey.valueKey("key"))} returns {@code Value.of(Value.of(1.0), + * Value.of(2.0))}. + *
  • If {@code put(AttributeKey.booleanArrayKey("key"), Arrays.asList(true, false))} was + * called, then {@code get(AttributeKey.valueKey("key"))} returns {@code + * Value.of(Value.of(true), Value.of(false))}. + *
+ */ @Nullable T get(AttributeKey key); From 42d4b8a5b79d4146fc052acfc6bc71ef8e9fcb53 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 6 Nov 2025 19:49:49 -0800 Subject: [PATCH 5/6] more javadoc --- .../api/common/AttributeType.java | 11 +++++---- .../opentelemetry/api/common/Attributes.java | 24 +++++++++++-------- .../api/common/AttributesBuilder.java | 12 ++++++++-- 3 files changed, 31 insertions(+), 16 deletions(-) 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 a174f0dcdda..6209301f573 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 @@ -19,10 +19,13 @@ public enum AttributeType { LONG_ARRAY, DOUBLE_ARRAY, /** - * Simple attributes (i.e. anything other than {@code VALUE} attributes) 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. + * Simple attributes ({@link AttributeType#STRING}, {@link AttributeType#LONG}, {@link + * AttributeType#DOUBLE}, {@link AttributeType#BOOLEAN}, {@link AttributeType#STRING_ARRAY}, + * {@link AttributeType#LONG_ARRAY}, {@link AttributeType#DOUBLE_ARRAY}, {@link + * AttributeType#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. */ VALUE } diff --git a/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java b/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java index ac00c404e07..1ee1c43718c 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java @@ -36,14 +36,8 @@ public interface Attributes { /** * Returns the value for the given {@link AttributeKey}, or {@code null} if not found. * - *

Simple attributes (i.e. anything other than {@link Value} attributes) 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. - * - *

Note: when passing a key of type {@link AttributeType#VALUE}, the returned value will match - * a narrower type with the given key if one exists. This is the inverse of {@link + *

Note: this method will automatically convert simple attributes to complex attributes when + * passed a key of type {@link AttributeType#VALUE}. This is the inverse of {@link * AttributesBuilder#put(AttributeKey, Object)} when the key is {@link AttributeType#VALUE}. * *

    @@ -72,7 +66,12 @@ public interface Attributes { @Nullable T get(AttributeKey key); - /** Iterates over all the key-value pairs of attributes contained by this instance. */ + /** + * Iterates over all the key-value pairs of attributes contained by this instance. + * + *

    Note: {@link AttributeType#VALUE} attributes will be represented as simple attributes if + * possible. See {@link AttributesBuilder#put(AttributeKey, Object)} for more details. + */ void forEach(BiConsumer, ? super Object> consumer); /** The number of attributes contained in this. */ @@ -81,7 +80,12 @@ public interface Attributes { /** Whether there are any attributes contained in this. */ boolean isEmpty(); - /** Returns a read-only view of this {@link Attributes} as a {@link Map}. */ + /** + * Returns a read-only view of this {@link Attributes} as a {@link Map}. + * + *

    Note: {@link AttributeType#VALUE} attributes will be represented as simple attributes in + * this map if possible. See {@link AttributesBuilder#put(AttributeKey, Object)} for more details. + */ Map, Object> asMap(); /** Returns a {@link Attributes} instance with no attributes. */ 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 d3abb713885..bc246ad3e8b 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 @@ -40,8 +40,16 @@ public interface AttributesBuilder { * Puts an {@link AttributeKey} with an associated value into this if the value is non-null. * Providing a null value does not remove or unset previously set values. * - *

    Note: when passing a key of type {@link AttributeType#VALUE}, the call will be coerced into - * a narrower type if possible. + *

    Simple attributes ({@link AttributeType#STRING}, {@link AttributeType#LONG}, {@link + * AttributeType#DOUBLE}, {@link AttributeType#BOOLEAN}, {@link AttributeType#STRING_ARRAY}, + * {@link AttributeType#LONG_ARRAY}, {@link AttributeType#DOUBLE_ARRAY}, {@link + * AttributeType#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. + * + *

    Note: This method will automatically convert complex attributes ({@link + * AttributeType#VALUE}) to simple attributes when possible. * *

      *
    • Calling {@code put(AttributeKey.valueKey("key"), Value.of("a"))} is equivalent to calling From 99668f0be6a55c84ba8e7dafe481b273e67e53d1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 7 Nov 2025 12:57:49 -0800 Subject: [PATCH 6/6] more --- .../opentelemetry/api/common/Attributes.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java b/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java index 1ee1c43718c..8ac46146e6b 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java +++ b/api/all/src/main/java/io/opentelemetry/api/common/Attributes.java @@ -36,9 +36,10 @@ public interface Attributes { /** * Returns the value for the given {@link AttributeKey}, or {@code null} if not found. * - *

      Note: this method will automatically convert simple attributes to complex attributes when - * passed a key of type {@link AttributeType#VALUE}. This is the inverse of {@link - * AttributesBuilder#put(AttributeKey, Object)} when the key is {@link AttributeType#VALUE}. + *

      Note: this method will automatically return the corresponding {@link Value} instance when + * passed a key of type {@link AttributeType#VALUE} and a simple attribute is found. This is the + * inverse of {@link AttributesBuilder#put(AttributeKey, Object)} when the key is {@link + * AttributeType#VALUE}. * *

        *
      • If {@code put(AttributeKey.stringKey("key"), "a")} was called, then {@code @@ -62,6 +63,17 @@ public interface Attributes { * called, then {@code get(AttributeKey.valueKey("key"))} returns {@code * Value.of(Value.of(true), Value.of(false))}. *
      + * + * Further, if {@code put(AttributeKey.valueKey("key"), Value.of(emptyList()))} was called, then + * + *
        + *
      • {@code get(AttributeKey.stringArrayKey("key"))} + *
      • {@code get(AttributeKey.longArrayKey("key"))} + *
      • {@code get(AttributeKey.booleanArrayKey("key"))} + *
      • {@code get(AttributeKey.doubleArrayKey("key"))} + *
      + * + * all return an empty list (as opposed to {@code null}). */ @Nullable T get(AttributeKey key);