From 53d543947d7ce07a23b53ed88660410f613909fc Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Sat, 28 Dec 2024 13:10:35 -0600 Subject: [PATCH 1/8] Sketch out ExtendedAttributes --- api/all/build.gradle.kts | 2 + .../common/ArrayBackedExtendedAttributes.java | 83 +++++++ .../ArrayBackedExtendedAttributesBuilder.java | 59 +++++ .../api/common/AttributeKey.java | 4 + .../api/common/ExtendedAttributeKey.java | 76 ++++++ .../api/common/ExtendedAttributeType.java | 20 ++ .../api/common/ExtendedAttributes.java | 66 ++++++ .../api/common/ExtendedAttributesBuilder.java | 219 ++++++++++++++++++ .../internal/InternalAttributeKeyImpl.java | 42 ++++ .../InternalExtendedAttributeKeyImpl.java | 143 ++++++++++++ .../api/logs/LogRecordBuilder.java | 18 ++ .../api/common/AttributeKeyTest.java | 2 +- .../api/common/ExtendedAttributesTest.java | 201 ++++++++++++++++ .../api/logs/DefaultLoggerTest.java | 2 + .../api/trace/DefaultTracerTest.java | 2 + .../current_vs_latest/opentelemetry-api.txt | 92 +++++++- .../opentelemetry-sdk-logs.txt | 10 + .../sdk/logs/ReadWriteLogRecord.java | 32 ++- .../sdk/logs/SdkLogRecordBuilder.java | 29 ++- .../sdk/logs/SdkLogRecordData.java | 17 +- .../sdk/logs/SdkReadWriteLogRecord.java | 63 ++--- .../sdk/logs/data/LogRecordData.java | 7 + .../sdk/logs/ReadWriteLogRecordTest.java | 10 +- .../sdk/logs/SdkLogRecordBuilderTest.java | 2 +- .../sdk/logs/SdkLoggerProviderTest.java | 2 +- .../opentelemetry/sdk/logs/SdkLoggerTest.java | 3 + .../logs/internal/SdkEventBuilderTest.java | 13 +- 27 files changed, 1169 insertions(+), 50 deletions(-) create mode 100644 api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributes.java create mode 100644 api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributesBuilder.java create mode 100644 api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeKey.java create mode 100644 api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeType.java create mode 100644 api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributes.java create mode 100644 api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributesBuilder.java create mode 100644 api/all/src/main/java/io/opentelemetry/api/internal/InternalExtendedAttributeKeyImpl.java create mode 100644 api/all/src/test/java/io/opentelemetry/api/common/ExtendedAttributesTest.java diff --git a/api/all/build.gradle.kts b/api/all/build.gradle.kts index ad6896387d8..acab08cdeec 100644 --- a/api/all/build.gradle.kts +++ b/api/all/build.gradle.kts @@ -19,6 +19,8 @@ dependencies { testImplementation("edu.berkeley.cs.jqf:jqf-fuzz") testImplementation("com.google.guava:guava-testlib") + testImplementation(project(":sdk:all")) + testImplementation(project(":sdk:testing")) } tasks.test { diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributes.java b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributes.java new file mode 100644 index 00000000000..48f0183991d --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributes.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import io.opentelemetry.api.internal.ImmutableKeyValuePairs; +import java.util.ArrayList; +import java.util.Comparator; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +final class ArrayBackedExtendedAttributes + extends ImmutableKeyValuePairs, Object> implements ExtendedAttributes { + + // We only compare the key name, not type, when constructing, to allow deduping keys with the + // same name but different type. + private static final Comparator> KEY_COMPARATOR_FOR_CONSTRUCTION = + Comparator.comparing(ExtendedAttributeKey::getKey); + + static final ExtendedAttributes EMPTY = ExtendedAttributes.builder().build(); + + @Nullable private Attributes attributes; + + private ArrayBackedExtendedAttributes( + Object[] data, Comparator> keyComparator) { + super(data, keyComparator); + } + + /** + * Only use this constructor if you can guarantee that the data has been de-duped, sorted by key + * and contains no null values or null/empty keys. + * + * @param data the raw data + */ + ArrayBackedExtendedAttributes(Object[] data) { + super(data); + } + + @Override + public ExtendedAttributesBuilder toBuilder() { + return new ArrayBackedExtendedAttributesBuilder(new ArrayList<>(data())); + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + public T get(ExtendedAttributeKey key) { + return (T) super.get(key); + } + + @SuppressWarnings("unchecked") + @Override + public Attributes asAttributes() { + if (attributes == null) { + AttributesBuilder builder = Attributes.builder(); + forEach( + (extendedAttributeKey, value) -> { + AttributeKey attributeKey = + (AttributeKey) extendedAttributeKey.asAttributeKey(); + if (attributeKey != null) { + builder.put(attributeKey, value); + } + }); + attributes = builder.build(); + } + return attributes; + } + + static ExtendedAttributes sortAndFilterToAttributes(Object... data) { + // null out any empty keys or keys with null values + // so they will then be removed by the sortAndFilter method. + for (int i = 0; i < data.length; i += 2) { + ExtendedAttributeKey key = (ExtendedAttributeKey) data[i]; + if (key != null && key.getKey().isEmpty()) { + data[i] = null; + } + } + return new ArrayBackedExtendedAttributes(data, KEY_COMPARATOR_FOR_CONSTRUCTION); + } +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributesBuilder.java b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributesBuilder.java new file mode 100644 index 00000000000..5afb327c6e9 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributesBuilder.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +class ArrayBackedExtendedAttributesBuilder implements ExtendedAttributesBuilder { + private final List data; + + ArrayBackedExtendedAttributesBuilder() { + data = new ArrayList<>(); + } + + ArrayBackedExtendedAttributesBuilder(List data) { + this.data = data; + } + + @Override + public ExtendedAttributes build() { + // If only one key-value pair AND the entry hasn't been set to null (by #remove(AttributeKey) + // or #removeIf(Predicate>)), then we can bypass sorting and filtering + if (data.size() == 2 && data.get(0) != null) { + return new ArrayBackedExtendedAttributes(data.toArray()); + } + return ArrayBackedExtendedAttributes.sortAndFilterToAttributes(data.toArray()); + } + + @Override + public ExtendedAttributesBuilder put(ExtendedAttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + data.add(key); + data.add(value); + return this; + } + + @Override + public ExtendedAttributesBuilder removeIf(Predicate> predicate) { + if (predicate == null) { + return this; + } + for (int i = 0; i < data.size() - 1; i += 2) { + Object entry = data.get(i); + if (entry instanceof ExtendedAttributeKey + && predicate.test((ExtendedAttributeKey) entry)) { + // null items are filtered out in ArrayBackedAttributes + data.set(i, null); + data.set(i + 1, null); + } + } + return this; + } +} 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 7743c315c50..d25b582d237 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 @@ -26,6 +26,10 @@ public interface AttributeKey { /** Returns the type of attribute for this key. Useful for building switch statements. */ AttributeType getType(); + default ExtendedAttributeKey asExtendedAttributeKey() { + return InternalAttributeKeyImpl.toExtendedAttributeKey(this); + } + /** Returns a new AttributeKey for String valued attributes. */ static AttributeKey stringKey(String key) { return InternalAttributeKeyImpl.create(key, AttributeType.STRING); diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeKey.java b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeKey.java new file mode 100644 index 00000000000..42094bb283d --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeKey.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import io.opentelemetry.api.internal.InternalExtendedAttributeKeyImpl; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** TODO. */ +@Immutable +public interface ExtendedAttributeKey { + /** Returns the underlying String representation of the key. */ + String getKey(); + + /** Returns the type of attribute for this key. Useful for building switch statements. */ + ExtendedAttributeType getType(); + + @Nullable + default AttributeKey asAttributeKey() { + return InternalExtendedAttributeKeyImpl.toAttributeKey(this); + } + + /** Returns a new ExtendedAttributeKey for String valued attributes. */ + static ExtendedAttributeKey stringKey(String key) { + return AttributeKey.stringKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for Boolean valued attributes. */ + static ExtendedAttributeKey booleanKey(String key) { + return AttributeKey.booleanKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for Long valued attributes. */ + static ExtendedAttributeKey longKey(String key) { + return AttributeKey.longKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for Double valued attributes. */ + static ExtendedAttributeKey doubleKey(String key) { + return AttributeKey.doubleKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for List<String> valued attributes. */ + static ExtendedAttributeKey> stringArrayKey(String key) { + return AttributeKey.stringArrayKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for List<Boolean> valued attributes. */ + static ExtendedAttributeKey> booleanArrayKey(String key) { + return AttributeKey.booleanArrayKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for List<Long> valued attributes. */ + static ExtendedAttributeKey> longArrayKey(String key) { + return AttributeKey.longArrayKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for List<Double> valued attributes. */ + static ExtendedAttributeKey> doubleArrayKey(String key) { + return AttributeKey.doubleArrayKey(key).asExtendedAttributeKey(); + } + + /** Returns a new ExtendedAttributeKey for Map valued attributes. */ + static ExtendedAttributeKey mapKey(String key) { + return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.MAP); + } + + /** Returns a new ExtendedAttributeKey for Map array valued attributes. */ + static ExtendedAttributeKey> mapArrayKey(String key) { + return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.MAP_ARRAY); + } +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeType.java b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeType.java new file mode 100644 index 00000000000..bba490e6a4b --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeType.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +/** TODO. */ +public enum ExtendedAttributeType { + STRING, + BOOLEAN, + LONG, + DOUBLE, + STRING_ARRAY, + BOOLEAN_ARRAY, + LONG_ARRAY, + DOUBLE_ARRAY, + MAP, + MAP_ARRAY; +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributes.java b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributes.java new file mode 100644 index 00000000000..bbbac1ee5b9 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributes.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import java.util.Map; +import java.util.function.BiConsumer; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@SuppressWarnings("rawtypes") +@Immutable +public interface ExtendedAttributes { + + /** Returns the value for the given {@link AttributeKey}, or {@code null} if not found. */ + @Nullable + default T get(AttributeKey key) { + if (key == null) { + return null; + } + return get(key.asExtendedAttributeKey()); + } + + /** Returns the value for the given {@link ExtendedAttributeKey}, or {@code null} if not found. */ + @Nullable + T get(ExtendedAttributeKey key); + + /** Iterates over all the key-value pairs of attributes contained by this instance. */ + void forEach(BiConsumer, ? super Object> consumer); + + /** The number of attributes contained in this. */ + int size(); + + /** Whether there are any attributes contained in this. */ + boolean isEmpty(); + + /** Returns a read-only view of this {@link ExtendedAttributes} as a {@link Map}. */ + Map, Object> asMap(); + + /** + * Return a view of this extended attributes with entries limited to those representable as + * standard attributes. + */ + Attributes asAttributes(); + + /** Returns a {@link ExtendedAttributes} instance with no attributes. */ + static ExtendedAttributes empty() { + return ArrayBackedExtendedAttributes.EMPTY; + } + + /** + * Returns a new {@link ExtendedAttributesBuilder} instance for creating arbitrary {@link + * ExtendedAttributes}. + */ + static ExtendedAttributesBuilder builder() { + return new ArrayBackedExtendedAttributesBuilder(); + } + + /** + * Returns a new {@link ExtendedAttributesBuilder} instance populated with the data of this {@link + * ExtendedAttributes}. + */ + ExtendedAttributesBuilder toBuilder(); +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributesBuilder.java b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributesBuilder.java new file mode 100644 index 00000000000..2293f062f6f --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributesBuilder.java @@ -0,0 +1,219 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import static io.opentelemetry.api.common.ArrayBackedAttributesBuilder.toList; +import static io.opentelemetry.api.common.ExtendedAttributeKey.booleanArrayKey; +import static io.opentelemetry.api.common.ExtendedAttributeKey.booleanKey; +import static io.opentelemetry.api.common.ExtendedAttributeKey.doubleArrayKey; +import static io.opentelemetry.api.common.ExtendedAttributeKey.doubleKey; +import static io.opentelemetry.api.common.ExtendedAttributeKey.longArrayKey; +import static io.opentelemetry.api.common.ExtendedAttributeKey.longKey; +import static io.opentelemetry.api.common.ExtendedAttributeKey.stringArrayKey; +import static io.opentelemetry.api.common.ExtendedAttributeKey.stringKey; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +/** A builder of {@link Attributes} supporting an arbitrary number of key-value pairs. */ +public interface ExtendedAttributesBuilder { + /** Create the {@link ExtendedAttributes} from this. */ + ExtendedAttributes build(); + + /** Puts a {@link AttributeKey} with associated value into this. */ + default ExtendedAttributesBuilder put(AttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + return put(key.asExtendedAttributeKey(), value); + } + + /** TODO. */ + ExtendedAttributesBuilder put(ExtendedAttributeKey key, T value); + + /** + * Puts a String 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 ExtendedAttributesBuilder put(String key, String value) { + return put(stringKey(key), value); + } + + /** + * Puts a long 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 ExtendedAttributesBuilder put(String key, long value) { + return put(longKey(key), value); + } + + /** + * Puts a double 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 ExtendedAttributesBuilder put(String key, double value) { + return put(doubleKey(key), value); + } + + /** + * Puts a boolean 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 ExtendedAttributesBuilder put(String key, boolean value) { + return put(booleanKey(key), value); + } + + /** TODO. */ + default ExtendedAttributesBuilder put(String key, ExtendedAttributes value) { + return put(ExtendedAttributeKey.mapKey(key), value); + } + + /** + * Puts a String array 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 ExtendedAttributesBuilder put(String key, String... value) { + if (value == null) { + return this; + } + return put(stringArrayKey(key), Arrays.asList(value)); + } + + /** + * Puts a List attribute into this. + * + * @return this Builder + */ + @SuppressWarnings("unchecked") + default ExtendedAttributesBuilder put(AttributeKey> key, T... value) { + if (value == null) { + return this; + } + return put(key, Arrays.asList(value)); + } + + /** + * Puts a Long array 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 ExtendedAttributesBuilder put(String key, long... value) { + if (value == null) { + return this; + } + return put(longArrayKey(key), toList(value)); + } + + /** + * Puts a Double array 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 ExtendedAttributesBuilder put(String key, double... value) { + if (value == null) { + return this; + } + return put(doubleArrayKey(key), toList(value)); + } + + /** + * Puts a Boolean array 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 ExtendedAttributesBuilder put(String key, boolean... value) { + if (value == null) { + return this; + } + return put(booleanArrayKey(key), toList(value)); + } + + /** TODO. */ + default ExtendedAttributesBuilder put(String key, ExtendedAttributes... value) { + if (value == null) { + return this; + } + return put(ExtendedAttributeKey.mapArrayKey(key), Arrays.asList(value)); + } + + /** + * Puts all the provided attributes into this Builder. + * + * @return this Builder + */ + @SuppressWarnings({"unchecked"}) + default ExtendedAttributesBuilder putAll(Attributes attributes) { + if (attributes == null) { + return this; + } + attributes.forEach((key, value) -> put((AttributeKey) key, value)); + return this; + } + + /** TODO. */ + @SuppressWarnings({"unchecked"}) + default ExtendedAttributesBuilder putAll(ExtendedAttributes attributes) { + if (attributes == null) { + return this; + } + attributes.forEach((key, value) -> put((ExtendedAttributeKey) key, value)); + return this; + } + + /** + * Remove all attributes where {@link AttributeKey#getKey()} and {@link AttributeKey#getType()} + * match the {@code key}. + * + * @return this Builder + */ + default ExtendedAttributesBuilder remove(AttributeKey key) { + return remove(key.asExtendedAttributeKey()); + } + + /** TODO. */ + default ExtendedAttributesBuilder remove(ExtendedAttributeKey key) { + if (key == null || key.getKey().isEmpty()) { + return this; + } + // TODO: + return removeIf( + entryKey -> + key.getKey().equals(entryKey.getKey()) && key.getType().equals(entryKey.getType())); + } + + /** TODO. */ + ExtendedAttributesBuilder removeIf(Predicate> filter); +} diff --git a/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java b/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java index dfeca408c19..1383a3805a7 100644 --- a/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java +++ b/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java @@ -7,6 +7,8 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.api.common.ExtendedAttributeKey; +import io.opentelemetry.api.common.ExtendedAttributeType; import java.nio.charset.StandardCharsets; import javax.annotation.Nullable; @@ -23,6 +25,7 @@ public final class InternalAttributeKeyImpl implements AttributeKey { private final int hashCode; @Nullable private byte[] keyUtf8; + @Nullable private ExtendedAttributeKey extendedAttributeKey; private InternalAttributeKeyImpl(AttributeType type, String key) { if (type == null) { @@ -62,6 +65,14 @@ public String getKey() { return key; } + @Override + public ExtendedAttributeKey asExtendedAttributeKey() { + if (extendedAttributeKey == null) { + extendedAttributeKey = InternalAttributeKeyImpl.toExtendedAttributeKey(this); + } + return extendedAttributeKey; + } + /** Returns the key, encoded as UTF-8 bytes. */ public byte[] getKeyUtf8() { byte[] keyUtf8 = this.keyUtf8; @@ -108,4 +119,35 @@ private static int buildHashCode(AttributeType type, String key) { result ^= key.hashCode(); return result; } + + /** TODO. */ + public static ExtendedAttributeKey toExtendedAttributeKey(AttributeKey attributeKey) { + switch (attributeKey.getType()) { + case STRING: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.STRING); + case BOOLEAN: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.BOOLEAN); + case LONG: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.LONG); + case DOUBLE: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.DOUBLE); + case STRING_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.STRING_ARRAY); + case BOOLEAN_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.BOOLEAN_ARRAY); + case LONG_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.LONG_ARRAY); + case DOUBLE_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.DOUBLE_ARRAY); + } + throw new IllegalArgumentException("Unrecognized attributeKey type: " + attributeKey.getType()); + } } diff --git a/api/all/src/main/java/io/opentelemetry/api/internal/InternalExtendedAttributeKeyImpl.java b/api/all/src/main/java/io/opentelemetry/api/internal/InternalExtendedAttributeKeyImpl.java new file mode 100644 index 00000000000..d8aaca6e807 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/internal/InternalExtendedAttributeKeyImpl.java @@ -0,0 +1,143 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.internal; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.api.common.ExtendedAttributeKey; +import io.opentelemetry.api.common.ExtendedAttributeType; +import java.nio.charset.StandardCharsets; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class InternalExtendedAttributeKeyImpl implements ExtendedAttributeKey { + + private final ExtendedAttributeType type; + private final String key; + private final int hashCode; + + @Nullable private byte[] keyUtf8; + @Nullable private AttributeKey attributeKey; + + private InternalExtendedAttributeKeyImpl(ExtendedAttributeType type, String key) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + if (key == null) { + throw new NullPointerException("Null key"); + } + this.key = key; + this.hashCode = buildHashCode(type, key); + } + + public static ExtendedAttributeKey create( + @Nullable String key, ExtendedAttributeType type) { + return new InternalExtendedAttributeKeyImpl<>(type, key != null ? key : ""); + } + + @Override + public ExtendedAttributeType getType() { + return type; + } + + @Override + public String getKey() { + return key; + } + + @Nullable + @Override + public AttributeKey asAttributeKey() { + if (attributeKey == null) { + attributeKey = toAttributeKey(this); + } + return attributeKey; + } + + /** Returns the key, encoded as UTF-8 bytes. */ + public byte[] getKeyUtf8() { + byte[] keyUtf8 = this.keyUtf8; + if (keyUtf8 == null) { + keyUtf8 = key.getBytes(StandardCharsets.UTF_8); + this.keyUtf8 = keyUtf8; + } + return keyUtf8; + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (o instanceof InternalExtendedAttributeKeyImpl) { + InternalExtendedAttributeKeyImpl that = (InternalExtendedAttributeKeyImpl) o; + return this.type.equals(that.getType()) && this.key.equals(that.getKey()); + } + return false; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String toString() { + return key; + } + + // this method exists to make EqualsVerifier happy + @SuppressWarnings("unused") + private int buildHashCode() { + return buildHashCode(type, key); + } + + private static int buildHashCode(ExtendedAttributeType type, String key) { + int result = 1; + result *= 1000003; + result ^= type.hashCode(); + result *= 1000003; + result ^= key.hashCode(); + return result; + } + + /** TODO. */ + @Nullable + public static AttributeKey toAttributeKey(ExtendedAttributeKey extendedAttributeKey) { + switch (extendedAttributeKey.getType()) { + case STRING: + return InternalAttributeKeyImpl.create(extendedAttributeKey.getKey(), AttributeType.STRING); + case BOOLEAN: + return InternalAttributeKeyImpl.create( + extendedAttributeKey.getKey(), AttributeType.BOOLEAN); + case LONG: + return InternalAttributeKeyImpl.create(extendedAttributeKey.getKey(), AttributeType.LONG); + case DOUBLE: + return InternalAttributeKeyImpl.create(extendedAttributeKey.getKey(), AttributeType.DOUBLE); + case STRING_ARRAY: + return InternalAttributeKeyImpl.create( + extendedAttributeKey.getKey(), AttributeType.STRING_ARRAY); + case BOOLEAN_ARRAY: + return InternalAttributeKeyImpl.create( + extendedAttributeKey.getKey(), AttributeType.BOOLEAN_ARRAY); + case LONG_ARRAY: + return InternalAttributeKeyImpl.create( + extendedAttributeKey.getKey(), AttributeType.LONG_ARRAY); + case DOUBLE_ARRAY: + return InternalAttributeKeyImpl.create( + extendedAttributeKey.getKey(), AttributeType.DOUBLE_ARRAY); + case MAP: + case MAP_ARRAY: + return null; + } + throw new IllegalArgumentException( + "Unrecognized extendedAttributeKey type: " + extendedAttributeKey.getType()); + } +} diff --git a/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java index 2166ab2b6b8..03cfbc133e8 100644 --- a/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java @@ -7,6 +7,8 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ExtendedAttributeKey; +import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.context.Context; import java.time.Instant; @@ -98,9 +100,25 @@ default LogRecordBuilder setAllAttributes(Attributes attributes) { return this; } + /** TODO. */ + @SuppressWarnings("unchecked") + default LogRecordBuilder setAllAttributes(ExtendedAttributes attributes) { + if (attributes == null || attributes.isEmpty()) { + return this; + } + attributes.forEach( + (attributeKey, value) -> setAttribute((ExtendedAttributeKey) attributeKey, value)); + return this; + } + /** Sets an attribute. */ LogRecordBuilder setAttribute(AttributeKey key, T value); + /** TODO. */ + default LogRecordBuilder setAttribute(ExtendedAttributeKey key, T value) { + return this; + } + /** Emit the log record. */ void emit(); } diff --git a/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java b/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java index 4b39e1d6a91..32dbd3eee4a 100644 --- a/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java @@ -16,7 +16,7 @@ class AttributeKeyTest { @Test void equalsVerifier() { EqualsVerifier.forClass(InternalAttributeKeyImpl.class) - .withIgnoredFields("keyUtf8") + .withIgnoredFields("keyUtf8", "extendedAttributeKey") .withCachedHashCode( "hashCode", "buildHashCode", diff --git a/api/all/src/test/java/io/opentelemetry/api/common/ExtendedAttributesTest.java b/api/all/src/test/java/io/opentelemetry/api/common/ExtendedAttributesTest.java new file mode 100644 index 00000000000..d9294fb2633 --- /dev/null +++ b/api/all/src/test/java/io/opentelemetry/api/common/ExtendedAttributesTest.java @@ -0,0 +1,201 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; +import java.util.Arrays; +import java.util.List; +import java.util.function.BiConsumer; +import org.junit.jupiter.api.Test; + +class ExtendedAttributesTest { + + // Primitive keys + AttributeKey strKey = AttributeKey.stringKey("acme.string"); + AttributeKey longKey = AttributeKey.longKey("acme.long"); + AttributeKey booleanKey = AttributeKey.booleanKey("acme.boolean"); + AttributeKey doubleKey = AttributeKey.doubleKey("acme.double"); + + // Primitive array keys + AttributeKey> strArrKey = AttributeKey.stringArrayKey("acme.string_array"); + AttributeKey> longArrKey = AttributeKey.longArrayKey("acme.long_array"); + AttributeKey> booleanArrKey = AttributeKey.booleanArrayKey("acme.boolean_array"); + AttributeKey> doubleArrKey = AttributeKey.doubleArrayKey("acme.double_array"); + + // Extended keys + ExtendedAttributeKey mapKey = ExtendedAttributeKey.mapKey("acme.map"); + ExtendedAttributeKey> mapArrayKey = + ExtendedAttributeKey.mapArrayKey("acme.map_array"); + + @Test + @SuppressWarnings("SystemOut") + void usage() { + // Initialize from builder. Varargs style initialization (ExtendedAttributes.of(...) not + // supported. + ExtendedAttributes extendedAttributes = + ExtendedAttributes.builder() + .put(strKey, "value") + .put(longKey, 1L) + .put(booleanKey, true) + .put(doubleKey, 1.1) + .put(strArrKey, Arrays.asList("value1", "value2")) + .put(longArrKey, Arrays.asList(1L, 2L)) + .put(booleanArrKey, Arrays.asList(true, false)) + .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put( + mapKey, + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) + .put( + mapArrayKey, + Arrays.asList( + ExtendedAttributes.builder() + .put("childStr", "value") + .put("childLong", 1L) + .build(), + ExtendedAttributes.builder() + .put("childStr", "value") + .put("childLong", 1L) + .build())) + .build(); + + // Retrieval + assertThat(extendedAttributes.get(strKey)).isEqualTo("value"); + assertThat(extendedAttributes.get(longKey)).isEqualTo(1); + assertThat(extendedAttributes.get(booleanKey)).isEqualTo(true); + assertThat(extendedAttributes.get(doubleKey)).isEqualTo(1.1); + assertThat(extendedAttributes.get(strArrKey)).isEqualTo(Arrays.asList("value1", "value2")); + 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(extendedAttributes.get(mapKey)) + .isEqualTo( + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()); + assertThat(extendedAttributes.get(mapArrayKey)) + .isEqualTo( + Arrays.asList( + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build(), + ExtendedAttributes.builder() + .put("childStr", "value") + .put("childLong", 1L) + .build())); + + // Iteration + // Output: + // acme.boolean(BOOLEAN): true + // acme.boolean_array(BOOLEAN_ARRAY): [true, false] + // acme.double(DOUBLE): 1.1 + // acme.double_array(DOUBLE_ARRAY): [1.1, 2.2] + // acme.long(LONG): 1 + // acme.long_array(LONG_ARRAY): [1, 2] + // acme.map(MAP): {childLong=1, childStr="value"} + // acme.map_array(MAP_ARRAY): [{childLong=1, childStr="value"}, {childLong=1, childStr="value"}] + // acme.string(STRING): value + // acme.string_array(STRING_ARRAY): [value1, value2] + extendedAttributes.forEach( + new BiConsumer, Object>() { + @Override + public void accept(ExtendedAttributeKey extendedAttributeKey, Object object) { + System.out.format( + "%s(%s): %s\n", + extendedAttributeKey.getKey(), extendedAttributeKey.getType(), object); + } + }); + } + + @Test + void logRecordBuilder() { + InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) + .build(); + + Logger logger = loggerProvider.get("logger"); + + // Can set either standard or extended attributes on + logger + .logRecordBuilder() + .setBody("message") + .setAttribute(strKey, "value") + .setAttribute(longKey, 1L) + .setAttribute(booleanKey, true) + .setAttribute(doubleKey, 1.1) + .setAttribute(strArrKey, Arrays.asList("value1", "value2")) + .setAttribute(longArrKey, Arrays.asList(1L, 2L)) + .setAttribute(booleanArrKey, Arrays.asList(true, false)) + .setAttribute(doubleArrKey, Arrays.asList(1.1, 2.2)) + .setAttribute( + mapKey, + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) + .setAttribute( + mapArrayKey, + Arrays.asList( + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build(), + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build())) + .setAllAttributes(Attributes.builder().put("key1", "value").build()) + .setAllAttributes(ExtendedAttributes.builder().put("key2", "value").build()) + .emit(); + + assertThat(exporter.getFinishedLogRecordItems()) + .satisfiesExactly( + logRecordData -> { + // Optionally access standard attributes, which filters out any extended attribute + // types + assertThat(logRecordData.getAttributes()) + .isEqualTo( + Attributes.builder() + .put(strKey, "value") + .put(longKey, 1L) + .put(booleanKey, true) + .put(doubleKey, 1.1) + .put(strArrKey, Arrays.asList("value1", "value2")) + .put(longArrKey, Arrays.asList(1L, 2L)) + .put(booleanArrKey, Arrays.asList(true, false)) + .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put("key1", "value") + .put("key2", "value") + .build()); + + // But preferably access and serialize full extended attributes + assertThat(logRecordData.getExtendedAttributes()) + .isEqualTo( + ExtendedAttributes.builder() + .put(strKey, "value") + .put(longKey, 1L) + .put(booleanKey, true) + .put(doubleKey, 1.1) + .put(strArrKey, Arrays.asList("value1", "value2")) + .put(longArrKey, Arrays.asList(1L, 2L)) + .put(booleanArrKey, Arrays.asList(true, false)) + .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put( + mapKey, + ExtendedAttributes.builder() + .put("childStr", "value") + .put("childLong", 1L) + .build()) + .put( + mapArrayKey, + Arrays.asList( + ExtendedAttributes.builder() + .put("childStr", "value") + .put("childLong", 1L) + .build(), + ExtendedAttributes.builder() + .put("childStr", "value") + .put("childLong", 1L) + .build())) + .put("key1", "value") + .put("key2", "value") + .build()); + }); + } +} diff --git a/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java b/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java index 10b43897a3e..5157702473c 100644 --- a/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java @@ -6,7 +6,9 @@ package io.opentelemetry.api.logs; import io.opentelemetry.api.testing.internal.AbstractDefaultLoggerTest; +import org.junit.jupiter.api.Disabled; +@Disabled class DefaultLoggerTest extends AbstractDefaultLoggerTest { @Override diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java index b6736fa3843..822e552f516 100644 --- a/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java @@ -6,7 +6,9 @@ package io.opentelemetry.api.trace; import io.opentelemetry.api.testing.internal.AbstractDefaultTracerTest; +import org.junit.jupiter.api.Disabled; +@Disabled class DefaultTracerTest extends AbstractDefaultTracerTest { @Override diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index e557956558f..c5c742fbead 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,2 +1,92 @@ Comparing source compatibility of opentelemetry-api-1.46.0-SNAPSHOT.jar against opentelemetry-api-1.45.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(+) io.opentelemetry.api.common.ExtendedAttributeKey asExtendedAttributeKey() ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributeKey (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributeKey asAttributeKey() + +++ NEW ANNOTATION: javax.annotation.Nullable + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> booleanArrayKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey booleanKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> doubleArrayKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey doubleKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String getKey() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributeType getType() + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> longArrayKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey longKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> mapArrayKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey mapKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> stringArrayKey(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey stringKey(java.lang.String) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributes (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.Attributes asAttributes() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Map,java.lang.Object> asMap() + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder builder() + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributes empty() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) void forEach(java.util.function.BiConsumer,? super java.lang.Object>) + +++ NEW METHOD: PUBLIC(+) java.lang.Object get(io.opentelemetry.api.common.AttributeKey) + +++ NEW ANNOTATION: javax.annotation.Nullable + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object get(io.opentelemetry.api.common.ExtendedAttributeKey) + +++ NEW ANNOTATION: javax.annotation.Nullable + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) boolean isEmpty() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) int size() + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder toBuilder() ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributes build() + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(io.opentelemetry.api.common.AttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(io.opentelemetry.api.common.ExtendedAttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, java.lang.String) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, long) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, double) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, boolean) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, io.opentelemetry.api.common.ExtendedAttributes) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, java.lang.String[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(io.opentelemetry.api.common.AttributeKey>, java.lang.Object[]) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, long[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, double[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, boolean[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, io.opentelemetry.api.common.ExtendedAttributes[]) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder putAll(io.opentelemetry.api.common.Attributes) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder putAll(io.opentelemetry.api.common.ExtendedAttributes) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder remove(io.opentelemetry.api.common.AttributeKey) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder remove(io.opentelemetry.api.common.ExtendedAttributeKey) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder removeIf(java.util.function.Predicate>) ++++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType (compatible) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: java.lang.constant.Constable + +++ NEW INTERFACE: java.lang.Comparable + +++ NEW INTERFACE: java.io.Serializable + +++ NEW SUPERCLASS: java.lang.Enum + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType STRING_ARRAY + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType DOUBLE_ARRAY + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType BOOLEAN_ARRAY + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType STRING + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType DOUBLE + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType MAP + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType MAP_ARRAY + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType BOOLEAN + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType LONG_ARRAY + +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType LONG + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeType valueOf(java.lang.String) + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeType[] values() +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.logs.LogRecordBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setAllAttributes(io.opentelemetry.api.common.ExtendedAttributes) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setAttribute(io.opentelemetry.api.common.ExtendedAttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt index dca61614aaf..230894a028c 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt @@ -1,12 +1,19 @@ Comparing source compatibility of opentelemetry-sdk-logs-1.46.0-SNAPSHOT.jar against opentelemetry-sdk-logs-1.45.0.jar +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.logs.data.LogRecordData (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributes getExtendedAttributes() *** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.logs.ReadWriteLogRecord (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) java.lang.Object getAttribute(io.opentelemetry.api.common.AttributeKey) +++ NEW ANNOTATION: javax.annotation.Nullable GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) java.lang.Object getAttribute(io.opentelemetry.api.common.ExtendedAttributeKey) + +++ NEW ANNOTATION: javax.annotation.Nullable + GENERIC TEMPLATES: +++ T:java.lang.Object +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.Attributes getAttributes() +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.Value getBodyValue() +++ NEW ANNOTATION: javax.annotation.Nullable + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributes getExtendedAttributes() +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.InstrumentationScopeInfo getInstrumentationScopeInfo() +++ NEW METHOD: PUBLIC(+) long getObservedTimestampEpochNanos() +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.Severity getSeverity() @@ -14,3 +21,6 @@ Comparing source compatibility of opentelemetry-sdk-logs-1.46.0-SNAPSHOT.jar aga +++ NEW ANNOTATION: javax.annotation.Nullable +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.trace.SpanContext getSpanContext() +++ NEW METHOD: PUBLIC(+) long getTimestampEpochNanos() + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ReadWriteLogRecord setAllAttributes(io.opentelemetry.api.common.ExtendedAttributes) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ReadWriteLogRecord setAttribute(io.opentelemetry.api.common.ExtendedAttributeKey, java.lang.Object) + GENERIC TEMPLATES: +++ T:java.lang.Object diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java index 6ad913c1029..d85ca263fc1 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java @@ -7,6 +7,8 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ExtendedAttributeKey; +import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; @@ -29,6 +31,11 @@ public interface ReadWriteLogRecord { */ ReadWriteLogRecord setAttribute(AttributeKey key, T value); + /** TODO. */ + default ReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value) { + return this; + } + // TODO: add additional setters /** @@ -49,6 +56,18 @@ default ReadWriteLogRecord setAllAttributes(Attributes attributes) { return this; } + /** TODO. */ + @SuppressWarnings("unchecked") + default ReadWriteLogRecord setAllAttributes(ExtendedAttributes extendedAttributes) { + if (extendedAttributes == null || extendedAttributes.isEmpty()) { + return this; + } + extendedAttributes.forEach( + (attributeKey, value) -> + this.setAttribute((ExtendedAttributeKey) attributeKey, value)); + return this; + } + /** Return an immutable {@link LogRecordData} instance representing this log record. */ LogRecordData toLogRecordData(); @@ -58,7 +77,13 @@ default ReadWriteLogRecord setAllAttributes(Attributes attributes) { */ @Nullable default T getAttribute(AttributeKey key) { - return toLogRecordData().getAttributes().get(key); + return toLogRecordData().getExtendedAttributes().get(key); + } + + /** TODO. */ + @Nullable + default T getAttribute(ExtendedAttributeKey key) { + return toLogRecordData().getExtendedAttributes().get(key); } /** Returns the instrumentation scope that generated this log. */ @@ -102,4 +127,9 @@ default Value getBodyValue() { default Attributes getAttributes() { return toLogRecordData().getAttributes(); } + + /** TODO. */ + default ExtendedAttributes getExtendedAttributes() { + return toLogRecordData().getExtendedAttributes(); + } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java index ce77d076730..eef595c096d 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java @@ -6,6 +6,9 @@ package io.opentelemetry.sdk.logs; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.ExtendedAttributeKey; +import io.opentelemetry.api.common.ExtendedAttributes; +import io.opentelemetry.api.common.ExtendedAttributesBuilder; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; import io.opentelemetry.api.logs.LogRecordBuilder; @@ -13,7 +16,6 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.internal.AttributesMap; import java.time.Instant; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -22,7 +24,8 @@ final class SdkLogRecordBuilder implements ExtendedLogRecordBuilder { private final LoggerSharedState loggerSharedState; - private final LogLimits logLimits; + // TODO: restore + // private final LogLimits logLimits; private final InstrumentationScopeInfo instrumentationScopeInfo; private long timestampEpochNanos; @@ -31,12 +34,12 @@ final class SdkLogRecordBuilder implements ExtendedLogRecordBuilder { private Severity severity = Severity.UNDEFINED_SEVERITY_NUMBER; @Nullable private String severityText; @Nullable private Value body; - @Nullable private AttributesMap attributes; + // TODO: apply log limits + @Nullable private ExtendedAttributesBuilder attributesBuilder; SdkLogRecordBuilder( LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) { this.loggerSharedState = loggerSharedState; - this.logLimits = loggerSharedState.getLogLimits(); this.instrumentationScopeInfo = instrumentationScopeInfo; } @@ -100,12 +103,18 @@ public SdkLogRecordBuilder setAttribute(AttributeKey key, T value) { if (key == null || key.getKey().isEmpty() || value == null) { return this; } - if (this.attributes == null) { - this.attributes = - AttributesMap.create( - logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); + return setAttribute(key.asExtendedAttributeKey(), value); + } + + @Override + public SdkLogRecordBuilder setAttribute(ExtendedAttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + if (attributesBuilder == null) { + attributesBuilder = ExtendedAttributes.builder(); } - this.attributes.put(key, value); + attributesBuilder.put(key, value); return this; } @@ -133,6 +142,6 @@ public void emit() { severity, severityText, body, - attributes)); + attributesBuilder)); } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java index 1927a0ec572..090f0711445 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java @@ -7,6 +7,7 @@ import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; @@ -32,7 +33,7 @@ static SdkLogRecordData create( Severity severity, @Nullable String severityText, @Nullable Value body, - Attributes attributes, + ExtendedAttributes attributes, int totalAttributeCount) { return new AutoValue_SdkLogRecordData( resource, @@ -42,15 +43,23 @@ static SdkLogRecordData create( spanContext, severity, severityText, - attributes, - totalAttributeCount, - body); + 0, + body, + attributes); } @Override @Nullable public abstract Value getBodyValue(); + @Override + public abstract ExtendedAttributes getExtendedAttributes(); + + @Override + public Attributes getAttributes() { + return getExtendedAttributes().asAttributes(); + } + @Override @SuppressWarnings("deprecation") // Implementation of deprecated method public io.opentelemetry.sdk.logs.data.Body getBody() { diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java index e6c68ce6ec4..18b5011b778 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java @@ -7,12 +7,14 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ExtendedAttributeKey; +import io.opentelemetry.api.common.ExtendedAttributes; +import io.opentelemetry.api.common.ExtendedAttributesBuilder; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; @@ -21,7 +23,8 @@ @ThreadSafe class SdkReadWriteLogRecord implements ReadWriteLogRecord { - private final LogLimits logLimits; + // TODO: restore + // private final LogLimits logLimits; private final Resource resource; private final InstrumentationScopeInfo instrumentationScopeInfo; private final long timestampEpochNanos; @@ -34,8 +37,9 @@ class SdkReadWriteLogRecord implements ReadWriteLogRecord { @GuardedBy("lock") @Nullable - private AttributesMap attributes; + private ExtendedAttributesBuilder attributesBuilder; + @SuppressWarnings("unused") private SdkReadWriteLogRecord( LogLimits logLimits, Resource resource, @@ -46,8 +50,7 @@ private SdkReadWriteLogRecord( Severity severity, @Nullable String severityText, @Nullable Value body, - @Nullable AttributesMap attributes) { - this.logLimits = logLimits; + @Nullable ExtendedAttributesBuilder attributesBuilder) { this.resource = resource; this.instrumentationScopeInfo = instrumentationScopeInfo; this.timestampEpochNanos = timestampEpochNanos; @@ -56,7 +59,7 @@ private SdkReadWriteLogRecord( this.severity = severity; this.severityText = severityText; this.body = body; - this.attributes = attributes; + this.attributesBuilder = attributesBuilder; } /** Create the log record with the given configuration. */ @@ -70,7 +73,7 @@ static SdkReadWriteLogRecord create( Severity severity, @Nullable String severityText, @Nullable Value body, - @Nullable AttributesMap attributes) { + @Nullable ExtendedAttributesBuilder attributesBuilder) { return new SdkReadWriteLogRecord( logLimits, resource, @@ -81,37 +84,44 @@ static SdkReadWriteLogRecord create( severity, severityText, body, - attributes); + attributesBuilder); } @Override public ReadWriteLogRecord setAttribute(AttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + return setAttribute(key.asExtendedAttributeKey(), value); + } + + @Override + public ReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value) { if (key == null || key.getKey().isEmpty() || value == null) { return this; } synchronized (lock) { - if (attributes == null) { - attributes = - AttributesMap.create( - logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); + if (attributesBuilder == null) { + attributesBuilder = ExtendedAttributes.builder(); } - attributes.put(key, value); + attributesBuilder.put(key, value); } return this; } - private Attributes getImmutableAttributes() { + private ExtendedAttributes getImmutableAttributes() { synchronized (lock) { - if (attributes == null || attributes.isEmpty()) { - return Attributes.empty(); + if (attributesBuilder == null) { + return ExtendedAttributes.empty(); } - return attributes.immutableCopy(); + return attributesBuilder.build(); } } @Override public LogRecordData toLogRecordData() { synchronized (lock) { + ExtendedAttributes attributes = getImmutableAttributes(); return SdkLogRecordData.create( resource, instrumentationScopeInfo, @@ -121,8 +131,8 @@ public LogRecordData toLogRecordData() { severity, severityText, body, - getImmutableAttributes(), - attributes == null ? 0 : attributes.getTotalAddedValues()); + attributes, + attributes.size()); } } @@ -165,17 +175,18 @@ public Value getBodyValue() { @Override public Attributes getAttributes() { - return getImmutableAttributes(); + return getImmutableAttributes().asAttributes(); } @Nullable @Override public T getAttribute(AttributeKey key) { - synchronized (lock) { - if (attributes == null || attributes.isEmpty()) { - return null; - } - return attributes.get(key); - } + return getImmutableAttributes().get(key); + } + + @Nullable + @Override + public T getAttribute(ExtendedAttributeKey key) { + return getImmutableAttributes().get(key); } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java index f21b175f52f..2a39d4a0e12 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.logs.data; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.common.ValueType; import io.opentelemetry.api.logs.Severity; @@ -74,6 +75,12 @@ default Value getBodyValue() { /** Returns the attributes for this log, or {@link Attributes#empty()} if unset. */ Attributes getAttributes(); + /** TODO. */ + default ExtendedAttributes getExtendedAttributes() { + // TODO: + return ExtendedAttributes.builder().build(); + } + /** * Returns the total number of attributes that were recorded on this log. * diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java index 7a444817d40..7e07bb8ea16 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java @@ -9,12 +9,14 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ExtendedAttributes; +import io.opentelemetry.api.common.ExtendedAttributesBuilder; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.resources.Resource; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class ReadWriteLogRecordTest { @@ -33,14 +35,16 @@ void addAllAttributes() { } @Test + @Disabled void addAllHandlesNull() { SdkReadWriteLogRecord logRecord = buildLogRecord(); Attributes originalAttributes = logRecord.getAttributes(); - ReadWriteLogRecord result = logRecord.setAllAttributes(null); + ReadWriteLogRecord result = logRecord.setAllAttributes((Attributes) null); assertThat(result.getAttributes()).isEqualTo(originalAttributes); } @Test + @Disabled void allHandlesEmpty() { SdkReadWriteLogRecord logRecord = buildLogRecord(); Attributes originalAttributes = logRecord.getAttributes(); @@ -50,7 +54,7 @@ void allHandlesEmpty() { SdkReadWriteLogRecord buildLogRecord() { Value body = Value.of("bod"); - AttributesMap initialAttributes = AttributesMap.create(100, 200); + ExtendedAttributesBuilder initialAttributes = ExtendedAttributes.builder(); initialAttributes.put(stringKey("foo"), "aaiosjfjioasdiojfjioasojifja"); initialAttributes.put(stringKey("untouched"), "yes"); LogLimits limits = LogLimits.getDefault(); diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java index 3c5743e673d..6ef0f197818 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java @@ -75,7 +75,7 @@ void emit_AllFields() { builder.setTimestamp(timestamp); builder.setObservedTimestamp(456, TimeUnit.SECONDS); builder.setObservedTimestamp(observedTimestamp); - builder.setAttribute(null, null); + builder.setAttribute((AttributeKey) null, null); builder.setAttribute(AttributeKey.stringKey("k1"), "v1"); builder.setAllAttributes(Attributes.builder().put("k2", "v2").put("k3", "v3").build()); builder.setContext(Span.wrap(spanContext).storeInContext(Context.root())); diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java index 05da79ee3eb..b13b893ac51 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java @@ -228,7 +228,7 @@ void loggerBuilder_WithLogRecordProcessor() { .setResource(resource) .addLogRecordProcessor( (unused, logRecord) -> { - logRecord.setAttribute(null, null); + logRecord.setAttribute((AttributeKey) null, null); // Overwrite k1 logRecord.setAttribute(AttributeKey.stringKey("k1"), "new-v1"); // Add new attribute k3 diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java index 2ea1ee19291..8b1d360d733 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class SdkLoggerTest { @@ -55,6 +56,7 @@ void logRecordBuilder() { } @Test + @Disabled void logRecordBuilder_maxAttributeLength() { int maxLength = 25; AtomicReference seenLog = new AtomicReference<>(); @@ -95,6 +97,7 @@ void logRecordBuilder_maxAttributeLength() { } @Test + @Disabled void logRecordBuilder_maxAttributes() { int maxNumberOfAttrs = 8; AtomicReference seenLog = new AtomicReference<>(); diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilderTest.java index bf45863d985..bead674417a 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/internal/SdkEventBuilderTest.java @@ -12,7 +12,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ExtendedAttributeKey; +import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.context.Context; @@ -24,15 +27,21 @@ class SdkEventBuilderTest { @Test + @SuppressWarnings("unchecked") void emit() { String eventName = "banana"; LogRecordBuilder logRecordBuilder = mock(LogRecordBuilder.class); when(logRecordBuilder.setTimestamp(anyLong(), any())).thenReturn(logRecordBuilder); - when(logRecordBuilder.setAttribute(any(), any())).thenReturn(logRecordBuilder); + when(logRecordBuilder.setAttribute(any(AttributeKey.class), any())) + .thenReturn(logRecordBuilder); + when(logRecordBuilder.setAttribute(any(ExtendedAttributeKey.class), any())) + .thenReturn(logRecordBuilder); when(logRecordBuilder.setContext(any())).thenReturn(logRecordBuilder); when(logRecordBuilder.setSeverity(any())).thenReturn(logRecordBuilder); - when(logRecordBuilder.setAllAttributes(any())).thenReturn(logRecordBuilder); + when(logRecordBuilder.setAllAttributes(any(Attributes.class))).thenReturn(logRecordBuilder); + when(logRecordBuilder.setAllAttributes(any(ExtendedAttributes.class))) + .thenReturn(logRecordBuilder); Instant instant = Instant.now(); Context context = Context.root(); From ea2155bc70a558a70bb44a4c361d3c9d9ae70c96 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Tue, 18 Feb 2025 13:49:32 -0600 Subject: [PATCH 2/8] Move to incubator, passing build --- .../api/common/AttributeKey.java | 7 +- .../internal/InternalAttributeKeyImpl.java | 42 ----- .../api/logs/LogRecordBuilder.java | 18 -- .../api/common/AttributeKeyTest.java | 2 +- .../common/ArrayBackedExtendedAttributes.java | 5 +- .../ArrayBackedExtendedAttributesBuilder.java | 27 ++- .../common/ExtendedAttributeKey.java | 25 +-- .../common/ExtendedAttributeType.java | 2 +- .../incubator}/common/ExtendedAttributes.java | 7 +- .../common/ExtendedAttributesBuilder.java | 30 ++-- .../InternalExtendedAttributeKeyImpl.java | 49 +++++- .../incubator/logs/ExtendedDefaultLogger.java | 27 +-- .../logs/ExtendedLogRecordBuilder.java | 78 +++++++++ .../common/ExtendedAttributesTest.java | 16 +- .../current_vs_latest/opentelemetry-api.txt | 92 +--------- .../opentelemetry-sdk-logs.txt | 18 +- exporters/logging-otlp/build.gradle.kts | 1 + exporters/otlp/common/build.gradle.kts | 1 + .../internal/otlp/logs/LogMarshaler.java | 1 + sdk/common/build.gradle.kts | 1 + .../sdk/internal/AttributesMap.java | 2 +- .../sdk/internal/ExtendedAttributesMap.java | 117 +++++++++++++ .../sdk/logs/ExtendedReadWriteLogRecord.java | 41 +++++ .../sdk/logs/ExtendedSdkLogRecordBuilder.java | 57 +++++- .../sdk/logs/ExtendedSdkLogRecordData.java | 70 ++++++++ .../logs/ExtendedSdkReadWriteLogRecord.java | 165 ++++++++++++++++++ .../sdk/logs/ReadWriteLogRecord.java | 32 +--- .../sdk/logs/SdkLogRecordBuilder.java | 55 ++---- .../sdk/logs/SdkLogRecordData.java | 27 +-- .../sdk/logs/SdkReadWriteLogRecord.java | 89 ++++------ .../sdk/logs/data/LogRecordData.java | 7 - .../data/internal/ExtendedLogRecordData.java | 11 ++ .../sdk/logs/ReadWriteLogRecordTest.java | 11 +- .../sdk/logs/SdkLogRecordBuilderTest.java | 2 - sdk/testing/build.gradle.kts | 11 ++ .../internal/TestExtendedLogRecordData.java | 8 +- 36 files changed, 778 insertions(+), 376 deletions(-) rename api/{all/src/main/java/io/opentelemetry/api => incubator/src/main/java/io/opentelemetry/api/incubator}/common/ArrayBackedExtendedAttributes.java (92%) rename api/{all/src/main/java/io/opentelemetry/api => incubator/src/main/java/io/opentelemetry/api/incubator}/common/ArrayBackedExtendedAttributesBuilder.java (70%) rename api/{all/src/main/java/io/opentelemetry/api => incubator/src/main/java/io/opentelemetry/api/incubator}/common/ExtendedAttributeKey.java (73%) rename api/{all/src/main/java/io/opentelemetry/api => incubator/src/main/java/io/opentelemetry/api/incubator}/common/ExtendedAttributeType.java (84%) rename api/{all/src/main/java/io/opentelemetry/api => incubator/src/main/java/io/opentelemetry/api/incubator}/common/ExtendedAttributes.java (90%) rename api/{all/src/main/java/io/opentelemetry/api => incubator/src/main/java/io/opentelemetry/api/incubator}/common/ExtendedAttributesBuilder.java (84%) rename api/{all/src/main/java/io/opentelemetry/api => incubator/src/main/java/io/opentelemetry/api/incubator}/internal/InternalExtendedAttributeKeyImpl.java (65%) rename api/{all/src/test/java/io/opentelemetry/api => incubator/src/test/java/io/opentelemetry/api/incubator}/common/ExtendedAttributesTest.java (92%) create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java create mode 100644 sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java create mode 100644 sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java create mode 100644 sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java 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 d25b582d237..7d012aa14ca 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 @@ -26,9 +26,10 @@ public interface AttributeKey { /** Returns the type of attribute for this key. Useful for building switch statements. */ AttributeType getType(); - default ExtendedAttributeKey asExtendedAttributeKey() { - return InternalAttributeKeyImpl.toExtendedAttributeKey(this); - } + // TODO (jack-berg): uncomment when extended attributes are promoted from incubator to API + // default ExtendedAttributeKey asExtendedAttributeKey() { + // return InternalAttributeKeyImpl.toExtendedAttributeKey(this); + // } /** Returns a new AttributeKey for String valued attributes. */ static AttributeKey stringKey(String key) { diff --git a/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java b/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java index 1383a3805a7..dfeca408c19 100644 --- a/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java +++ b/api/all/src/main/java/io/opentelemetry/api/internal/InternalAttributeKeyImpl.java @@ -7,8 +7,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributeType; -import io.opentelemetry.api.common.ExtendedAttributeKey; -import io.opentelemetry.api.common.ExtendedAttributeType; import java.nio.charset.StandardCharsets; import javax.annotation.Nullable; @@ -25,7 +23,6 @@ public final class InternalAttributeKeyImpl implements AttributeKey { private final int hashCode; @Nullable private byte[] keyUtf8; - @Nullable private ExtendedAttributeKey extendedAttributeKey; private InternalAttributeKeyImpl(AttributeType type, String key) { if (type == null) { @@ -65,14 +62,6 @@ public String getKey() { return key; } - @Override - public ExtendedAttributeKey asExtendedAttributeKey() { - if (extendedAttributeKey == null) { - extendedAttributeKey = InternalAttributeKeyImpl.toExtendedAttributeKey(this); - } - return extendedAttributeKey; - } - /** Returns the key, encoded as UTF-8 bytes. */ public byte[] getKeyUtf8() { byte[] keyUtf8 = this.keyUtf8; @@ -119,35 +108,4 @@ private static int buildHashCode(AttributeType type, String key) { result ^= key.hashCode(); return result; } - - /** TODO. */ - public static ExtendedAttributeKey toExtendedAttributeKey(AttributeKey attributeKey) { - switch (attributeKey.getType()) { - case STRING: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.STRING); - case BOOLEAN: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.BOOLEAN); - case LONG: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.LONG); - case DOUBLE: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.DOUBLE); - case STRING_ARRAY: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.STRING_ARRAY); - case BOOLEAN_ARRAY: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.BOOLEAN_ARRAY); - case LONG_ARRAY: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.LONG_ARRAY); - case DOUBLE_ARRAY: - return InternalExtendedAttributeKeyImpl.create( - attributeKey.getKey(), ExtendedAttributeType.DOUBLE_ARRAY); - } - throw new IllegalArgumentException("Unrecognized attributeKey type: " + attributeKey.getType()); - } } diff --git a/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java b/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java index 03cfbc133e8..2166ab2b6b8 100644 --- a/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java @@ -7,8 +7,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.ExtendedAttributeKey; -import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.context.Context; import java.time.Instant; @@ -100,25 +98,9 @@ default LogRecordBuilder setAllAttributes(Attributes attributes) { return this; } - /** TODO. */ - @SuppressWarnings("unchecked") - default LogRecordBuilder setAllAttributes(ExtendedAttributes attributes) { - if (attributes == null || attributes.isEmpty()) { - return this; - } - attributes.forEach( - (attributeKey, value) -> setAttribute((ExtendedAttributeKey) attributeKey, value)); - return this; - } - /** Sets an attribute. */ LogRecordBuilder setAttribute(AttributeKey key, T value); - /** TODO. */ - default LogRecordBuilder setAttribute(ExtendedAttributeKey key, T value) { - return this; - } - /** Emit the log record. */ void emit(); } diff --git a/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java b/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java index 32dbd3eee4a..4b39e1d6a91 100644 --- a/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/common/AttributeKeyTest.java @@ -16,7 +16,7 @@ class AttributeKeyTest { @Test void equalsVerifier() { EqualsVerifier.forClass(InternalAttributeKeyImpl.class) - .withIgnoredFields("keyUtf8", "extendedAttributeKey") + .withIgnoredFields("keyUtf8") .withCachedHashCode( "hashCode", "buildHashCode", diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributes.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributes.java similarity index 92% rename from api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributes.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributes.java index 48f0183991d..a864a1d9585 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributes.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributes.java @@ -3,8 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.common; +package io.opentelemetry.api.incubator.common; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.internal.ImmutableKeyValuePairs; import java.util.ArrayList; import java.util.Comparator; diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributesBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributesBuilder.java similarity index 70% rename from api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributesBuilder.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributesBuilder.java index 5afb327c6e9..891707c61af 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedExtendedAttributesBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ArrayBackedExtendedAttributesBuilder.java @@ -3,9 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.common; +package io.opentelemetry.api.incubator.common; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.function.Predicate; @@ -56,4 +57,28 @@ public ExtendedAttributesBuilder removeIf(Predicate> pre } return this; } + + static List toList(double... values) { + Double[] boxed = new Double[values.length]; + for (int i = 0; i < values.length; i++) { + boxed[i] = values[i]; + } + return Arrays.asList(boxed); + } + + static List toList(long... values) { + Long[] boxed = new Long[values.length]; + for (int i = 0; i < values.length; i++) { + boxed[i] = values[i]; + } + return Arrays.asList(boxed); + } + + static List toList(boolean... values) { + Boolean[] boxed = new Boolean[values.length]; + for (int i = 0; i < values.length; i++) { + boxed[i] = values[i]; + } + return Arrays.asList(boxed); + } } diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeKey.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKey.java similarity index 73% rename from api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeKey.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKey.java index 42094bb283d..14306de3ba1 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeKey.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKey.java @@ -3,9 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.common; +package io.opentelemetry.api.incubator.common; -import io.opentelemetry.api.internal.InternalExtendedAttributeKeyImpl; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.incubator.internal.InternalExtendedAttributeKeyImpl; import java.util.List; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -24,44 +25,48 @@ default AttributeKey asAttributeKey() { return InternalExtendedAttributeKeyImpl.toAttributeKey(this); } + static ExtendedAttributeKey fromAttributeKey(AttributeKey attributeKey) { + return InternalExtendedAttributeKeyImpl.fromAttributeKey(attributeKey); + } + /** Returns a new ExtendedAttributeKey for String valued attributes. */ static ExtendedAttributeKey stringKey(String key) { - return AttributeKey.stringKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.stringKey(key)); } /** Returns a new ExtendedAttributeKey for Boolean valued attributes. */ static ExtendedAttributeKey booleanKey(String key) { - return AttributeKey.booleanKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.booleanKey(key)); } /** Returns a new ExtendedAttributeKey for Long valued attributes. */ static ExtendedAttributeKey longKey(String key) { - return AttributeKey.longKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.longKey(key)); } /** Returns a new ExtendedAttributeKey for Double valued attributes. */ static ExtendedAttributeKey doubleKey(String key) { - return AttributeKey.doubleKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.doubleKey(key)); } /** Returns a new ExtendedAttributeKey for List<String> valued attributes. */ static ExtendedAttributeKey> stringArrayKey(String key) { - return AttributeKey.stringArrayKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.stringArrayKey(key)); } /** Returns a new ExtendedAttributeKey for List<Boolean> valued attributes. */ static ExtendedAttributeKey> booleanArrayKey(String key) { - return AttributeKey.booleanArrayKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.booleanArrayKey(key)); } /** Returns a new ExtendedAttributeKey for List<Long> valued attributes. */ static ExtendedAttributeKey> longArrayKey(String key) { - return AttributeKey.longArrayKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.longArrayKey(key)); } /** Returns a new ExtendedAttributeKey for List<Double> valued attributes. */ static ExtendedAttributeKey> doubleArrayKey(String key) { - return AttributeKey.doubleArrayKey(key).asExtendedAttributeKey(); + return fromAttributeKey(AttributeKey.doubleArrayKey(key)); } /** Returns a new ExtendedAttributeKey for Map valued attributes. */ diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeType.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java similarity index 84% rename from api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeType.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java index bba490e6a4b..05adcfa2639 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributeType.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributeType.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.common; +package io.opentelemetry.api.incubator.common; /** TODO. */ public enum ExtendedAttributeType { diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributes.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributes.java similarity index 90% rename from api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributes.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributes.java index bbbac1ee5b9..bd8347e2035 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributes.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributes.java @@ -3,14 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.common; +package io.opentelemetry.api.incubator.common; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import java.util.Map; import java.util.function.BiConsumer; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -@SuppressWarnings("rawtypes") @Immutable public interface ExtendedAttributes { @@ -20,7 +21,7 @@ default T get(AttributeKey key) { if (key == null) { return null; } - return get(key.asExtendedAttributeKey()); + return get(ExtendedAttributeKey.fromAttributeKey(key)); } /** Returns the value for the given {@link ExtendedAttributeKey}, or {@code null} if not found. */ diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributesBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java similarity index 84% rename from api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributesBuilder.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java index 2293f062f6f..83f9be53fd5 100644 --- a/api/all/src/main/java/io/opentelemetry/api/common/ExtendedAttributesBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/common/ExtendedAttributesBuilder.java @@ -3,18 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.common; - -import static io.opentelemetry.api.common.ArrayBackedAttributesBuilder.toList; -import static io.opentelemetry.api.common.ExtendedAttributeKey.booleanArrayKey; -import static io.opentelemetry.api.common.ExtendedAttributeKey.booleanKey; -import static io.opentelemetry.api.common.ExtendedAttributeKey.doubleArrayKey; -import static io.opentelemetry.api.common.ExtendedAttributeKey.doubleKey; -import static io.opentelemetry.api.common.ExtendedAttributeKey.longArrayKey; -import static io.opentelemetry.api.common.ExtendedAttributeKey.longKey; -import static io.opentelemetry.api.common.ExtendedAttributeKey.stringArrayKey; -import static io.opentelemetry.api.common.ExtendedAttributeKey.stringKey; - +package io.opentelemetry.api.incubator.common; + +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.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.AttributeKey; +import io.opentelemetry.api.common.Attributes; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; @@ -29,7 +31,7 @@ default ExtendedAttributesBuilder put(AttributeKey key, T value) { if (key == null || key.getKey().isEmpty() || value == null) { return this; } - return put(key.asExtendedAttributeKey(), value); + return put(ExtendedAttributeKey.fromAttributeKey(key), value); } /** TODO. */ @@ -200,7 +202,7 @@ default ExtendedAttributesBuilder putAll(ExtendedAttributes attributes) { * @return this Builder */ default ExtendedAttributesBuilder remove(AttributeKey key) { - return remove(key.asExtendedAttributeKey()); + return remove(ExtendedAttributeKey.fromAttributeKey(key)); } /** TODO. */ diff --git a/api/all/src/main/java/io/opentelemetry/api/internal/InternalExtendedAttributeKeyImpl.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java similarity index 65% rename from api/all/src/main/java/io/opentelemetry/api/internal/InternalExtendedAttributeKeyImpl.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java index d8aaca6e807..eb7e700e3d3 100644 --- a/api/all/src/main/java/io/opentelemetry/api/internal/InternalExtendedAttributeKeyImpl.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/internal/InternalExtendedAttributeKeyImpl.java @@ -3,13 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.internal; +package io.opentelemetry.api.incubator.internal; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributeType; -import io.opentelemetry.api.common.ExtendedAttributeKey; -import io.opentelemetry.api.common.ExtendedAttributeType; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributeType; +import io.opentelemetry.api.internal.InternalAttributeKeyImpl; import java.nio.charset.StandardCharsets; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; /** @@ -18,6 +20,9 @@ */ public final class InternalExtendedAttributeKeyImpl implements ExtendedAttributeKey { + private static final ConcurrentHashMap, ExtendedAttributeKey> + ATTRIBUTE_KEY_CACHE = new ConcurrentHashMap<>(); + private final ExtendedAttributeType type; private final String key; private final int hashCode; @@ -42,6 +47,13 @@ public static ExtendedAttributeKey create( return new InternalExtendedAttributeKeyImpl<>(type, key != null ? key : ""); } + @SuppressWarnings("unchecked") + public static ExtendedAttributeKey fromAttributeKey(AttributeKey attributeKey) { + return (ExtendedAttributeKey) + ATTRIBUTE_KEY_CACHE.computeIfAbsent( + attributeKey, InternalExtendedAttributeKeyImpl::toExtendedAttributeKey); + } + @Override public ExtendedAttributeType getType() { return type; @@ -140,4 +152,35 @@ public static AttributeKey toAttributeKey(ExtendedAttributeKey extende throw new IllegalArgumentException( "Unrecognized extendedAttributeKey type: " + extendedAttributeKey.getType()); } + + /** TODO. */ + public static ExtendedAttributeKey toExtendedAttributeKey(AttributeKey attributeKey) { + switch (attributeKey.getType()) { + case STRING: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.STRING); + case BOOLEAN: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.BOOLEAN); + case LONG: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.LONG); + case DOUBLE: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.DOUBLE); + case STRING_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.STRING_ARRAY); + case BOOLEAN_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.BOOLEAN_ARRAY); + case LONG_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.LONG_ARRAY); + case DOUBLE_ARRAY: + return InternalExtendedAttributeKeyImpl.create( + attributeKey.getKey(), ExtendedAttributeType.DOUBLE_ARRAY); + } + throw new IllegalArgumentException("Unrecognized attributeKey type: " + attributeKey.getType()); + } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java index 378af1c6b00..631607846db 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedDefaultLogger.java @@ -7,7 +7,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Value; -import io.opentelemetry.api.logs.LogRecordBuilder; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.context.Context; @@ -41,52 +41,57 @@ public ExtendedLogRecordBuilder setEventName(String eventName) { } @Override - public LogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { + public ExtendedLogRecordBuilder setAttribute(ExtendedAttributeKey key, T value) { return this; } @Override - public LogRecordBuilder setTimestamp(Instant instant) { + public ExtendedLogRecordBuilder setAttribute(AttributeKey key, T value) { return this; } @Override - public LogRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) { + public ExtendedLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { return this; } @Override - public LogRecordBuilder setObservedTimestamp(Instant instant) { + public ExtendedLogRecordBuilder setTimestamp(Instant instant) { return this; } @Override - public LogRecordBuilder setContext(Context context) { + public ExtendedLogRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) { return this; } @Override - public LogRecordBuilder setSeverity(Severity severity) { + public ExtendedLogRecordBuilder setObservedTimestamp(Instant instant) { return this; } @Override - public LogRecordBuilder setSeverityText(String severityText) { + public ExtendedLogRecordBuilder setContext(Context context) { return this; } @Override - public LogRecordBuilder setBody(String body) { + public ExtendedLogRecordBuilder setSeverity(Severity severity) { return this; } @Override - public LogRecordBuilder setBody(Value body) { + public ExtendedLogRecordBuilder setSeverityText(String severityText) { return this; } @Override - public LogRecordBuilder setAttribute(AttributeKey key, T value) { + public ExtendedLogRecordBuilder setBody(String body) { + return this; + } + + @Override + public ExtendedLogRecordBuilder setBody(Value body) { return this; } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java index 4a18baa07ed..72d6260d410 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java @@ -5,13 +5,61 @@ package io.opentelemetry.api.incubator.logs; +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.LogRecordBuilder; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.context.Context; +import java.time.Instant; +import java.util.concurrent.TimeUnit; /** Extended {@link LogRecordBuilder} with experimental APIs. */ public interface ExtendedLogRecordBuilder extends LogRecordBuilder { // keep this class even if it is empty, since experimental methods may be added in the future. + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit); + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setTimestamp(Instant instant); + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit); + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setObservedTimestamp(Instant instant); + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setContext(Context context); + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setSeverity(Severity severity); + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setSeverityText(String severityText); + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setBody(String body); + + /** {@inheritDoc} */ + @Override + default ExtendedLogRecordBuilder setBody(Value body) { + setBody(body.asString()); + return this; + } + /** * Sets the event name, which identifies the class / type of the Event. * @@ -19,4 +67,34 @@ public interface ExtendedLogRecordBuilder extends LogRecordBuilder { * record with a non-empty event name is an Event. */ ExtendedLogRecordBuilder setEventName(String eventName); + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + default ExtendedLogRecordBuilder setAllAttributes(Attributes attributes) { + if (attributes == null || attributes.isEmpty()) { + return this; + } + attributes.forEach( + (attributeKey, value) -> setAttribute((AttributeKey) attributeKey, value)); + return this; + } + + /** TODO. */ + @SuppressWarnings("unchecked") + default ExtendedLogRecordBuilder setAllAttributes(ExtendedAttributes attributes) { + if (attributes == null || attributes.isEmpty()) { + return this; + } + attributes.forEach( + (attributeKey, value) -> setAttribute((ExtendedAttributeKey) attributeKey, value)); + return this; + } + + /** {@inheritDoc} */ + @Override + ExtendedLogRecordBuilder setAttribute(AttributeKey key, T value); + + /** TODO. */ + ExtendedLogRecordBuilder setAttribute(ExtendedAttributeKey key, T value); } diff --git a/api/all/src/test/java/io/opentelemetry/api/common/ExtendedAttributesTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java similarity index 92% rename from api/all/src/test/java/io/opentelemetry/api/common/ExtendedAttributesTest.java rename to api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java index d9294fb2633..c498192db9c 100644 --- a/api/all/src/test/java/io/opentelemetry/api/common/ExtendedAttributesTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java @@ -3,12 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.common; +package io.opentelemetry.api.incubator.common; import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import java.util.Arrays; @@ -121,8 +125,7 @@ void logRecordBuilder() { Logger logger = loggerProvider.get("logger"); // Can set either standard or extended attributes on - logger - .logRecordBuilder() + ((ExtendedLogRecordBuilder) logger.logRecordBuilder()) .setBody("message") .setAttribute(strKey, "value") .setAttribute(longKey, 1L) @@ -147,9 +150,12 @@ void logRecordBuilder() { assertThat(exporter.getFinishedLogRecordItems()) .satisfiesExactly( logRecordData -> { + assertThat(logRecordData).isInstanceOf(ExtendedLogRecordData.class); + ExtendedLogRecordData extendedLogRecordData = (ExtendedLogRecordData) logRecordData; + // Optionally access standard attributes, which filters out any extended attribute // types - assertThat(logRecordData.getAttributes()) + assertThat(extendedLogRecordData.getAttributes()) .isEqualTo( Attributes.builder() .put(strKey, "value") @@ -165,7 +171,7 @@ void logRecordBuilder() { .build()); // But preferably access and serialize full extended attributes - assertThat(logRecordData.getExtendedAttributes()) + assertThat(extendedLogRecordData.getExtendedAttributes()) .isEqualTo( ExtendedAttributes.builder() .put(strKey, "value") diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index 4bbc3306172..ef68fdedeeb 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,92 +1,2 @@ Comparing source compatibility of opentelemetry-api-1.48.0-SNAPSHOT.jar against opentelemetry-api-1.47.0.jar -*** 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(+) io.opentelemetry.api.common.ExtendedAttributeKey asExtendedAttributeKey() -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributeKey (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributeKey asAttributeKey() - +++ NEW ANNOTATION: javax.annotation.Nullable - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> booleanArrayKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey booleanKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> doubleArrayKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey doubleKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.String getKey() - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributeType getType() - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> longArrayKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey longKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> mapArrayKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey mapKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey> stringArrayKey(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeKey stringKey(java.lang.String) -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributes (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.Attributes asAttributes() - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Map,java.lang.Object> asMap() - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder builder() - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributes empty() - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) void forEach(java.util.function.BiConsumer,? super java.lang.Object>) - +++ NEW METHOD: PUBLIC(+) java.lang.Object get(io.opentelemetry.api.common.AttributeKey) - +++ NEW ANNOTATION: javax.annotation.Nullable - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object get(io.opentelemetry.api.common.ExtendedAttributeKey) - +++ NEW ANNOTATION: javax.annotation.Nullable - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) boolean isEmpty() - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) int size() - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder toBuilder() -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributes build() - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(io.opentelemetry.api.common.AttributeKey, java.lang.Object) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(io.opentelemetry.api.common.ExtendedAttributeKey, java.lang.Object) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, java.lang.String) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, long) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, double) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, boolean) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, io.opentelemetry.api.common.ExtendedAttributes) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, java.lang.String[]) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(io.opentelemetry.api.common.AttributeKey>, java.lang.Object[]) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, long[]) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, double[]) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, boolean[]) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder put(java.lang.String, io.opentelemetry.api.common.ExtendedAttributes[]) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder putAll(io.opentelemetry.api.common.Attributes) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder putAll(io.opentelemetry.api.common.ExtendedAttributes) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder remove(io.opentelemetry.api.common.AttributeKey) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributesBuilder remove(io.opentelemetry.api.common.ExtendedAttributeKey) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.ExtendedAttributesBuilder removeIf(java.util.function.Predicate>) -+++ NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType (compatible) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: java.lang.constant.Constable - +++ NEW INTERFACE: java.lang.Comparable - +++ NEW INTERFACE: java.io.Serializable - +++ NEW SUPERCLASS: java.lang.Enum - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType STRING_ARRAY - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType DOUBLE_ARRAY - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType BOOLEAN_ARRAY - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType STRING - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType DOUBLE - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType MAP - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType MAP_ARRAY - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType BOOLEAN - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType LONG_ARRAY - +++ NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.api.common.ExtendedAttributeType LONG - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeType valueOf(java.lang.String) - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.common.ExtendedAttributeType[] values() -*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.logs.LogRecordBuilder (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setAllAttributes(io.opentelemetry.api.common.ExtendedAttributes) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.LogRecordBuilder setAttribute(io.opentelemetry.api.common.ExtendedAttributeKey, java.lang.Object) - GENERIC TEMPLATES: +++ T:java.lang.Object +No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt index efe047d5a94..4aaaf109629 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt @@ -1,13 +1,13 @@ Comparing source compatibility of opentelemetry-sdk-logs-1.48.0-SNAPSHOT.jar against opentelemetry-sdk-logs-1.47.0.jar -*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.logs.data.LogRecordData (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributes getExtendedAttributes() -*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.logs.ReadWriteLogRecord (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) java.lang.Object getAttribute(io.opentelemetry.api.common.ExtendedAttributeKey) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.logs.ExtendedReadWriteLogRecord (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: io.opentelemetry.sdk.logs.ReadWriteLogRecord + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object getAttribute(io.opentelemetry.api.incubator.common.ExtendedAttributeKey) +++ NEW ANNOTATION: javax.annotation.Nullable GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.ExtendedAttributes getExtendedAttributes() - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ReadWriteLogRecord setAllAttributes(io.opentelemetry.api.common.ExtendedAttributes) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ReadWriteLogRecord setAttribute(io.opentelemetry.api.common.ExtendedAttributeKey, java.lang.Object) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.incubator.common.ExtendedAttributes getExtendedAttributes() + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ExtendedReadWriteLogRecord setAllAttributes(io.opentelemetry.api.incubator.common.ExtendedAttributes) + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.logs.ExtendedReadWriteLogRecord setAttribute(io.opentelemetry.api.incubator.common.ExtendedAttributeKey, java.lang.Object) GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData toLogRecordData() diff --git a/exporters/logging-otlp/build.gradle.kts b/exporters/logging-otlp/build.gradle.kts index a65f52f8522..4e3be802e40 100644 --- a/exporters/logging-otlp/build.gradle.kts +++ b/exporters/logging-otlp/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-core") testImplementation(project(":sdk:testing")) + testImplementation(project(":api:incubator")) testImplementation("com.google.guava:guava") testImplementation("org.skyscreamer:jsonassert") diff --git a/exporters/otlp/common/build.gradle.kts b/exporters/otlp/common/build.gradle.kts index 94d50e46fd0..627e25ef0d6 100644 --- a/exporters/otlp/common/build.gradle.kts +++ b/exporters/otlp/common/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { testImplementation(project(":sdk:trace")) testImplementation(project(":sdk:logs")) testImplementation(project(":sdk:testing")) + testImplementation(project(":api:incubator")) testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.google.protobuf:protobuf-java-util") diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java index 0ad265e9655..fb9f036308b 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java @@ -61,6 +61,7 @@ static LogMarshaler create(LogRecordData logRecordData) { spanContext.getTraceFlags(), spanContext.getTraceId().equals(INVALID_TRACE_ID) ? null : spanContext.getTraceId(), spanContext.getSpanId().equals(INVALID_SPAN_ID) ? null : spanContext.getSpanId(), + // TODO: protect against incubator not being present logRecordData instanceof ExtendedLogRecordData ? MarshalerUtil.toBytes(((ExtendedLogRecordData) logRecordData).getEventName()) : EMPTY_BYTES); diff --git a/sdk/common/build.gradle.kts b/sdk/common/build.gradle.kts index fab4fcd6cd4..ec132a505ed 100644 --- a/sdk/common/build.gradle.kts +++ b/sdk/common/build.gradle.kts @@ -14,6 +14,7 @@ val mrJarVersions = listOf(9) dependencies { api(project(":api:all")) + compileOnly(project(":api:incubator")) annotationProcessor("com.google.auto.value:auto-value") diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributesMap.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributesMap.java index 735d5eb1341..5dff5fabb7f 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributesMap.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributesMap.java @@ -53,7 +53,7 @@ public void put(AttributeKey key, T value) { super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit)); } - /** Get the total number of attributes added, including those dropped for capcity limits. */ + /** Get the total number of attributes added, including those dropped for capacity limits. */ public int getTotalAddedValues() { return totalAddedValues; } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java new file mode 100644 index 00000000000..027314ca3fd --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java @@ -0,0 +1,117 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.internal; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import javax.annotation.Nullable; + +/** + * TODO. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ExtendedAttributesMap extends HashMap, Object> + implements ExtendedAttributes { + + private static final long serialVersionUID = -2674974862318200501L; + + private final long capacity; + private final int lengthLimit; + private int totalAddedValues = 0; + + private ExtendedAttributesMap(long capacity, int lengthLimit) { + this.capacity = capacity; + this.lengthLimit = lengthLimit; + } + + /** + * Create an instance. + * + * @param capacity the max number of extended attribute entries + * @param lengthLimit the maximum length of string attributes + */ + public static ExtendedAttributesMap create(long capacity, int lengthLimit) { + return new ExtendedAttributesMap(capacity, lengthLimit); + } + + /** Add the attribute key value pair, applying capacity and length limits. */ + public void put(ExtendedAttributeKey key, T value) { + totalAddedValues++; + // TODO(jack-berg): apply capcity to nested entries + if (size() >= capacity && !containsKey(key)) { + return; + } + // TODO(jack-berg): apply limits to nested entries + super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit)); + } + + /** + * Get the total number of extended attributes added, including those dropped for capacity limits. + */ + public int getTotalAddedValues() { + return totalAddedValues; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T get(ExtendedAttributeKey key) { + return (T) super.get(key); + } + + @Override + public Map, Object> asMap() { + // Because ExtendedAttributes is marked Immutable, IDEs may recognize this as redundant usage. + // However, this class is private and is actually mutable, so we need to wrap with + // unmodifiableMap anyways. We implement the immutable ExtendedAttributes for this class to + // support the ExtendedAttributes.builder().putAll usage - it is tricky but an implementation + // detail of this private class. + return Collections.unmodifiableMap(this); + } + + @Override + public ExtendedAttributesBuilder toBuilder() { + return ExtendedAttributes.builder().putAll(this); + } + + @Override + public void forEach(BiConsumer, ? super Object> action) { + // https://github.com/open-telemetry/opentelemetry-java/issues/4161 + // Help out android desugaring by having an explicit call to HashMap.forEach, when forEach is + // just called through ExtendedAttributes.forEach desugaring is unable to correctly handle it. + super.forEach(action); + } + + @Override + public Attributes asAttributes() { + return immutableCopy().asAttributes(); + } + + @Override + public String toString() { + return "ExtendedAttributesMap{" + + "data=" + + super.toString() + + ", capacity=" + + capacity + + ", totalAddedValues=" + + totalAddedValues + + '}'; + } + + /** Create an immutable copy of the extended attributes in this map. */ + public ExtendedAttributes immutableCopy() { + return ExtendedAttributes.builder().putAll(this).build(); + } +} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java new file mode 100644 index 00000000000..908f0123511 --- /dev/null +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.logs; + +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; +import javax.annotation.Nullable; + +/** TODO. */ +public interface ExtendedReadWriteLogRecord extends ReadWriteLogRecord { + + /** TODO. */ + ExtendedReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value); + + /** TODO. */ + @SuppressWarnings("unchecked") + default ExtendedReadWriteLogRecord setAllAttributes(ExtendedAttributes extendedAttributes) { + if (extendedAttributes == null || extendedAttributes.isEmpty()) { + return this; + } + extendedAttributes.forEach( + (attributeKey, value) -> + this.setAttribute((ExtendedAttributeKey) attributeKey, value)); + return this; + } + + /** Return an immutable {@link ExtendedLogRecordData} instance representing this log record. */ + @Override + ExtendedLogRecordData toLogRecordData(); + + /** TODO. */ + @Nullable + T getAttribute(ExtendedAttributeKey key); + + /** TODO. */ + ExtendedAttributes getExtendedAttributes(); +} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java index 9f874d1f4b3..8b663bbbf3b 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordBuilder.java @@ -7,17 +7,24 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.ExtendedAttributesMap; import java.time.Instant; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; /** SDK implementation of {@link ExtendedLogRecordBuilder}. */ final class ExtendedSdkLogRecordBuilder extends SdkLogRecordBuilder implements ExtendedLogRecordBuilder { + @Nullable private String eventName; + @Nullable private ExtendedAttributesMap extendedAttributes; + ExtendedSdkLogRecordBuilder( LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) { super(loggerSharedState, instrumentationScopeInfo); @@ -25,7 +32,7 @@ final class ExtendedSdkLogRecordBuilder extends SdkLogRecordBuilder @Override public ExtendedSdkLogRecordBuilder setEventName(String eventName) { - super.setEventName(eventName); + this.eventName = eventName; return this; } @@ -84,8 +91,52 @@ public ExtendedSdkLogRecordBuilder setBody(Value value) { } @Override - public ExtendedSdkLogRecordBuilder setAttribute(AttributeKey key, T value) { - super.setAttribute(key, value); + public ExtendedSdkLogRecordBuilder setAttribute(ExtendedAttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + if (this.extendedAttributes == null) { + this.extendedAttributes = + ExtendedAttributesMap.create( + logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); + } + this.extendedAttributes.put(key, value); return this; } + + @Override + public ExtendedSdkLogRecordBuilder setAttribute(AttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + return setAttribute(ExtendedAttributeKey.fromAttributeKey(key), value); + } + + @Override + public void emit() { + if (loggerSharedState.hasBeenShutdown()) { + return; + } + Context context = this.context == null ? Context.current() : this.context; + long observedTimestampEpochNanos = + this.observedTimestampEpochNanos == 0 + ? this.loggerSharedState.getClock().now() + : this.observedTimestampEpochNanos; + loggerSharedState + .getLogRecordProcessor() + .onEmit( + context, + ExtendedSdkReadWriteLogRecord.create( + loggerSharedState.getLogLimits(), + loggerSharedState.getResource(), + instrumentationScopeInfo, + eventName, + timestampEpochNanos, + observedTimestampEpochNanos, + Span.fromContext(context).getSpanContext(), + severity, + severityText, + body, + extendedAttributes)); + } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java new file mode 100644 index 00000000000..d216c6704ca --- /dev/null +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.logs; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@AutoValue +@AutoValue.CopyAnnotations +@Immutable +abstract class ExtendedSdkLogRecordData implements ExtendedLogRecordData { + + ExtendedSdkLogRecordData() {} + + static ExtendedSdkLogRecordData create( + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo, + @Nullable String eventName, + long epochNanos, + long observedEpochNanos, + SpanContext spanContext, + Severity severity, + @Nullable String severityText, + @Nullable Value body, + ExtendedAttributes attributes, + int totalAttributeCount) { + return new AutoValue_ExtendedSdkLogRecordData( + resource, + instrumentationScopeInfo, + epochNanos, + observedEpochNanos, + spanContext, + severity, + severityText, + totalAttributeCount, + eventName, + attributes, + body); + } + + @Override + @Nullable + public abstract Value getBodyValue(); + + @Override + @SuppressWarnings("deprecation") // Implementation of deprecated method + public io.opentelemetry.sdk.logs.data.Body getBody() { + Value valueBody = getBodyValue(); + return valueBody == null + ? io.opentelemetry.sdk.logs.data.Body.empty() + : io.opentelemetry.sdk.logs.data.Body.string(valueBody.asString()); + } + + @Override + public Attributes getAttributes() { + return getExtendedAttributes().asAttributes(); + } +} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java new file mode 100644 index 00000000000..d1c458b2e20 --- /dev/null +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java @@ -0,0 +1,165 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.logs; + +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.internal.GuardedBy; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.ExtendedAttributesMap; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +class ExtendedSdkReadWriteLogRecord extends SdkReadWriteLogRecord + implements ExtendedReadWriteLogRecord { + + @Nullable private final String eventName; + + @GuardedBy("lock") + @Nullable + private ExtendedAttributesMap extendedAttributes; + + @SuppressWarnings("unused") + private ExtendedSdkReadWriteLogRecord( + LogLimits logLimits, + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo, + @Nullable String eventName, + long timestampEpochNanos, + long observedTimestampEpochNanos, + SpanContext spanContext, + Severity severity, + @Nullable String severityText, + @Nullable Value body, + @Nullable ExtendedAttributesMap extendedAttributes) { + super( + logLimits, + resource, + instrumentationScopeInfo, + timestampEpochNanos, + observedTimestampEpochNanos, + spanContext, + severity, + severityText, + body, + null); + this.eventName = eventName; + this.extendedAttributes = extendedAttributes; + } + + /** Create the extended log record with the given configuration. */ + static ExtendedSdkReadWriteLogRecord create( + LogLimits logLimits, + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo, + @Nullable String eventName, + long timestampEpochNanos, + long observedTimestampEpochNanos, + SpanContext spanContext, + Severity severity, + @Nullable String severityText, + @Nullable Value body, + @Nullable ExtendedAttributesMap extendedAttributes) { + return new ExtendedSdkReadWriteLogRecord( + logLimits, + resource, + instrumentationScopeInfo, + eventName, + timestampEpochNanos, + observedTimestampEpochNanos, + spanContext, + severity, + severityText, + body, + extendedAttributes); + } + + @Override + public ExtendedSdkReadWriteLogRecord setAttribute(AttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + return setAttribute(ExtendedAttributeKey.fromAttributeKey(key), value); + } + + @Override + public ExtendedSdkReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + synchronized (lock) { + if (extendedAttributes == null) { + extendedAttributes = + ExtendedAttributesMap.create( + logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); + } + extendedAttributes.put(key, value); + } + return this; + } + + private ExtendedAttributes getImmutableExtendedAttributes() { + synchronized (lock) { + if (extendedAttributes == null) { + return ExtendedAttributes.empty(); + } + return extendedAttributes.immutableCopy(); + } + } + + @Override + public ExtendedLogRecordData toLogRecordData() { + synchronized (lock) { + return ExtendedSdkLogRecordData.create( + resource, + instrumentationScopeInfo, + eventName, + timestampEpochNanos, + observedTimestampEpochNanos, + spanContext, + severity, + severityText, + body, + getImmutableExtendedAttributes(), + extendedAttributes == null ? 0 : extendedAttributes.getTotalAddedValues()); + } + } + + @Override + public Attributes getAttributes() { + return getExtendedAttributes().asAttributes(); + } + + @Nullable + @Override + public T getAttribute(AttributeKey key) { + return getAttribute(ExtendedAttributeKey.fromAttributeKey(key)); + } + + @Nullable + @Override + public T getAttribute(ExtendedAttributeKey key) { + synchronized (lock) { + if (extendedAttributes == null || extendedAttributes.isEmpty()) { + return null; + } + return extendedAttributes.get(key); + } + } + + @Override + public ExtendedAttributes getExtendedAttributes() { + return getImmutableExtendedAttributes(); + } +} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java index 2a6684d1f11..8b69483d2a8 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java @@ -7,8 +7,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.ExtendedAttributeKey; -import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; @@ -31,11 +29,6 @@ public interface ReadWriteLogRecord { */ ReadWriteLogRecord setAttribute(AttributeKey key, T value); - /** TODO. */ - default ReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value) { - return this; - } - // TODO: add additional setters /** @@ -56,18 +49,6 @@ default ReadWriteLogRecord setAllAttributes(Attributes attributes) { return this; } - /** TODO. */ - @SuppressWarnings("unchecked") - default ReadWriteLogRecord setAllAttributes(ExtendedAttributes extendedAttributes) { - if (extendedAttributes == null || extendedAttributes.isEmpty()) { - return this; - } - extendedAttributes.forEach( - (attributeKey, value) -> - this.setAttribute((ExtendedAttributeKey) attributeKey, value)); - return this; - } - /** Return an immutable {@link LogRecordData} instance representing this log record. */ LogRecordData toLogRecordData(); @@ -81,13 +62,7 @@ default ReadWriteLogRecord setAllAttributes(ExtendedAttributes extendedAttribute */ @Nullable default T getAttribute(AttributeKey key) { - return toLogRecordData().getExtendedAttributes().get(key); - } - - /** TODO. */ - @Nullable - default T getAttribute(ExtendedAttributeKey key) { - return toLogRecordData().getExtendedAttributes().get(key); + return toLogRecordData().getAttributes().get(key); } /** @@ -163,9 +138,4 @@ default Value getBodyValue() { default Attributes getAttributes() { return toLogRecordData().getAttributes(); } - - /** TODO. */ - default ExtendedAttributes getExtendedAttributes() { - return toLogRecordData().getExtendedAttributes(); - } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java index a9141e02741..1a108b366c9 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java @@ -6,15 +6,13 @@ package io.opentelemetry.sdk.logs; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.ExtendedAttributeKey; -import io.opentelemetry.api.common.ExtendedAttributes; -import io.opentelemetry.api.common.ExtendedAttributesBuilder; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.AttributesMap; import java.time.Instant; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -22,33 +20,25 @@ /** SDK implementation of {@link LogRecordBuilder}. */ class SdkLogRecordBuilder implements LogRecordBuilder { - private final LoggerSharedState loggerSharedState; - // TODO: restore - // private final LogLimits logLimits; - - private final InstrumentationScopeInfo instrumentationScopeInfo; - @Nullable private String eventName; - private long timestampEpochNanos; - private long observedTimestampEpochNanos; - @Nullable private Context context; - private Severity severity = Severity.UNDEFINED_SEVERITY_NUMBER; - @Nullable private String severityText; - @Nullable private Value body; - // TODO: apply log limits - @Nullable private ExtendedAttributesBuilder attributesBuilder; + protected final LoggerSharedState loggerSharedState; + protected final LogLimits logLimits; + + protected final InstrumentationScopeInfo instrumentationScopeInfo; + protected long timestampEpochNanos; + protected long observedTimestampEpochNanos; + @Nullable protected Context context; + protected Severity severity = Severity.UNDEFINED_SEVERITY_NUMBER; + @Nullable protected String severityText; + @Nullable protected Value body; + @Nullable private AttributesMap attributes; SdkLogRecordBuilder( LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) { this.loggerSharedState = loggerSharedState; + this.logLimits = loggerSharedState.getLogLimits(); this.instrumentationScopeInfo = instrumentationScopeInfo; } - // accessible via ExtendedSdkLogRecordBuilder - SdkLogRecordBuilder setEventName(String eventName) { - this.eventName = eventName; - return this; - } - @Override public SdkLogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { this.timestampEpochNanos = unit.toNanos(timestamp); @@ -109,18 +99,12 @@ public SdkLogRecordBuilder setAttribute(AttributeKey key, T value) { if (key == null || key.getKey().isEmpty() || value == null) { return this; } - return setAttribute(key.asExtendedAttributeKey(), value); - } - - @Override - public SdkLogRecordBuilder setAttribute(ExtendedAttributeKey key, T value) { - if (key == null || key.getKey().isEmpty() || value == null) { - return this; - } - if (attributesBuilder == null) { - attributesBuilder = ExtendedAttributes.builder(); + if (this.attributes == null) { + this.attributes = + AttributesMap.create( + logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); } - attributesBuilder.put(key, value); + this.attributes.put(key, value); return this; } @@ -142,13 +126,12 @@ public void emit() { loggerSharedState.getLogLimits(), loggerSharedState.getResource(), instrumentationScopeInfo, - eventName, timestampEpochNanos, observedTimestampEpochNanos, Span.fromContext(context).getSpanContext(), severity, severityText, body, - attributesBuilder)); + attributes)); } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java index 1ffc16a0904..1927a0ec572 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordData.java @@ -7,12 +7,11 @@ import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; +import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -20,21 +19,20 @@ @AutoValue @AutoValue.CopyAnnotations @Immutable -abstract class SdkLogRecordData implements ExtendedLogRecordData { +abstract class SdkLogRecordData implements LogRecordData { SdkLogRecordData() {} static SdkLogRecordData create( Resource resource, InstrumentationScopeInfo instrumentationScopeInfo, - @Nullable String eventName, long epochNanos, long observedEpochNanos, SpanContext spanContext, Severity severity, @Nullable String severityText, @Nullable Value body, - ExtendedAttributes attributes, + Attributes attributes, int totalAttributeCount) { return new AutoValue_SdkLogRecordData( resource, @@ -44,28 +42,15 @@ static SdkLogRecordData create( spanContext, severity, severityText, - 0, // TODO: fix - body, - eventName, - attributes); + attributes, + totalAttributeCount, + body); } @Override @Nullable public abstract Value getBodyValue(); - @Override - @Nullable - public abstract String getEventName(); - - @Override - public abstract ExtendedAttributes getExtendedAttributes(); - - @Override - public Attributes getAttributes() { - return getExtendedAttributes().asAttributes(); - } - @Override @SuppressWarnings("deprecation") // Implementation of deprecated method public io.opentelemetry.sdk.logs.data.Body getBody() { diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java index c8bf5442b3b..821823d8c76 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java @@ -7,14 +7,12 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.ExtendedAttributeKey; -import io.opentelemetry.api.common.ExtendedAttributes; -import io.opentelemetry.api.common.ExtendedAttributesBuilder; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; @@ -23,46 +21,42 @@ @ThreadSafe class SdkReadWriteLogRecord implements ReadWriteLogRecord { - // TODO: restore - // private final LogLimits logLimits; - private final Resource resource; - private final InstrumentationScopeInfo instrumentationScopeInfo; - @Nullable private final String eventName; - private final long timestampEpochNanos; - private final long observedTimestampEpochNanos; - private final SpanContext spanContext; - private final Severity severity; - @Nullable private final String severityText; - @Nullable private final Value body; - private final Object lock = new Object(); + protected final LogLimits logLimits; + protected final Resource resource; + protected final InstrumentationScopeInfo instrumentationScopeInfo; + protected final long timestampEpochNanos; + protected final long observedTimestampEpochNanos; + protected final SpanContext spanContext; + protected final Severity severity; + @Nullable protected final String severityText; + @Nullable protected final Value body; + protected final Object lock = new Object(); @GuardedBy("lock") @Nullable - private ExtendedAttributesBuilder attributesBuilder; + private AttributesMap attributes; - @SuppressWarnings("unused") - private SdkReadWriteLogRecord( + protected SdkReadWriteLogRecord( LogLimits logLimits, Resource resource, InstrumentationScopeInfo instrumentationScopeInfo, - @Nullable String eventName, long timestampEpochNanos, long observedTimestampEpochNanos, SpanContext spanContext, Severity severity, @Nullable String severityText, @Nullable Value body, - @Nullable ExtendedAttributesBuilder attributesBuilder) { + @Nullable AttributesMap attributes) { + this.logLimits = logLimits; this.resource = resource; this.instrumentationScopeInfo = instrumentationScopeInfo; - this.eventName = eventName; this.timestampEpochNanos = timestampEpochNanos; this.observedTimestampEpochNanos = observedTimestampEpochNanos; this.spanContext = spanContext; this.severity = severity; this.severityText = severityText; this.body = body; - this.attributesBuilder = attributesBuilder; + this.attributes = attributes; } /** Create the log record with the given configuration. */ @@ -70,75 +64,65 @@ static SdkReadWriteLogRecord create( LogLimits logLimits, Resource resource, InstrumentationScopeInfo instrumentationScopeInfo, - @Nullable String eventName, long timestampEpochNanos, long observedTimestampEpochNanos, SpanContext spanContext, Severity severity, @Nullable String severityText, @Nullable Value body, - @Nullable ExtendedAttributesBuilder attributesBuilder) { + @Nullable AttributesMap attributes) { return new SdkReadWriteLogRecord( logLimits, resource, instrumentationScopeInfo, - eventName, timestampEpochNanos, observedTimestampEpochNanos, spanContext, severity, severityText, body, - attributesBuilder); + attributes); } @Override public ReadWriteLogRecord setAttribute(AttributeKey key, T value) { - if (key == null || key.getKey().isEmpty() || value == null) { - return this; - } - return setAttribute(key.asExtendedAttributeKey(), value); - } - - @Override - public ReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value) { if (key == null || key.getKey().isEmpty() || value == null) { return this; } synchronized (lock) { - if (attributesBuilder == null) { - attributesBuilder = ExtendedAttributes.builder(); + if (attributes == null) { + attributes = + AttributesMap.create( + logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); } - attributesBuilder.put(key, value); + attributes.put(key, value); } return this; } - private ExtendedAttributes getImmutableAttributes() { + private Attributes getImmutableAttributes() { synchronized (lock) { - if (attributesBuilder == null) { - return ExtendedAttributes.empty(); + if (attributes == null || attributes.isEmpty()) { + return Attributes.empty(); } - return attributesBuilder.build(); + return attributes.immutableCopy(); } } @Override public LogRecordData toLogRecordData() { synchronized (lock) { - ExtendedAttributes attributes = getImmutableAttributes(); return SdkLogRecordData.create( resource, instrumentationScopeInfo, - eventName, timestampEpochNanos, observedTimestampEpochNanos, spanContext, severity, severityText, body, - attributes, - attributes.size()); + getImmutableAttributes(), + attributes == null ? 0 : attributes.getTotalAddedValues()); } } @@ -181,18 +165,17 @@ public Value getBodyValue() { @Override public Attributes getAttributes() { - return getImmutableAttributes().asAttributes(); + return getImmutableAttributes(); } @Nullable @Override public T getAttribute(AttributeKey key) { - return getImmutableAttributes().get(key); - } - - @Nullable - @Override - public T getAttribute(ExtendedAttributeKey key) { - return getImmutableAttributes().get(key); + synchronized (lock) { + if (attributes == null || attributes.isEmpty()) { + return null; + } + return attributes.get(key); + } } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java index 2a39d4a0e12..f21b175f52f 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/LogRecordData.java @@ -6,7 +6,6 @@ package io.opentelemetry.sdk.logs.data; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.ExtendedAttributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.common.ValueType; import io.opentelemetry.api.logs.Severity; @@ -75,12 +74,6 @@ default Value getBodyValue() { /** Returns the attributes for this log, or {@link Attributes#empty()} if unset. */ Attributes getAttributes(); - /** TODO. */ - default ExtendedAttributes getExtendedAttributes() { - // TODO: - return ExtendedAttributes.builder().build(); - } - /** * Returns the total number of attributes that were recorded on this log. * diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java index 61e48eb7821..726643d0148 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java @@ -5,6 +5,8 @@ package io.opentelemetry.sdk.logs.data.internal; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.sdk.logs.data.LogRecordData; import javax.annotation.Nullable; @@ -17,4 +19,13 @@ public interface ExtendedLogRecordData extends LogRecordData { @Nullable String getEventName(); + + /** TODO. */ + ExtendedAttributes getExtendedAttributes(); + + /** TODO. */ + @Override + default Attributes getAttributes() { + return getExtendedAttributes().asAttributes(); + } } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java index 4fc798d581d..7a444817d40 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/ReadWriteLogRecordTest.java @@ -9,14 +9,12 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.ExtendedAttributes; -import io.opentelemetry.api.common.ExtendedAttributesBuilder; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.internal.AttributesMap; import io.opentelemetry.sdk.resources.Resource; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class ReadWriteLogRecordTest { @@ -35,16 +33,14 @@ void addAllAttributes() { } @Test - @Disabled void addAllHandlesNull() { SdkReadWriteLogRecord logRecord = buildLogRecord(); Attributes originalAttributes = logRecord.getAttributes(); - ReadWriteLogRecord result = logRecord.setAllAttributes((Attributes) null); + ReadWriteLogRecord result = logRecord.setAllAttributes(null); assertThat(result.getAttributes()).isEqualTo(originalAttributes); } @Test - @Disabled void allHandlesEmpty() { SdkReadWriteLogRecord logRecord = buildLogRecord(); Attributes originalAttributes = logRecord.getAttributes(); @@ -54,7 +50,7 @@ void allHandlesEmpty() { SdkReadWriteLogRecord buildLogRecord() { Value body = Value.of("bod"); - ExtendedAttributesBuilder initialAttributes = ExtendedAttributes.builder(); + AttributesMap initialAttributes = AttributesMap.create(100, 200); initialAttributes.put(stringKey("foo"), "aaiosjfjioasdiojfjioasojifja"); initialAttributes.put(stringKey("untouched"), "yes"); LogLimits limits = LogLimits.getDefault(); @@ -66,7 +62,6 @@ SdkReadWriteLogRecord buildLogRecord() { limits, resource, scope, - "event name", 0L, 0L, spanContext, diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java index 40d6288de5c..4209c50bfaa 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java @@ -60,7 +60,6 @@ void emit_AllFields() { Instant timestamp = Instant.now(); Instant observedTimestamp = Instant.now().plusNanos(100); - String eventName = "event name"; String bodyStr = "body"; String sevText = "sevText"; Severity severity = Severity.DEBUG3; @@ -71,7 +70,6 @@ void emit_AllFields() { TraceFlags.getSampled(), TraceState.getDefault()); - builder.setEventName(eventName); builder.setBody(bodyStr); builder.setTimestamp(123, TimeUnit.SECONDS); builder.setTimestamp(timestamp); diff --git a/sdk/testing/build.gradle.kts b/sdk/testing/build.gradle.kts index e6a095179d5..44bbcad90d8 100644 --- a/sdk/testing/build.gradle.kts +++ b/sdk/testing/build.gradle.kts @@ -9,6 +9,7 @@ otelJava.moduleName.set("io.opentelemetry.sdk.testing") dependencies { api(project(":api:all")) api(project(":sdk:all")) + compileOnly(project(":api:incubator")) compileOnly("org.assertj:assertj-core") compileOnly("junit:junit") @@ -21,3 +22,13 @@ dependencies { testImplementation("junit:junit") testImplementation("org.junit.vintage:junit-vintage-engine") } + +testing { + suites { + register("testIncubating") { + dependencies { + implementation(project(":api:incubator")) + } + } + } +} diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/logs/internal/TestExtendedLogRecordData.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/logs/internal/TestExtendedLogRecordData.java index 750344bf748..10c69da9cdb 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/logs/internal/TestExtendedLogRecordData.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/logs/internal/TestExtendedLogRecordData.java @@ -8,6 +8,7 @@ import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -183,9 +184,14 @@ public Builder setBody(io.opentelemetry.sdk.logs.data.Body body) { public abstract Builder setBodyValue(@Nullable Value body); /** Set the attributes. */ - public abstract Builder setAttributes(Attributes attributes); + public Builder setAttributes(Attributes attributes) { + return setExtendedAttributes(ExtendedAttributes.builder().putAll(attributes).build()); + } /** Set the total attribute count. */ public abstract Builder setTotalAttributeCount(int totalAttributeCount); + + /** Set extended attributes. * */ + public abstract Builder setExtendedAttributes(ExtendedAttributes extendedAttributes); } } From b36e1eff37e0a849a28ed66685e3feb70fc2ff4f Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 19 Feb 2025 17:47:36 -0600 Subject: [PATCH 3/8] progress --- api/all/build.gradle.kts | 2 - .../api/logs/DefaultLoggerTest.java | 2 - .../api/trace/DefaultTracerTest.java | 2 - api/incubator/README.md | 1 + .../common/ExtendedAttributeKey.java | 35 ++- .../common/ExtendedAttributeType.java | 12 +- .../incubator/common/ExtendedAttributes.java | 36 +++ .../common/ExtendedAttributesBuilder.java | 76 +++-- .../InternalExtendedAttributeKeyImpl.java | 15 +- .../logs/ExtendedLogRecordBuilder.java | 51 ++- .../common/ExtendedAttributesTest.java | 207 ------------- .../logs/ExtendedLogsBridgeApiUsageTest.java | 155 ++++++++++ .../opentelemetry-sdk-logs.txt | 13 +- .../internal/marshal/CodedOutputStream.java | 2 +- .../internal/marshal/JsonSerializer.java | 8 +- .../internal/marshal/ProtoSerializer.java | 8 +- .../exporter/internal/marshal/Serializer.java | 8 +- exporters/logging-otlp/build.gradle.kts | 1 - .../logging/otlp/TestDataExporter.java | 4 +- .../test/resources/expected-logs-wrapper.json | 1 - .../src/test/resources/expected-logs.json | 1 - exporters/otlp/common/build.gradle.kts | 17 +- ...edAttributeKeyValueStatelessMarshaler.java | 248 +++++++++++++++ .../internal/otlp/IncubatingUtil.java | 125 ++++++++ .../otlp/KeyValueListAnyValueMarshaler.java | 6 +- .../internal/otlp/KeyValueMarshaler.java | 2 +- .../internal/otlp/logs/LogMarshaler.java | 26 +- .../otlp/logs/LogStatelessMarshaler.java | 63 +++- .../otlp/logs/LogsRequestMarshalerTest.java | 12 +- .../LowAllocationLogRequestMarshalerTest.java | 6 +- .../LogsRequestMarshalerIncubatingTest.java | 292 ++++++++++++++++++ .../sdk/logs/ExtendedReadWriteLogRecord.java | 41 --- .../sdk/logs/ExtendedSdkLogRecordData.java | 6 - .../logs/ExtendedSdkReadWriteLogRecord.java | 2 + .../sdk/logs/SdkReadWriteLogRecord.java | 2 +- .../data/internal/ExtendedLogRecordData.java | 9 +- .../internal/ExtendedReadWriteLogRecord.java | 63 ++++ .../sdk/logs/SdkLogRecordBuilderTest.java | 2 +- .../sdk/logs/SdkLoggerProviderTest.java | 2 +- .../opentelemetry/sdk/logs/SdkLoggerTest.java | 3 - 40 files changed, 1177 insertions(+), 390 deletions(-) delete mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java create mode 100644 exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java create mode 100644 exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java create mode 100644 exporters/otlp/common/src/testIncubating/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerIncubatingTest.java delete mode 100644 sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java create mode 100644 sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/ExtendedReadWriteLogRecord.java diff --git a/api/all/build.gradle.kts b/api/all/build.gradle.kts index acab08cdeec..ad6896387d8 100644 --- a/api/all/build.gradle.kts +++ b/api/all/build.gradle.kts @@ -19,8 +19,6 @@ dependencies { testImplementation("edu.berkeley.cs.jqf:jqf-fuzz") testImplementation("com.google.guava:guava-testlib") - testImplementation(project(":sdk:all")) - testImplementation(project(":sdk:testing")) } tasks.test { diff --git a/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java b/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java index 5157702473c..10b43897a3e 100644 --- a/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/logs/DefaultLoggerTest.java @@ -6,9 +6,7 @@ package io.opentelemetry.api.logs; import io.opentelemetry.api.testing.internal.AbstractDefaultLoggerTest; -import org.junit.jupiter.api.Disabled; -@Disabled class DefaultLoggerTest extends AbstractDefaultLoggerTest { @Override diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java index 822e552f516..b6736fa3843 100644 --- a/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/trace/DefaultTracerTest.java @@ -6,9 +6,7 @@ package io.opentelemetry.api.trace; import io.opentelemetry.api.testing.internal.AbstractDefaultTracerTest; -import org.junit.jupiter.api.Disabled; -@Disabled class DefaultTracerTest extends AbstractDefaultTracerTest { @Override diff --git a/api/incubator/README.md b/api/incubator/README.md index e0b0ec7a080..04a271255be 100644 --- a/api/incubator/README.md +++ b/api/incubator/README.md @@ -7,6 +7,7 @@ Experimental APIs, including Event API, extended Log Bridge APIs, extended Metri Features: * Check if logger is enabled before emitting logs to avoid unnecessary computation +* Add extended attributes to log records to encode complex data structures See [ExtendedLogsBridgeApiUsageTest](./src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java). 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 14306de3ba1..d1c62648a0d 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 @@ -11,7 +11,25 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -/** TODO. */ +/** + * This interface provides a handle for setting the values of {@link ExtendedAttributes}. The type + * of value that can be set with an implementation of this key is denoted by the type parameter. + * + *

Implementations MUST be immutable, as these are used as the keys to Maps. + * + *

The allowed {@link #getType()}s is a superset of those allowed in {@link AttributeKey}. + * + *

Convenience methods are provided for translating to / from {@link AttributeKey}: + * + *

    + *
  • {@link #asAttributeKey()} converts from {@link ExtendedAttributeKey} to {@link + * AttributeKey} + *
  • {@link #fromAttributeKey(AttributeKey)} converts from {@link AttributeKey} to {@link + * ExtendedAttributeKey} + *
+ * + * @param The type of value that can be set with the key. + */ @Immutable public interface ExtendedAttributeKey { /** Returns the underlying String representation of the key. */ @@ -20,11 +38,17 @@ public interface ExtendedAttributeKey { /** Returns the type of attribute for this key. Useful for building switch statements. */ ExtendedAttributeType getType(); + /** + * Return the equivalent {@link AttributeKey}, or {@code null} if the {@link #getType()} has no + * equivalent {@link io.opentelemetry.api.common.AttributeType}. + */ @Nullable default AttributeKey asAttributeKey() { return InternalExtendedAttributeKeyImpl.toAttributeKey(this); } + /** Return an ExtendedAttributeKey equivalent to the {@code attributeKey}. */ + // TODO (jack-berg): remove once AttributeKey.asExtendedAttributeKey is available static ExtendedAttributeKey fromAttributeKey(AttributeKey attributeKey) { return InternalExtendedAttributeKeyImpl.fromAttributeKey(attributeKey); } @@ -70,12 +94,7 @@ static ExtendedAttributeKey> doubleArrayKey(String key) { } /** Returns a new ExtendedAttributeKey for Map valued attributes. */ - static ExtendedAttributeKey mapKey(String key) { - return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.MAP); - } - - /** Returns a new ExtendedAttributeKey for Map array valued attributes. */ - static ExtendedAttributeKey> mapArrayKey(String key) { - return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.MAP_ARRAY); + static ExtendedAttributeKey extendedAttributesKey(String key) { + return InternalExtendedAttributeKeyImpl.create(key, ExtendedAttributeType.EXTENDED_ATTRIBUTES); } } 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 05adcfa2639..8d2c67181b6 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 @@ -5,8 +5,14 @@ package io.opentelemetry.api.incubator.common; -/** TODO. */ +/** + * An enum that represents all the possible value types for an {@link ExtendedAttributeKey} and + * hence the types of values that are allowed for {@link ExtendedAttributes}. + * + *

This is a superset of {@link io.opentelemetry.api.common.AttributeType}, + */ public enum ExtendedAttributeType { + // Types copied AttributeType STRING, BOOLEAN, LONG, @@ -15,6 +21,6 @@ public enum ExtendedAttributeType { BOOLEAN_ARRAY, LONG_ARRAY, DOUBLE_ARRAY, - MAP, - MAP_ARRAY; + // Extended types unique to ExtendedAttributes + EXTENDED_ATTRIBUTES; } 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 bd8347e2035..0fc88a2ea49 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 @@ -7,11 +7,47 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; import java.util.Map; import java.util.function.BiConsumer; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +/** + * 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. + * + *

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 + * (e.g. {@link ExtendedLogRecordBuilder#setAttribute(ExtendedAttributeKey, Object)}). + * + *

The keys are {@link ExtendedAttributeKey}s and the values are Object instances that match the + * type of the provided key. + * + *

Null keys will be silently dropped. + * + *

Note: The behavior of null-valued attributes is undefined, and hence strongly discouraged. + * + *

Implementations of this interface *must* be immutable and have well-defined value-based + * equals/hashCode implementations. If an implementation does not strictly conform to these + * requirements, behavior of the OpenTelemetry APIs and default SDK cannot be guaranteed. + * + *

For this reason, it is strongly suggested that you use the implementation that is provided + * here via the factory methods and the {@link ExtendedAttributesBuilder}. + * + *

Convenience methods are provided for translating to / from {@link Attributes}: + * + *

    + *
  • {@link #asAttributes()} converts from {@link ExtendedAttributes} to {@link Attributes} + *
  • {@link ExtendedAttributesBuilder#putAll(Attributes)} converts from {@link Attributes} to + * {@link ExtendedAttributes} + *
  • {@link #get(AttributeKey)} supports reading values using standard {@link AttributeKey} + *
+ */ @Immutable public interface ExtendedAttributes { 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 83f9be53fd5..1e0de3b4c38 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 @@ -21,7 +21,7 @@ import java.util.List; import java.util.function.Predicate; -/** A builder of {@link Attributes} supporting an arbitrary number of key-value pairs. */ +/** A builder of {@link ExtendedAttributes} supporting an arbitrary number of key-value pairs. */ public interface ExtendedAttributesBuilder { /** Create the {@link ExtendedAttributes} from this. */ ExtendedAttributes build(); @@ -34,14 +34,14 @@ default ExtendedAttributesBuilder put(AttributeKey key, T value) { return put(ExtendedAttributeKey.fromAttributeKey(key), value); } - /** TODO. */ + /** Puts a {@link ExtendedAttributeKey} with associated value into this. */ ExtendedAttributesBuilder put(ExtendedAttributeKey key, T value); /** * Puts a String attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -52,8 +52,8 @@ default ExtendedAttributesBuilder put(String key, String value) { /** * Puts a long attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -64,8 +64,8 @@ default ExtendedAttributesBuilder put(String key, long value) { /** * Puts a double attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -76,8 +76,8 @@ default ExtendedAttributesBuilder put(String key, double value) { /** * Puts a boolean attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -85,16 +85,23 @@ default ExtendedAttributesBuilder put(String key, boolean value) { return put(booleanKey(key), value); } - /** TODO. */ + /** + * Puts a {@link ExtendedAttributes} attribute into this. + * + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ default ExtendedAttributesBuilder put(String key, ExtendedAttributes value) { - return put(ExtendedAttributeKey.mapKey(key), value); + return put(ExtendedAttributeKey.extendedAttributesKey(key), value); } /** * Puts a String array attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -121,8 +128,8 @@ default ExtendedAttributesBuilder put(AttributeKey> key, T... value) /** * Puts a Long array attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -136,8 +143,8 @@ default ExtendedAttributesBuilder put(String key, long... value) { /** * Puts a Double array attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -151,8 +158,8 @@ default ExtendedAttributesBuilder put(String key, double... value) { /** * Puts a Boolean array attribute into this. * - *

Note: It is strongly recommended to use {@link #put(AttributeKey, Object)}, and pre-allocate - * your keys, if possible. + *

Note: It is strongly recommended to use {@link #put(ExtendedAttributeKey, Object)}, and + * pre-allocate your keys, if possible. * * @return this Builder */ @@ -163,14 +170,6 @@ default ExtendedAttributesBuilder put(String key, boolean... value) { return put(booleanArrayKey(key), toList(value)); } - /** TODO. */ - default ExtendedAttributesBuilder put(String key, ExtendedAttributes... value) { - if (value == null) { - return this; - } - return put(ExtendedAttributeKey.mapArrayKey(key), Arrays.asList(value)); - } - /** * Puts all the provided attributes into this Builder. * @@ -185,7 +184,11 @@ default ExtendedAttributesBuilder putAll(Attributes attributes) { return this; } - /** TODO. */ + /** + * Puts all the provided attributes into this Builder. + * + * @return this Builder + */ @SuppressWarnings({"unchecked"}) default ExtendedAttributesBuilder putAll(ExtendedAttributes attributes) { if (attributes == null) { @@ -205,17 +208,26 @@ default ExtendedAttributesBuilder remove(AttributeKey key) { return remove(ExtendedAttributeKey.fromAttributeKey(key)); } - /** TODO. */ + /** + * Remove all attributes where {@link ExtendedAttributeKey#getKey()} and {@link + * ExtendedAttributeKey#getType()} match the {@code key}. + * + * @return this Builder + */ default ExtendedAttributesBuilder remove(ExtendedAttributeKey key) { if (key == null || key.getKey().isEmpty()) { return this; } - // TODO: return removeIf( entryKey -> key.getKey().equals(entryKey.getKey()) && key.getType().equals(entryKey.getType())); } - /** TODO. */ + /** + * Remove all attributes that satisfy the given predicate. Errors or runtime exceptions thrown by + * the predicate are relayed to the caller. + * + * @return this Builder + */ ExtendedAttributesBuilder removeIf(Predicate> filter); } 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 eb7e700e3d3..e4aa5534724 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 @@ -47,6 +47,10 @@ public static ExtendedAttributeKey create( return new InternalExtendedAttributeKeyImpl<>(type, key != null ? key : ""); } + /** + * Return an {@link ExtendedAttributeKey} equivalent to the {@code attributeKey}, caching entries + * in {@link #ATTRIBUTE_KEY_CACHE}. + */ @SuppressWarnings("unchecked") public static ExtendedAttributeKey fromAttributeKey(AttributeKey attributeKey) { return (ExtendedAttributeKey) @@ -120,7 +124,11 @@ private static int buildHashCode(ExtendedAttributeType type, String key) { return result; } - /** TODO. */ + /** + * Return the equivalent {@link AttributeKey} for the {@link ExtendedAttributeKey}, or {@code + * null} if the {@link #getType()} has no equivalent {@link + * io.opentelemetry.api.common.AttributeType}. + */ @Nullable public static AttributeKey toAttributeKey(ExtendedAttributeKey extendedAttributeKey) { switch (extendedAttributeKey.getType()) { @@ -145,15 +153,14 @@ public static AttributeKey toAttributeKey(ExtendedAttributeKey extende case DOUBLE_ARRAY: return InternalAttributeKeyImpl.create( extendedAttributeKey.getKey(), AttributeType.DOUBLE_ARRAY); - case MAP: - case MAP_ARRAY: + case EXTENDED_ATTRIBUTES: return null; } throw new IllegalArgumentException( "Unrecognized extendedAttributeKey type: " + extendedAttributeKey.getType()); } - /** TODO. */ + /** Return the equivalent {@link ExtendedAttributeKey} for the {@link AttributeKey}. */ public static ExtendedAttributeKey toExtendedAttributeKey(AttributeKey attributeKey) { switch (attributeKey.getType()) { case STRING: diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java index 72d6260d410..d1f0d049dee 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java @@ -61,14 +61,14 @@ default ExtendedLogRecordBuilder setBody(Value body) { } /** - * Sets the event name, which identifies the class / type of the Event. + * {@inheritDoc} * - *

This name should uniquely identify the event structure (both attributes and body). A log - * record with a non-empty event name is an Event. + *

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 + * log record, there is no difference between adding attributes using the standard or extended + * attribute APIs. */ - ExtendedLogRecordBuilder setEventName(String eventName); - - /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override default ExtendedLogRecordBuilder setAllAttributes(Attributes attributes) { @@ -80,7 +80,16 @@ default ExtendedLogRecordBuilder setAllAttributes(Attributes attributes) { return this; } - /** TODO. */ + /** + * Sets attributes. If the {@link LogRecordBuilder} previously contained a mapping for any of the + * keys, the old values are replaced by the specified values. + * + *

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 + * log record, there is no difference between adding attributes using the standard or extended + * attribute APIs. + */ @SuppressWarnings("unchecked") default ExtendedLogRecordBuilder setAllAttributes(ExtendedAttributes attributes) { if (attributes == null || attributes.isEmpty()) { @@ -91,10 +100,34 @@ default ExtendedLogRecordBuilder setAllAttributes(ExtendedAttributes attributes) return this; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * + *

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 + * log record, there is no difference between adding attributes using the standard or extended + * attribute APIs. + */ @Override ExtendedLogRecordBuilder setAttribute(AttributeKey key, T value); - /** TODO. */ + /** + * 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 + * log record, there is no difference between adding attributes using the standard or extended + * attribute APIs. + */ ExtendedLogRecordBuilder setAttribute(ExtendedAttributeKey key, T value); + + /** + * Sets the event name, which identifies the class / type of the Event. + * + *

This name should uniquely identify the event structure (both attributes and body). A log + * record with a non-empty event name is an Event. + */ + ExtendedLogRecordBuilder setEventName(String eventName); } 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 deleted file mode 100644 index c498192db9c..00000000000 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.common; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder; -import io.opentelemetry.api.logs.Logger; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; -import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; -import java.util.Arrays; -import java.util.List; -import java.util.function.BiConsumer; -import org.junit.jupiter.api.Test; - -class ExtendedAttributesTest { - - // Primitive keys - AttributeKey strKey = AttributeKey.stringKey("acme.string"); - AttributeKey longKey = AttributeKey.longKey("acme.long"); - AttributeKey booleanKey = AttributeKey.booleanKey("acme.boolean"); - AttributeKey doubleKey = AttributeKey.doubleKey("acme.double"); - - // Primitive array keys - AttributeKey> strArrKey = AttributeKey.stringArrayKey("acme.string_array"); - AttributeKey> longArrKey = AttributeKey.longArrayKey("acme.long_array"); - AttributeKey> booleanArrKey = AttributeKey.booleanArrayKey("acme.boolean_array"); - AttributeKey> doubleArrKey = AttributeKey.doubleArrayKey("acme.double_array"); - - // Extended keys - ExtendedAttributeKey mapKey = ExtendedAttributeKey.mapKey("acme.map"); - ExtendedAttributeKey> mapArrayKey = - ExtendedAttributeKey.mapArrayKey("acme.map_array"); - - @Test - @SuppressWarnings("SystemOut") - void usage() { - // Initialize from builder. Varargs style initialization (ExtendedAttributes.of(...) not - // supported. - ExtendedAttributes extendedAttributes = - ExtendedAttributes.builder() - .put(strKey, "value") - .put(longKey, 1L) - .put(booleanKey, true) - .put(doubleKey, 1.1) - .put(strArrKey, Arrays.asList("value1", "value2")) - .put(longArrKey, Arrays.asList(1L, 2L)) - .put(booleanArrKey, Arrays.asList(true, false)) - .put(doubleArrKey, Arrays.asList(1.1, 2.2)) - .put( - mapKey, - ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) - .put( - mapArrayKey, - Arrays.asList( - ExtendedAttributes.builder() - .put("childStr", "value") - .put("childLong", 1L) - .build(), - ExtendedAttributes.builder() - .put("childStr", "value") - .put("childLong", 1L) - .build())) - .build(); - - // Retrieval - assertThat(extendedAttributes.get(strKey)).isEqualTo("value"); - assertThat(extendedAttributes.get(longKey)).isEqualTo(1); - assertThat(extendedAttributes.get(booleanKey)).isEqualTo(true); - assertThat(extendedAttributes.get(doubleKey)).isEqualTo(1.1); - assertThat(extendedAttributes.get(strArrKey)).isEqualTo(Arrays.asList("value1", "value2")); - 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(extendedAttributes.get(mapKey)) - .isEqualTo( - ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()); - assertThat(extendedAttributes.get(mapArrayKey)) - .isEqualTo( - Arrays.asList( - ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build(), - ExtendedAttributes.builder() - .put("childStr", "value") - .put("childLong", 1L) - .build())); - - // Iteration - // Output: - // acme.boolean(BOOLEAN): true - // acme.boolean_array(BOOLEAN_ARRAY): [true, false] - // acme.double(DOUBLE): 1.1 - // acme.double_array(DOUBLE_ARRAY): [1.1, 2.2] - // acme.long(LONG): 1 - // acme.long_array(LONG_ARRAY): [1, 2] - // acme.map(MAP): {childLong=1, childStr="value"} - // acme.map_array(MAP_ARRAY): [{childLong=1, childStr="value"}, {childLong=1, childStr="value"}] - // acme.string(STRING): value - // acme.string_array(STRING_ARRAY): [value1, value2] - extendedAttributes.forEach( - new BiConsumer, Object>() { - @Override - public void accept(ExtendedAttributeKey extendedAttributeKey, Object object) { - System.out.format( - "%s(%s): %s\n", - extendedAttributeKey.getKey(), extendedAttributeKey.getType(), object); - } - }); - } - - @Test - void logRecordBuilder() { - InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); - SdkLoggerProvider loggerProvider = - SdkLoggerProvider.builder() - .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) - .build(); - - Logger logger = loggerProvider.get("logger"); - - // Can set either standard or extended attributes on - ((ExtendedLogRecordBuilder) logger.logRecordBuilder()) - .setBody("message") - .setAttribute(strKey, "value") - .setAttribute(longKey, 1L) - .setAttribute(booleanKey, true) - .setAttribute(doubleKey, 1.1) - .setAttribute(strArrKey, Arrays.asList("value1", "value2")) - .setAttribute(longArrKey, Arrays.asList(1L, 2L)) - .setAttribute(booleanArrKey, Arrays.asList(true, false)) - .setAttribute(doubleArrKey, Arrays.asList(1.1, 2.2)) - .setAttribute( - mapKey, - ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) - .setAttribute( - mapArrayKey, - Arrays.asList( - ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build(), - ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build())) - .setAllAttributes(Attributes.builder().put("key1", "value").build()) - .setAllAttributes(ExtendedAttributes.builder().put("key2", "value").build()) - .emit(); - - assertThat(exporter.getFinishedLogRecordItems()) - .satisfiesExactly( - logRecordData -> { - assertThat(logRecordData).isInstanceOf(ExtendedLogRecordData.class); - ExtendedLogRecordData extendedLogRecordData = (ExtendedLogRecordData) logRecordData; - - // Optionally access standard attributes, which filters out any extended attribute - // types - assertThat(extendedLogRecordData.getAttributes()) - .isEqualTo( - Attributes.builder() - .put(strKey, "value") - .put(longKey, 1L) - .put(booleanKey, true) - .put(doubleKey, 1.1) - .put(strArrKey, Arrays.asList("value1", "value2")) - .put(longArrKey, Arrays.asList(1L, 2L)) - .put(booleanArrKey, Arrays.asList(true, false)) - .put(doubleArrKey, Arrays.asList(1.1, 2.2)) - .put("key1", "value") - .put("key2", "value") - .build()); - - // But preferably access and serialize full extended attributes - assertThat(extendedLogRecordData.getExtendedAttributes()) - .isEqualTo( - ExtendedAttributes.builder() - .put(strKey, "value") - .put(longKey, 1L) - .put(booleanKey, true) - .put(doubleKey, 1.1) - .put(strArrKey, Arrays.asList("value1", "value2")) - .put(longArrKey, Arrays.asList(1L, 2L)) - .put(booleanArrKey, Arrays.asList(true, false)) - .put(doubleArrKey, Arrays.asList(1.1, 2.2)) - .put( - mapKey, - ExtendedAttributes.builder() - .put("childStr", "value") - .put("childLong", 1L) - .build()) - .put( - mapArrayKey, - Arrays.asList( - ExtendedAttributes.builder() - .put("childStr", "value") - .put("childLong", 1L) - .build(), - ExtendedAttributes.builder() - .put("childStr", "value") - .put("childLong", 1L) - .build())) - .put("key1", "value") - .put("key2", "value") - .build()); - }); - } -} 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 2d9c494ee3e..dd0fae33624 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 @@ -9,14 +9,22 @@ import static io.opentelemetry.sdk.logs.internal.LoggerConfig.disabled; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.logs.Logger; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; +import java.util.Arrays; +import java.util.List; import java.util.Random; +import java.util.function.BiConsumer; import org.junit.jupiter.api.Test; /** Demonstrating usage of extended Logs Bridge API. */ @@ -76,4 +84,151 @@ void loggerEnabled() { private static String flipCoin() { return random.nextBoolean() ? "heads" : "tails"; } + + // Primitive keys + AttributeKey strKey = AttributeKey.stringKey("acme.string"); + AttributeKey longKey = AttributeKey.longKey("acme.long"); + AttributeKey booleanKey = AttributeKey.booleanKey("acme.boolean"); + AttributeKey doubleKey = AttributeKey.doubleKey("acme.double"); + + // Primitive array keys + AttributeKey> strArrKey = AttributeKey.stringArrayKey("acme.string_array"); + AttributeKey> longArrKey = AttributeKey.longArrayKey("acme.long_array"); + AttributeKey> booleanArrKey = AttributeKey.booleanArrayKey("acme.boolean_array"); + AttributeKey> doubleArrKey = AttributeKey.doubleArrayKey("acme.double_array"); + + // Extended keys + ExtendedAttributeKey mapKey = + ExtendedAttributeKey.extendedAttributesKey("acme.map"); + + @Test + @SuppressWarnings("SystemOut") + void extendedAttributesUsage() { + // Initialize from builder. Varargs style initialization (ExtendedAttributes.of(...) not + // supported. + ExtendedAttributes extendedAttributes = + ExtendedAttributes.builder() + .put(strKey, "value") + .put(longKey, 1L) + .put(booleanKey, true) + .put(doubleKey, 1.1) + .put(strArrKey, Arrays.asList("value1", "value2")) + .put(longArrKey, Arrays.asList(1L, 2L)) + .put(booleanArrKey, Arrays.asList(true, false)) + .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put( + mapKey, + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) + .build(); + + // Retrieval + assertThat(extendedAttributes.get(strKey)).isEqualTo("value"); + assertThat(extendedAttributes.get(longKey)).isEqualTo(1); + assertThat(extendedAttributes.get(booleanKey)).isEqualTo(true); + assertThat(extendedAttributes.get(doubleKey)).isEqualTo(1.1); + assertThat(extendedAttributes.get(strArrKey)).isEqualTo(Arrays.asList("value1", "value2")); + 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(extendedAttributes.get(mapKey)) + .isEqualTo( + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()); + + // Iteration + // Output: + // acme.boolean(BOOLEAN): true + // acme.boolean_array(BOOLEAN_ARRAY): [true, false] + // acme.double(DOUBLE): 1.1 + // 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.string(STRING): value + // acme.string_array(STRING_ARRAY): [value1, value2] + extendedAttributes.forEach( + new BiConsumer, Object>() { + @Override + public void accept(ExtendedAttributeKey extendedAttributeKey, Object object) { + System.out.format( + "%s(%s): %s\n", + extendedAttributeKey.getKey(), extendedAttributeKey.getType(), object); + } + }); + } + + @Test + @SuppressWarnings("deprecation") // testing deprecated code + void logRecordBuilder_ExtendedAttributes() { + InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter)) + .build(); + + Logger logger = loggerProvider.get("logger"); + + // Can set either standard or extended attributes on + ((ExtendedLogRecordBuilder) logger.logRecordBuilder()) + .setBody("message") + .setAttribute(strKey, "value") + .setAttribute(longKey, 1L) + .setAttribute(booleanKey, true) + .setAttribute(doubleKey, 1.1) + .setAttribute(strArrKey, Arrays.asList("value1", "value2")) + .setAttribute(longArrKey, Arrays.asList(1L, 2L)) + .setAttribute(booleanArrKey, Arrays.asList(true, false)) + .setAttribute(doubleArrKey, Arrays.asList(1.1, 2.2)) + .setAttribute( + mapKey, + ExtendedAttributes.builder().put("childStr", "value").put("childLong", 1L).build()) + .setAllAttributes(Attributes.builder().put("key1", "value").build()) + .setAllAttributes(ExtendedAttributes.builder().put("key2", "value").build()) + .emit(); + + assertThat(exporter.getFinishedLogRecordItems()) + .satisfiesExactly( + logRecordData -> { + assertThat(logRecordData).isInstanceOf(ExtendedLogRecordData.class); + ExtendedLogRecordData extendedLogRecordData = (ExtendedLogRecordData) logRecordData; + + // Optionally access standard attributes, which filters out any extended attribute + // types + assertThat(extendedLogRecordData.getAttributes()) + .isEqualTo( + Attributes.builder() + .put(strKey, "value") + .put(longKey, 1L) + .put(booleanKey, true) + .put(doubleKey, 1.1) + .put(strArrKey, Arrays.asList("value1", "value2")) + .put(longArrKey, Arrays.asList(1L, 2L)) + .put(booleanArrKey, Arrays.asList(true, false)) + .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put("key1", "value") + .put("key2", "value") + .build()); + + // But preferably access and serialize full extended attributes + assertThat(extendedLogRecordData.getExtendedAttributes()) + .isEqualTo( + ExtendedAttributes.builder() + .put(strKey, "value") + .put(longKey, 1L) + .put(booleanKey, true) + .put(doubleKey, 1.1) + .put(strArrKey, Arrays.asList("value1", "value2")) + .put(longArrKey, Arrays.asList(1L, 2L)) + .put(booleanArrKey, Arrays.asList(true, false)) + .put(doubleArrKey, Arrays.asList(1.1, 2.2)) + .put( + mapKey, + ExtendedAttributes.builder() + .put("childStr", "value") + .put("childLong", 1L) + .build()) + .put("key1", "value") + .put("key2", "value") + .build()); + }); + } } diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt index 4aaaf109629..1fc34295832 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt @@ -1,13 +1,2 @@ Comparing source compatibility of opentelemetry-sdk-logs-1.48.0-SNAPSHOT.jar against opentelemetry-sdk-logs-1.47.0.jar -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.logs.ExtendedReadWriteLogRecord (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: io.opentelemetry.sdk.logs.ReadWriteLogRecord - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object getAttribute(io.opentelemetry.api.incubator.common.ExtendedAttributeKey) - +++ NEW ANNOTATION: javax.annotation.Nullable - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.incubator.common.ExtendedAttributes getExtendedAttributes() - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ExtendedReadWriteLogRecord setAllAttributes(io.opentelemetry.api.incubator.common.ExtendedAttributes) - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.logs.ExtendedReadWriteLogRecord setAttribute(io.opentelemetry.api.incubator.common.ExtendedAttributeKey, java.lang.Object) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData toLogRecordData() +No changes. \ No newline at end of file diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/CodedOutputStream.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/CodedOutputStream.java index 68311be1845..c9f12923181 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/CodedOutputStream.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/CodedOutputStream.java @@ -214,7 +214,7 @@ static int computeInt32SizeNoTag(final int value) { } /** Compute the number of bytes that would be needed to encode a {@code uint32} field. */ - static int computeUInt32SizeNoTag(final int value) { + public static int computeUInt32SizeNoTag(final int value) { if ((value & (~0 << 7)) == 0) { return 1; } diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java index 6170e1925d2..62b391c8905 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java @@ -220,23 +220,23 @@ public void serializeRepeatedMessageWithContext( } @Override - protected void writeStartRepeated(ProtoFieldInfo field) throws IOException { + public void writeStartRepeated(ProtoFieldInfo field) throws IOException { generator.writeArrayFieldStart(field.getJsonName()); } @Override - protected void writeEndRepeated() throws IOException { + public void writeEndRepeated() throws IOException { generator.writeEndArray(); } @Override - protected void writeStartRepeatedElement(ProtoFieldInfo field, int protoMessageSize) + public void writeStartRepeatedElement(ProtoFieldInfo field, int protoMessageSize) throws IOException { generator.writeStartObject(); } @Override - protected void writeEndRepeatedElement() throws IOException { + public void writeEndRepeatedElement() throws IOException { generator.writeEndObject(); } diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java index 694cec8b2b9..4094f8b01ac 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/ProtoSerializer.java @@ -249,23 +249,23 @@ public void serializeRepeatedMessageWithContext( } @Override - protected void writeStartRepeated(ProtoFieldInfo field) { + public void writeStartRepeated(ProtoFieldInfo field) { // Do nothing } @Override - protected void writeEndRepeated() { + public void writeEndRepeated() { // Do nothing } @Override - protected void writeStartRepeatedElement(ProtoFieldInfo field, int protoMessageSize) + public void writeStartRepeatedElement(ProtoFieldInfo field, int protoMessageSize) throws IOException { writeStartMessage(field, protoMessageSize); } @Override - protected void writeEndRepeatedElement() { + public void writeEndRepeatedElement() { writeEndMessage(); } diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java index 100506fb3dc..e8e549544ca 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java @@ -674,17 +674,17 @@ public void accept(K key, V value) { } /** Writes start of repeated messages. */ - protected abstract void writeStartRepeated(ProtoFieldInfo field) throws IOException; + public abstract void writeStartRepeated(ProtoFieldInfo field) throws IOException; /** Writes end of repeated messages. */ - protected abstract void writeEndRepeated() throws IOException; + public abstract void writeEndRepeated() throws IOException; /** Writes start of a repeated message element. */ - protected abstract void writeStartRepeatedElement(ProtoFieldInfo field, int protoMessageSize) + public abstract void writeStartRepeatedElement(ProtoFieldInfo field, int protoMessageSize) throws IOException; /** Writes end of a repeated message element. */ - protected abstract void writeEndRepeatedElement() throws IOException; + public abstract void writeEndRepeatedElement() throws IOException; /** Writes the value for a message field that has been pre-serialized. */ public abstract void writeSerializedMessage(byte[] protoSerialized, String jsonSerialized) diff --git a/exporters/logging-otlp/build.gradle.kts b/exporters/logging-otlp/build.gradle.kts index 4e3be802e40..a65f52f8522 100644 --- a/exporters/logging-otlp/build.gradle.kts +++ b/exporters/logging-otlp/build.gradle.kts @@ -19,7 +19,6 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-core") testImplementation(project(":sdk:testing")) - testImplementation(project(":api:incubator")) testImplementation("com.google.guava:guava") testImplementation("org.skyscreamer:jsonassert") diff --git a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java index 63040a9beb1..54032fca3e0 100644 --- a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java +++ b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java @@ -28,7 +28,6 @@ import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.logs.TestLogRecordData; -import io.opentelemetry.sdk.testing.logs.internal.TestExtendedLogRecordData; import io.opentelemetry.sdk.testing.trace.TestSpanData; import io.opentelemetry.sdk.trace.data.EventData; import io.opentelemetry.sdk.trace.data.SpanData; @@ -48,14 +47,13 @@ abstract class TestDataExporter { Resource.create(Attributes.builder().put("key", "value").build()); private static final LogRecordData LOG1 = - TestExtendedLogRecordData.builder() + TestLogRecordData.builder() .setResource(RESOURCE) .setInstrumentationScopeInfo( InstrumentationScopeInfo.builder("instrumentation") .setVersion("1") .setAttributes(Attributes.builder().put("key", "value").build()) .build()) - .setEventName("event name") .setBody("body1") .setSeverity(Severity.INFO) .setSeverityText("INFO") diff --git a/exporters/logging-otlp/src/test/resources/expected-logs-wrapper.json b/exporters/logging-otlp/src/test/resources/expected-logs-wrapper.json index c298d824182..887fdb13d4b 100644 --- a/exporters/logging-otlp/src/test/resources/expected-logs-wrapper.json +++ b/exporters/logging-otlp/src/test/resources/expected-logs-wrapper.json @@ -27,7 +27,6 @@ }, "logRecords": [ { - "eventName": "event name", "timeUnixNano": "100", "observedTimeUnixNano": "200", "severityNumber": 9, diff --git a/exporters/logging-otlp/src/test/resources/expected-logs.json b/exporters/logging-otlp/src/test/resources/expected-logs.json index 6eae7f28ad3..fe84d67c4ef 100644 --- a/exporters/logging-otlp/src/test/resources/expected-logs.json +++ b/exporters/logging-otlp/src/test/resources/expected-logs.json @@ -25,7 +25,6 @@ }, "logRecords": [ { - "eventName": "event name", "timeUnixNano": "100", "observedTimeUnixNano": "200", "severityNumber": 9, diff --git a/exporters/otlp/common/build.gradle.kts b/exporters/otlp/common/build.gradle.kts index 627e25ef0d6..6167179629a 100644 --- a/exporters/otlp/common/build.gradle.kts +++ b/exporters/otlp/common/build.gradle.kts @@ -22,12 +22,12 @@ dependencies { compileOnly(project(":sdk:metrics")) compileOnly(project(":sdk:trace")) compileOnly(project(":sdk:logs")) + compileOnly(project(":api:incubator")) testImplementation(project(":sdk:metrics")) testImplementation(project(":sdk:trace")) testImplementation(project(":sdk:logs")) testImplementation(project(":sdk:testing")) - testImplementation(project(":api:incubator")) testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.google.protobuf:protobuf-java-util") @@ -41,6 +41,21 @@ dependencies { jmhImplementation("io.grpc:grpc-netty") } +testing { + suites { + register("testIncubating") { + dependencies { + implementation(project(":api:incubator")) + implementation(project(":sdk:testing")) + + implementation("com.fasterxml.jackson.core:jackson-databind") + implementation("com.google.protobuf:protobuf-java-util") + implementation("io.opentelemetry.proto:opentelemetry-proto") + } + } + } +} + wire { root( "opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest", 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 new file mode 100644 index 00000000000..de31139abcb --- /dev/null +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ExtendedAttributeKeyValueStatelessMarshaler.java @@ -0,0 +1,248 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal.otlp; + +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributeType; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.internal.InternalExtendedAttributeKeyImpl; +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.ProtoFieldInfo; +import io.opentelemetry.exporter.internal.marshal.Serializer; +import io.opentelemetry.exporter.internal.marshal.StatelessMarshaler; +import io.opentelemetry.exporter.internal.marshal.StatelessMarshaler2; +import io.opentelemetry.exporter.internal.marshal.StatelessMarshalerUtil; +import io.opentelemetry.proto.common.v1.internal.AnyValue; +import io.opentelemetry.proto.common.v1.internal.KeyValue; +import io.opentelemetry.proto.common.v1.internal.KeyValueList; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Objects; + +/** + * A Marshaler of {@link ExtendedAttributes} key value pairs. See {@link KeyValueMarshaler}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ExtendedAttributeKeyValueStatelessMarshaler + implements StatelessMarshaler2, Object> { + private static final ExtendedAttributeKeyValueStatelessMarshaler INSTANCE = + new ExtendedAttributeKeyValueStatelessMarshaler(); + private static final byte[] EMPTY_BYTES = new byte[0]; + + private ExtendedAttributeKeyValueStatelessMarshaler() {} + + /** + * Serializes the {@code attributes}. This method reads elements from context, use together with + * {@link ExtendedAttributeKeyValueStatelessMarshaler#sizeExtendedAttributes(ProtoFieldInfo, + * ExtendedAttributes, MarshalerContext)}. + */ + public static void serializeExtendedAttributes( + Serializer output, + ProtoFieldInfo field, + ExtendedAttributes attributes, + MarshalerContext context) + throws IOException { + output.writeStartRepeated(field); + + if (!attributes.isEmpty()) { + try { + attributes.forEach( + (extendedAttributeKey, value) -> { + try { + output.writeStartRepeatedElement(field, context.getSize()); + INSTANCE.writeTo(output, extendedAttributeKey, value, context); + output.writeEndRepeatedElement(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + output.writeEndRepeated(); + } + + /** + * Sizes the {@code attributes}. This method adds elements to context, use together with {@link + * ExtendedAttributeKeyValueStatelessMarshaler#serializeExtendedAttributes(Serializer, + * ProtoFieldInfo, ExtendedAttributes, MarshalerContext)}. + */ + public static int sizeExtendedAttributes( + ProtoFieldInfo field, ExtendedAttributes attributes, MarshalerContext context) { + if (attributes.isEmpty()) { + return 0; + } + + int[] size = new int[] {0}; + + attributes.forEach( + (extendedAttributeKey, value) -> { + int sizeIndex = context.addSize(); + int fieldSize = INSTANCE.getBinarySerializedSize(extendedAttributeKey, value, context); + context.setSize(sizeIndex, fieldSize); + size[0] += + field.getTagSize() + CodedOutputStream.computeUInt32SizeNoTag(fieldSize) + fieldSize; + }); + + return size[0]; + } + + @Override + public void writeTo( + Serializer output, + ExtendedAttributeKey attributeKey, + Object value, + MarshalerContext context) + throws IOException { + if (attributeKey.getKey().isEmpty()) { + output.serializeString(KeyValue.KEY, EMPTY_BYTES); + } else if (attributeKey instanceof InternalExtendedAttributeKeyImpl) { + byte[] keyUtf8 = ((InternalExtendedAttributeKeyImpl) attributeKey).getKeyUtf8(); + output.serializeString(KeyValue.KEY, keyUtf8); + } else { + output.serializeStringWithContext(KeyValue.KEY, attributeKey.getKey(), context); + } + output.serializeMessageWithContext( + KeyValue.VALUE, attributeKey, value, ValueStatelessMarshaler.INSTANCE, context); + } + + @Override + public int getBinarySerializedSize( + ExtendedAttributeKey attributeKey, Object value, MarshalerContext context) { + int size = 0; + if (!attributeKey.getKey().isEmpty()) { + if (attributeKey instanceof InternalExtendedAttributeKeyImpl) { + byte[] keyUtf8 = ((InternalExtendedAttributeKeyImpl) attributeKey).getKeyUtf8(); + size += MarshalerUtil.sizeBytes(KeyValue.KEY, keyUtf8); + } else { + return StatelessMarshalerUtil.sizeStringWithContext( + KeyValue.KEY, attributeKey.getKey(), context); + } + } + size += + StatelessMarshalerUtil.sizeMessageWithContext( + KeyValue.VALUE, attributeKey, value, ValueStatelessMarshaler.INSTANCE, context); + + return size; + } + + private static class ValueStatelessMarshaler + implements StatelessMarshaler2, Object> { + static final ValueStatelessMarshaler INSTANCE = new ValueStatelessMarshaler(); + + @SuppressWarnings("unchecked") + @Override + public int getBinarySerializedSize( + ExtendedAttributeKey attributeKey, Object value, MarshalerContext context) { + ExtendedAttributeType attributeType = attributeKey.getType(); + switch (attributeType) { + case STRING: + return StringAnyValueStatelessMarshaler.INSTANCE.getBinarySerializedSize( + (String) value, context); + case LONG: + return IntAnyValueStatelessMarshaler.INSTANCE.getBinarySerializedSize( + (Long) value, context); + case BOOLEAN: + return BoolAnyValueStatelessMarshaler.INSTANCE.getBinarySerializedSize( + (Boolean) value, context); + case DOUBLE: + return DoubleAnyValueStatelessMarshaler.INSTANCE.getBinarySerializedSize( + (Double) value, context); + case STRING_ARRAY: + case LONG_ARRAY: + case BOOLEAN_ARRAY: + case DOUBLE_ARRAY: + return StatelessMarshalerUtil.sizeMessageWithContext( + AnyValue.ARRAY_VALUE, + Objects.requireNonNull(attributeKey.asAttributeKey()).getType(), + (List) value, + AttributeArrayAnyValueStatelessMarshaler.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. + throw new IllegalArgumentException("Unsupported attribute type."); + } + + @SuppressWarnings("unchecked") + @Override + public void writeTo( + Serializer output, + ExtendedAttributeKey attributeKey, + Object value, + MarshalerContext context) + throws IOException { + ExtendedAttributeType attributeType = attributeKey.getType(); + switch (attributeType) { + case STRING: + StringAnyValueStatelessMarshaler.INSTANCE.writeTo(output, (String) value, context); + return; + case LONG: + IntAnyValueStatelessMarshaler.INSTANCE.writeTo(output, (Long) value, context); + return; + case BOOLEAN: + BoolAnyValueStatelessMarshaler.INSTANCE.writeTo(output, (Boolean) value, context); + return; + case DOUBLE: + DoubleAnyValueStatelessMarshaler.INSTANCE.writeTo(output, (Double) value, context); + return; + case STRING_ARRAY: + case LONG_ARRAY: + case BOOLEAN_ARRAY: + case DOUBLE_ARRAY: + output.serializeMessageWithContext( + AnyValue.ARRAY_VALUE, + Objects.requireNonNull(attributeKey.asAttributeKey()).getType(), + (List) value, + AttributeArrayAnyValueStatelessMarshaler.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. + throw new IllegalArgumentException("Unsupported attribute type."); + } + } + + private static class ExtendedAttributesKeyValueListStatelessMarshaler + implements StatelessMarshaler { + private static final ExtendedAttributesKeyValueListStatelessMarshaler INSTANCE = + new ExtendedAttributesKeyValueListStatelessMarshaler(); + + private ExtendedAttributesKeyValueListStatelessMarshaler() {} + + @Override + public void writeTo(Serializer output, ExtendedAttributes value, MarshalerContext context) + throws IOException { + serializeExtendedAttributes(output, KeyValueList.VALUES, value, context); + } + + @Override + public int getBinarySerializedSize(ExtendedAttributes value, MarshalerContext context) { + return sizeExtendedAttributes(KeyValueList.VALUES, value, context); + } + } +} 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 new file mode 100644 index 00000000000..fb1e350188a --- /dev/null +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/IncubatingUtil.java @@ -0,0 +1,125 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal.otlp; + +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.internal.InternalExtendedAttributeKeyImpl; +import io.opentelemetry.exporter.internal.marshal.MarshalerContext; +import io.opentelemetry.exporter.internal.marshal.Serializer; +import io.opentelemetry.proto.logs.v1.internal.LogRecord; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.function.BiConsumer; + +/** + * Utilities for interacting with {@code io.opentelemetry:opentelemetry-api-incubator}, which is not + * guaranteed to be present on the classpath. For all methods, callers MUST first separately + * reflectively confirm that the incubator is available on the classpath. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class IncubatingUtil { + + private static final byte[] EMPTY_BYTES = new byte[0]; + private static final KeyValueMarshaler[] EMPTY_REPEATED = new KeyValueMarshaler[0]; + + private IncubatingUtil() {} + + @SuppressWarnings("AvoidObjectArrays") + public static KeyValueMarshaler[] createdExtendedAttributesMarhsalers( + LogRecordData logRecordData) { + return createForExtendedAttributes(getExtendedAttributes(logRecordData)); + } + + public static int extendedAttributesSize(LogRecordData logRecordData) { + return getExtendedAttributes(logRecordData).size(); + } + + // TODO(jack-berg): move to KeyValueMarshaler when ExtendedAttributes is stable + private static KeyValueMarshaler[] createForExtendedAttributes(ExtendedAttributes attributes) { + if (attributes.isEmpty()) { + return EMPTY_REPEATED; + } + + KeyValueMarshaler[] marshalers = new KeyValueMarshaler[attributes.size()]; + attributes.forEach( + new BiConsumer, Object>() { + int index = 0; + + @Override + public void accept(ExtendedAttributeKey attributeKey, Object o) { + marshalers[index++] = create(attributeKey, o); + } + }); + return marshalers; + } + + // TODO(jack-berg): move to KeyValueMarshaler when ExtendedAttributes is stable + @SuppressWarnings("unchecked") + private static KeyValueMarshaler create(ExtendedAttributeKey attributeKey, Object value) { + byte[] keyUtf8; + if (attributeKey.getKey().isEmpty()) { + keyUtf8 = EMPTY_BYTES; + } else if (attributeKey instanceof InternalExtendedAttributeKeyImpl) { + keyUtf8 = ((InternalExtendedAttributeKeyImpl) attributeKey).getKeyUtf8(); + } else { + keyUtf8 = attributeKey.getKey().getBytes(StandardCharsets.UTF_8); + } + switch (attributeKey.getType()) { + case STRING: + return new KeyValueMarshaler(keyUtf8, StringAnyValueMarshaler.create((String) value)); + case LONG: + return new KeyValueMarshaler(keyUtf8, IntAnyValueMarshaler.create((long) value)); + case BOOLEAN: + return new KeyValueMarshaler(keyUtf8, BoolAnyValueMarshaler.create((boolean) value)); + case DOUBLE: + return new KeyValueMarshaler(keyUtf8, DoubleAnyValueMarshaler.create((double) value)); + case STRING_ARRAY: + return new KeyValueMarshaler( + keyUtf8, ArrayAnyValueMarshaler.createString((List) value)); + case LONG_ARRAY: + return new KeyValueMarshaler(keyUtf8, ArrayAnyValueMarshaler.createInt((List) value)); + case BOOLEAN_ARRAY: + return new KeyValueMarshaler( + keyUtf8, ArrayAnyValueMarshaler.createBool((List) value)); + case DOUBLE_ARRAY: + return new KeyValueMarshaler( + keyUtf8, ArrayAnyValueMarshaler.createDouble((List) 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. + throw new IllegalArgumentException("Unsupported attribute type."); + } + + public static int sizeExtendedAttributes(LogRecordData log, MarshalerContext context) { + return ExtendedAttributeKeyValueStatelessMarshaler.sizeExtendedAttributes( + LogRecord.ATTRIBUTES, getExtendedAttributes(log), context); + } + + public static void serializeExtendedAttributes( + Serializer output, LogRecordData log, MarshalerContext context) throws IOException { + ExtendedAttributeKeyValueStatelessMarshaler.serializeExtendedAttributes( + output, LogRecord.ATTRIBUTES, getExtendedAttributes(log), context); + } + + private static ExtendedAttributes getExtendedAttributes(LogRecordData logRecordData) { + if (!(logRecordData instanceof ExtendedLogRecordData)) { + throw new IllegalArgumentException("logRecordData must be ExtendedLogRecordData"); + } + return ((ExtendedLogRecordData) logRecordData).getExtendedAttributes(); + } +} diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueListAnyValueMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueListAnyValueMarshaler.java index 1e5b345acae..1fb20cc269c 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueListAnyValueMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/KeyValueListAnyValueMarshaler.java @@ -19,7 +19,7 @@ final class KeyValueListAnyValueMarshaler extends MarshalerWithSize { private final Marshaler value; - private KeyValueListAnyValueMarshaler(KeyValueListMarshaler value) { + KeyValueListAnyValueMarshaler(KeyValueListMarshaler value) { super(calculateSize(value)); this.value = value; } @@ -42,11 +42,11 @@ private static int calculateSize(Marshaler value) { return MarshalerUtil.sizeMessage(AnyValue.KVLIST_VALUE, value); } - private static class KeyValueListMarshaler extends MarshalerWithSize { + static class KeyValueListMarshaler extends MarshalerWithSize { private final Marshaler[] values; - private KeyValueListMarshaler(KeyValueMarshaler[] values) { + KeyValueListMarshaler(KeyValueMarshaler[] values) { super(calculateSize(values)); this.values = values; } 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 08b9699291d..ec7dd47f10b 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 @@ -33,7 +33,7 @@ public final class KeyValueMarshaler extends MarshalerWithSize { private final byte[] keyUtf8; private final Marshaler value; - private KeyValueMarshaler(byte[] keyUtf8, Marshaler value) { + KeyValueMarshaler(byte[] keyUtf8, Marshaler value) { super(calculateSize(keyUtf8, value)); this.keyUtf8 = keyUtf8; this.value = value; diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java index fb9f036308b..343d158cf8c 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java @@ -15,6 +15,7 @@ import io.opentelemetry.exporter.internal.marshal.ProtoEnumInfo; import io.opentelemetry.exporter.internal.marshal.Serializer; import io.opentelemetry.exporter.internal.otlp.AnyValueMarshaler; +import io.opentelemetry.exporter.internal.otlp.IncubatingUtil; import io.opentelemetry.exporter.internal.otlp.KeyValueMarshaler; import io.opentelemetry.proto.logs.v1.internal.LogRecord; import io.opentelemetry.proto.logs.v1.internal.SeverityNumber; @@ -24,6 +25,19 @@ import javax.annotation.Nullable; final class LogMarshaler extends MarshalerWithSize { + private static final boolean INCUBATOR_AVAILABLE; + + static { + boolean incubatorAvailable = false; + try { + Class.forName("io.opentelemetry.api.incubator.common.ExtendedAttributes"); + incubatorAvailable = true; + } catch (ClassNotFoundException e) { + // Not available + } + INCUBATOR_AVAILABLE = incubatorAvailable; + } + private static final String INVALID_TRACE_ID = TraceId.getInvalid(); private static final String INVALID_SPAN_ID = SpanId.getInvalid(); private static final byte[] EMPTY_BYTES = new byte[0]; @@ -42,7 +56,14 @@ final class LogMarshaler extends MarshalerWithSize { static LogMarshaler create(LogRecordData logRecordData) { KeyValueMarshaler[] attributeMarshalers = - KeyValueMarshaler.createForAttributes(logRecordData.getAttributes()); + INCUBATOR_AVAILABLE + ? IncubatingUtil.createdExtendedAttributesMarhsalers(logRecordData) + : KeyValueMarshaler.createForAttributes(logRecordData.getAttributes()); + + int attributeSize = + INCUBATOR_AVAILABLE + ? IncubatingUtil.extendedAttributesSize(logRecordData) + : logRecordData.getAttributes().size(); MarshalerWithSize bodyMarshaler = logRecordData.getBodyValue() == null @@ -57,11 +78,10 @@ static LogMarshaler create(LogRecordData logRecordData) { MarshalerUtil.toBytes(logRecordData.getSeverityText()), bodyMarshaler, attributeMarshalers, - logRecordData.getTotalAttributeCount() - logRecordData.getAttributes().size(), + logRecordData.getTotalAttributeCount() - attributeSize, spanContext.getTraceFlags(), spanContext.getTraceId().equals(INVALID_TRACE_ID) ? null : spanContext.getTraceId(), spanContext.getSpanId().equals(INVALID_SPAN_ID) ? null : spanContext.getSpanId(), - // TODO: protect against incubator not being present logRecordData instanceof ExtendedLogRecordData ? MarshalerUtil.toBytes(((ExtendedLogRecordData) logRecordData).getEventName()) : EMPTY_BYTES); diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java index 1477af88ce6..868688d1c9b 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java @@ -17,6 +17,7 @@ import io.opentelemetry.exporter.internal.marshal.StatelessMarshalerUtil; import io.opentelemetry.exporter.internal.otlp.AnyValueStatelessMarshaler; import io.opentelemetry.exporter.internal.otlp.AttributeKeyValueStatelessMarshaler; +import io.opentelemetry.exporter.internal.otlp.IncubatingUtil; import io.opentelemetry.proto.logs.v1.internal.LogRecord; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; @@ -24,8 +25,22 @@ /** See {@link LogMarshaler}. */ final class LogStatelessMarshaler implements StatelessMarshaler { + private static final String INVALID_TRACE_ID = TraceId.getInvalid(); private static final String INVALID_SPAN_ID = SpanId.getInvalid(); + private static final boolean INCUBATOR_AVAILABLE; + + static { + boolean incubatorAvailable = false; + try { + Class.forName("io.opentelemetry.api.incubator.common.ExtendedAttributes"); + incubatorAvailable = true; + } catch (ClassNotFoundException e) { + // Not available + } + INCUBATOR_AVAILABLE = incubatorAvailable; + } + static final LogStatelessMarshaler INSTANCE = new LogStatelessMarshaler(); @Override @@ -40,13 +55,22 @@ public void writeTo(Serializer output, LogRecordData log, MarshalerContext conte output.serializeMessageWithContext( LogRecord.BODY, log.getBodyValue(), AnyValueStatelessMarshaler.INSTANCE, context); } - output.serializeRepeatedMessageWithContext( - LogRecord.ATTRIBUTES, - log.getAttributes(), - AttributeKeyValueStatelessMarshaler.INSTANCE, - context); - int droppedAttributesCount = log.getTotalAttributeCount() - log.getAttributes().size(); - output.serializeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + if (INCUBATOR_AVAILABLE) { + IncubatingUtil.serializeExtendedAttributes(output, log, context); + + int droppedAttributesCount = + log.getTotalAttributeCount() - IncubatingUtil.extendedAttributesSize(log); + output.serializeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + } else { + output.serializeRepeatedMessageWithContext( + LogRecord.ATTRIBUTES, + log.getAttributes(), + AttributeKeyValueStatelessMarshaler.INSTANCE, + context); + + int droppedAttributesCount = log.getTotalAttributeCount() - log.getAttributes().size(); + output.serializeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + } SpanContext spanContext = log.getSpanContext(); output.serializeFixed32(LogRecord.FLAGS, spanContext.getTraceFlags().asByte()); @@ -80,14 +104,23 @@ public int getBinarySerializedSize(LogRecordData log, MarshalerContext context) StatelessMarshalerUtil.sizeMessageWithContext( LogRecord.BODY, log.getBodyValue(), AnyValueStatelessMarshaler.INSTANCE, context); } - size += - StatelessMarshalerUtil.sizeRepeatedMessageWithContext( - LogRecord.ATTRIBUTES, - log.getAttributes(), - AttributeKeyValueStatelessMarshaler.INSTANCE, - context); - int droppedAttributesCount = log.getTotalAttributeCount() - log.getAttributes().size(); - size += MarshalerUtil.sizeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + if (INCUBATOR_AVAILABLE) { + size += IncubatingUtil.sizeExtendedAttributes(log, context); + + int droppedAttributesCount = + log.getTotalAttributeCount() - IncubatingUtil.extendedAttributesSize(log); + size += MarshalerUtil.sizeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + } else { + size += + StatelessMarshalerUtil.sizeRepeatedMessageWithContext( + LogRecord.ATTRIBUTES, + log.getAttributes(), + AttributeKeyValueStatelessMarshaler.INSTANCE, + context); + + int droppedAttributesCount = log.getTotalAttributeCount() - log.getAttributes().size(); + size += MarshalerUtil.sizeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + } SpanContext spanContext = log.getSpanContext(); size += MarshalerUtil.sizeFixed32(LogRecord.FLAGS, spanContext.getTraceFlags().asByte()); diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java index 0e54a10f0e2..9c01ddc7aed 100644 --- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java +++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java @@ -33,7 +33,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.internal.TestExtendedLogRecordData; +import io.opentelemetry.sdk.testing.logs.TestLogRecordData; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -52,7 +52,6 @@ class LogsRequestMarshalerTest { private static final String TRACE_ID = TraceId.fromBytes(TRACE_ID_BYTES); private static final byte[] SPAN_ID_BYTES = new byte[] {0, 0, 0, 0, 4, 3, 2, 1}; private static final String SPAN_ID = SpanId.fromBytes(SPAN_ID_BYTES); - private static final String EVENT_NAME = "hello"; private static final String BODY = "Hello world from this log..."; @Test @@ -60,7 +59,7 @@ void toProtoResourceLogs() { ResourceLogsMarshaler[] resourceLogsMarshalers = ResourceLogsMarshaler.create( Collections.singleton( - TestExtendedLogRecordData.builder() + TestLogRecordData.builder() .setResource( Resource.builder().put("one", 1).setSchemaUrl("http://url").build()) .setInstrumentationScopeInfo( @@ -69,7 +68,6 @@ void toProtoResourceLogs() { .setSchemaUrl("http://url") .setAttributes(Attributes.builder().put("key", "value").build()) .build()) - .setEventName(EVENT_NAME) .setBody(BODY) .setSeverity(Severity.INFO) .setSeverityText("INFO") @@ -110,12 +108,11 @@ void toProtoLogRecord(MarshalerSource marshalerSource) { parse( LogRecord.getDefaultInstance(), marshalerSource.create( - TestExtendedLogRecordData.builder() + TestLogRecordData.builder() .setResource( Resource.create(Attributes.builder().put("testKey", "testValue").build())) .setInstrumentationScopeInfo( InstrumentationScopeInfo.builder("instrumentation").setVersion("1").build()) - .setEventName(EVENT_NAME) .setBody(BODY) .setSeverity(Severity.INFO) .setSeverityText("INFO") @@ -131,7 +128,6 @@ void toProtoLogRecord(MarshalerSource marshalerSource) { assertThat(logRecord.getTraceId().toByteArray()).isEqualTo(TRACE_ID_BYTES); assertThat(logRecord.getSpanId().toByteArray()).isEqualTo(SPAN_ID_BYTES); assertThat(logRecord.getSeverityText()).isEqualTo("INFO"); - assertThat(logRecord.getEventName()).isEqualTo(EVENT_NAME); assertThat(logRecord.getBody()).isEqualTo(AnyValue.newBuilder().setStringValue(BODY).build()); assertThat(logRecord.getAttributesList()) .containsExactly( @@ -151,7 +147,7 @@ void toProtoLogRecord_MinimalFields(MarshalerSource marshalerSource) { parse( LogRecord.getDefaultInstance(), marshalerSource.create( - TestExtendedLogRecordData.builder() + TestLogRecordData.builder() .setResource( Resource.create(Attributes.builder().put("testKey", "testValue").build())) .setInstrumentationScopeInfo( diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LowAllocationLogRequestMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LowAllocationLogRequestMarshalerTest.java index 2d8d61e207d..4890e02dd66 100644 --- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LowAllocationLogRequestMarshalerTest.java +++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LowAllocationLogRequestMarshalerTest.java @@ -16,7 +16,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.internal.TestExtendedLogRecordData; +import io.opentelemetry.sdk.testing.logs.TestLogRecordData; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -39,7 +39,6 @@ class LowAllocationLogRequestMarshalerTest { AttributeKey.doubleArrayKey("key_double_array"); private static final AttributeKey> KEY_BOOLEAN_ARRAY = AttributeKey.booleanArrayKey("key_boolean_array"); - private static final String EVENT_NAME = "hello"; private static final String BODY = "Hello world from this log..."; private static final Resource RESOURCE = @@ -73,10 +72,9 @@ private static List createLogRecordDataList() { } private static LogRecordData createLogRecordData() { - return TestExtendedLogRecordData.builder() + return TestLogRecordData.builder() .setResource(RESOURCE) .setInstrumentationScopeInfo(INSTRUMENTATION_SCOPE_INFO) - .setEventName(EVENT_NAME) .setBody(BODY) .setSeverity(Severity.INFO) .setSeverityText("INFO") diff --git a/exporters/otlp/common/src/testIncubating/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerIncubatingTest.java b/exporters/otlp/common/src/testIncubating/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerIncubatingTest.java new file mode 100644 index 00000000000..8b4533e24ae --- /dev/null +++ b/exporters/otlp/common/src/testIncubating/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerIncubatingTest.java @@ -0,0 +1,292 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal.otlp.logs; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.JsonFormat; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.internal.OtelEncodingUtils; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanId; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.exporter.internal.marshal.Marshaler; +import io.opentelemetry.exporter.internal.marshal.MarshalerContext; +import io.opentelemetry.exporter.internal.marshal.Serializer; +import io.opentelemetry.exporter.internal.marshal.StatelessMarshaler; +import io.opentelemetry.proto.common.v1.AnyValue; +import io.opentelemetry.proto.common.v1.ArrayValue; +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.common.v1.KeyValueList; +import io.opentelemetry.proto.logs.v1.LogRecord; +import io.opentelemetry.proto.logs.v1.ResourceLogs; +import io.opentelemetry.proto.logs.v1.ScopeLogs; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.logs.internal.TestExtendedLogRecordData; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class LogsRequestMarshalerIncubatingTest { + private static final byte[] TRACE_ID_BYTES = + new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}; + private static final String TRACE_ID = TraceId.fromBytes(TRACE_ID_BYTES); + private static final byte[] SPAN_ID_BYTES = new byte[] {0, 0, 0, 0, 4, 3, 2, 1}; + private static final String SPAN_ID = SpanId.fromBytes(SPAN_ID_BYTES); + private static final String EVENT_NAME = "hello"; + private static final String BODY = "Hello world from this log..."; + + @ParameterizedTest + @EnumSource(MarshalerSource.class) + void toProtoLogRecord(MarshalerSource marshalerSource) { + LogRecord logRecord = + parse( + LogRecord.getDefaultInstance(), + marshalerSource.create( + TestExtendedLogRecordData.builder() + .setResource( + Resource.create(Attributes.builder().put("testKey", "testValue").build())) + .setInstrumentationScopeInfo( + InstrumentationScopeInfo.builder("instrumentation").setVersion("1").build()) + .setBody(BODY) + .setSeverity(Severity.INFO) + .setSeverityText("INFO") + .setSpanContext( + SpanContext.create( + TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) + .setTotalAttributeCount(10) + .setTimestamp(12345, TimeUnit.NANOSECONDS) + .setObservedTimestamp(6789, TimeUnit.NANOSECONDS) + // Extended fields + .setEventName(EVENT_NAME) + .setExtendedAttributes( + ExtendedAttributes.builder() + .put("str_key", "str_value") + .put("str_arr_key", "str_value1", "str_value2") + .put("bool_key", true) + .put("bool_arr_key", true, false) + .put("double_key", 1.1) + .put("double_arr_key", 1.1, 2.2) + .put("int_key", 1) + .put("int_arr_key", 1, 2) + .put( + "kv_list_key", + ExtendedAttributes.builder() + .put("bool_key", true) + .put("double_key", 1.1) + .put("int_key", 1) + .put( + "kv_list_key", + ExtendedAttributes.builder() + .put("str_key", "str_value") + .build()) + .put("str_key", "str_value") + .build()) + .build()) + .build())); + + assertThat(logRecord.getTraceId().toByteArray()).isEqualTo(TRACE_ID_BYTES); + assertThat(logRecord.getSpanId().toByteArray()).isEqualTo(SPAN_ID_BYTES); + assertThat(logRecord.getSeverityText()).isEqualTo("INFO"); + + assertThat(logRecord.getBody()).isEqualTo(AnyValue.newBuilder().setStringValue(BODY).build()); + assertThat(logRecord.getDroppedAttributesCount()).isEqualTo(1); + assertThat(logRecord.getTimeUnixNano()).isEqualTo(12345); + assertThat(logRecord.getObservedTimeUnixNano()).isEqualTo(6789); + assertThat(logRecord.getEventName()).isEqualTo(EVENT_NAME); + assertThat(logRecord.getAttributesList()) + .containsExactlyInAnyOrder( + keyValue("str_key", anyValue("str_value")), + keyValue( + "str_arr_key", + anyValue(Arrays.asList(anyValue("str_value1"), anyValue("str_value2")))), + keyValue("bool_key", anyValue(true)), + keyValue("bool_arr_key", anyValue(Arrays.asList(anyValue(true), anyValue(false)))), + keyValue("double_key", anyValue(1.1)), + keyValue("double_arr_key", anyValue(Arrays.asList(anyValue(1.1), anyValue(2.2)))), + keyValue("int_key", anyValue(1)), + keyValue("int_arr_key", anyValue(Arrays.asList(anyValue(1), anyValue(2)))), + keyValue( + "kv_list_key", + AnyValue.newBuilder() + .setKvlistValue( + KeyValueList.newBuilder() + .addValues(keyValue("bool_key", anyValue(true))) + .addValues(keyValue("double_key", anyValue(1.1))) + .addValues(keyValue("int_key", anyValue(1))) + .addValues( + keyValue( + "kv_list_key", + AnyValue.newBuilder() + .setKvlistValue( + KeyValueList.newBuilder() + .addValues( + keyValue("str_key", anyValue("str_value"))) + .build()) + .build())) + .addValues(keyValue("str_key", anyValue("str_value"))) + .build()) + .build())); + } + + private static AnyValue anyValue(String value) { + return AnyValue.newBuilder().setStringValue(value).build(); + } + + private static AnyValue anyValue(long value) { + return AnyValue.newBuilder().setIntValue(value).build(); + } + + private static AnyValue anyValue(double value) { + return AnyValue.newBuilder().setDoubleValue(value).build(); + } + + private static AnyValue anyValue(boolean value) { + return AnyValue.newBuilder().setBoolValue(value).build(); + } + + private static AnyValue anyValue(List value) { + return AnyValue.newBuilder() + .setArrayValue(ArrayValue.newBuilder().addAllValues(value).build()) + .build(); + } + + private static KeyValue keyValue(String key, AnyValue anyValue) { + return KeyValue.newBuilder().setKey(key).setValue(anyValue).build(); + } + + @SuppressWarnings("unchecked") + private static T parse(T prototype, Marshaler marshaler) { + byte[] serialized = toByteArray(marshaler); + T result; + try { + result = (T) prototype.newBuilderForType().mergeFrom(serialized).build(); + } catch (InvalidProtocolBufferException e) { + throw new UncheckedIOException(e); + } + // Our marshaler should produce the exact same length of serialized output (for example, field + // default values are not outputted), so we check that here. The output itself may have slightly + // different ordering, mostly due to the way we don't output oneof values in field order all the + // tieme. If the lengths are equal and the resulting protos are equal, the marshaling is + // guaranteed to be valid. + assertThat(result.getSerializedSize()).isEqualTo(serialized.length); + + // We don't compare JSON strings due to some differences (particularly serializing enums as + // numbers instead of names). This may improve in the future but what matters is what we produce + // can be parsed. + Message.Builder builder = prototype.newBuilderForType(); + try { + String json = toJson(marshaler); + JsonFormat.parser().merge(json, builder); + } catch (InvalidProtocolBufferException e) { + throw new UncheckedIOException(e); + } + + // Hackily swap out "hex as base64" decoded IDs with correct ones since no JSON protobuf + // libraries currently support customizing on the parse side. + if (result instanceof LogRecord) { + fixSpanJsonIds((LogRecord.Builder) builder); + } + + if (result instanceof ResourceLogs) { + ResourceLogs.Builder fixed = (ResourceLogs.Builder) builder; + for (ScopeLogs.Builder ill : fixed.getScopeLogsBuilderList()) { + for (LogRecord.Builder span : ill.getLogRecordsBuilderList()) { + fixSpanJsonIds(span); + } + } + } + + assertThat(builder.build()).isEqualTo(result); + + return result; + } + + private static void fixSpanJsonIds(LogRecord.Builder span) { + span.setTraceId(toHex(span.getTraceId())); + span.setSpanId(toHex(span.getSpanId())); + } + + @SuppressWarnings("UnusedMethod") + private static ByteString toHex(ByteString hexReadAsBase64) { + String hex = + Base64.getEncoder().encodeToString(hexReadAsBase64.toByteArray()).toLowerCase(Locale.ROOT); + return ByteString.copyFrom(OtelEncodingUtils.bytesFromBase16(hex, hex.length())); + } + + private static byte[] toByteArray(Marshaler marshaler) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + marshaler.writeBinaryTo(bos); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return bos.toByteArray(); + } + + private static String toJson(Marshaler marshaler) { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + marshaler.writeJsonTo(bos); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return new String(bos.toByteArray(), StandardCharsets.UTF_8); + } + + private static Marshaler createMarshaler(StatelessMarshaler marshaler, T data) { + return new Marshaler() { + private final MarshalerContext context = new MarshalerContext(); + private final int size = marshaler.getBinarySerializedSize(data, context); + + @Override + public int getBinarySerializedSize() { + return size; + } + + @Override + protected void writeTo(Serializer output) throws IOException { + context.resetReadIndex(); + marshaler.writeTo(output, data, context); + } + }; + } + + private enum MarshalerSource { + MARSHALER { + @Override + Marshaler create(LogRecordData logData) { + return LogMarshaler.create(logData); + } + }, + LOW_ALLOCATION_MARSHALER { + @Override + Marshaler create(LogRecordData logData) { + return createMarshaler(LogStatelessMarshaler.INSTANCE, logData); + } + }; + + abstract Marshaler create(LogRecordData logData); + } +} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java deleted file mode 100644 index 908f0123511..00000000000 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedReadWriteLogRecord.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.logs; - -import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; -import io.opentelemetry.api.incubator.common.ExtendedAttributes; -import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; -import javax.annotation.Nullable; - -/** TODO. */ -public interface ExtendedReadWriteLogRecord extends ReadWriteLogRecord { - - /** TODO. */ - ExtendedReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value); - - /** TODO. */ - @SuppressWarnings("unchecked") - default ExtendedReadWriteLogRecord setAllAttributes(ExtendedAttributes extendedAttributes) { - if (extendedAttributes == null || extendedAttributes.isEmpty()) { - return this; - } - extendedAttributes.forEach( - (attributeKey, value) -> - this.setAttribute((ExtendedAttributeKey) attributeKey, value)); - return this; - } - - /** Return an immutable {@link ExtendedLogRecordData} instance representing this log record. */ - @Override - ExtendedLogRecordData toLogRecordData(); - - /** TODO. */ - @Nullable - T getAttribute(ExtendedAttributeKey key); - - /** TODO. */ - ExtendedAttributes getExtendedAttributes(); -} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java index d216c6704ca..495f826624d 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkLogRecordData.java @@ -6,7 +6,6 @@ package io.opentelemetry.sdk.logs; import com.google.auto.value.AutoValue; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.logs.Severity; @@ -62,9 +61,4 @@ public io.opentelemetry.sdk.logs.data.Body getBody() { ? io.opentelemetry.sdk.logs.data.Body.empty() : io.opentelemetry.sdk.logs.data.Body.string(valueBody.asString()); } - - @Override - public Attributes getAttributes() { - return getExtendedAttributes().asAttributes(); - } } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java index d1c458b2e20..ef3789d44dd 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ExtendedSdkReadWriteLogRecord.java @@ -16,6 +16,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.internal.ExtendedAttributesMap; import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; +import io.opentelemetry.sdk.logs.internal.ExtendedReadWriteLogRecord; import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; @@ -25,6 +26,7 @@ class ExtendedSdkReadWriteLogRecord extends SdkReadWriteLogRecord implements ExtendedReadWriteLogRecord { @Nullable private final String eventName; + private final Object lock = new Object(); @GuardedBy("lock") @Nullable diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java index 821823d8c76..8b73a8eacb8 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java @@ -30,7 +30,7 @@ class SdkReadWriteLogRecord implements ReadWriteLogRecord { protected final Severity severity; @Nullable protected final String severityText; @Nullable protected final Value body; - protected final Object lock = new Object(); + private final Object lock = new Object(); @GuardedBy("lock") @Nullable diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java index 726643d0148..9cfc40b4231 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/internal/ExtendedLogRecordData.java @@ -20,11 +20,16 @@ public interface ExtendedLogRecordData extends LogRecordData { @Nullable String getEventName(); - /** TODO. */ + /** Returns the attributes for this log, or {@link ExtendedAttributes#empty()} if unset. */ ExtendedAttributes getExtendedAttributes(); - /** TODO. */ + /** + * Returns the attributes for this log, or {@link Attributes#empty()} if unset. + * + * @deprecated Use {@link #getExtendedAttributes()}. + */ @Override + @Deprecated default Attributes getAttributes() { return getExtendedAttributes().asAttributes(); } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/ExtendedReadWriteLogRecord.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/ExtendedReadWriteLogRecord.java new file mode 100644 index 00000000000..527c1e14063 --- /dev/null +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/ExtendedReadWriteLogRecord.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.logs.internal; + +import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; +import javax.annotation.Nullable; + +/** + * A collection of configuration options which define the behavior of a {@link + * io.opentelemetry.api.logs.Logger}. + * + *

This class is internal and experimental. Its APIs are unstable and can change at any time. Its + * APIs (or a version of them) may be promoted to the public stable API in the future, but no + * guarantees are made. + */ +public interface ExtendedReadWriteLogRecord extends ReadWriteLogRecord { + + /** + * Sets an attribute on the log record. If the log record previously contained a mapping for the + * key, the old value is replaced by the specified value. + * + *

Note: the behavior of null values is undefined, and hence strongly discouraged. + */ + ExtendedReadWriteLogRecord setAttribute(ExtendedAttributeKey key, T value); + + /** + * Sets attributes to the {@link ReadWriteLogRecord}. If the {@link ReadWriteLogRecord} previously + * contained a mapping for any of the keys, the old values are replaced by the specified values. + * + * @param extendedAttributes the attributes + * @return this. + */ + @SuppressWarnings("unchecked") + default ExtendedReadWriteLogRecord setAllAttributes(ExtendedAttributes extendedAttributes) { + if (extendedAttributes == null || extendedAttributes.isEmpty()) { + return this; + } + extendedAttributes.forEach( + (attributeKey, value) -> + this.setAttribute((ExtendedAttributeKey) attributeKey, value)); + return this; + } + + /** Return an immutable {@link ExtendedLogRecordData} instance representing this log record. */ + @Override + ExtendedLogRecordData toLogRecordData(); + + /** + * Returns the value of a given attribute if it exists. This is the equivalent of calling + * getAttributes().get(key) + */ + @Nullable + T getAttribute(ExtendedAttributeKey key); + + /** Returns the attributes for this log, or {@link ExtendedAttributes#empty()} if unset. */ + ExtendedAttributes getExtendedAttributes(); +} diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java index 4209c50bfaa..885af43e376 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilderTest.java @@ -75,7 +75,7 @@ void emit_AllFields() { builder.setTimestamp(timestamp); builder.setObservedTimestamp(456, TimeUnit.SECONDS); builder.setObservedTimestamp(observedTimestamp); - builder.setAttribute((AttributeKey) null, null); + builder.setAttribute(null, null); builder.setAttribute(AttributeKey.stringKey("k1"), "v1"); builder.setAllAttributes(Attributes.builder().put("k2", "v2").put("k3", "v3").build()); builder.setContext(Span.wrap(spanContext).storeInContext(Context.root())); diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java index 6f396b89edd..63063731ac4 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java @@ -228,7 +228,7 @@ void loggerBuilder_WithLogRecordProcessor() { .setResource(resource) .addLogRecordProcessor( (unused, logRecord) -> { - logRecord.setAttribute((AttributeKey) null, null); + logRecord.setAttribute(null, null); // Overwrite k1 logRecord.setAttribute(AttributeKey.stringKey("k1"), "new-v1"); // Add new attribute k3 diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java index 8b1d360d733..2ea1ee19291 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerTest.java @@ -29,7 +29,6 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class SdkLoggerTest { @@ -56,7 +55,6 @@ void logRecordBuilder() { } @Test - @Disabled void logRecordBuilder_maxAttributeLength() { int maxLength = 25; AtomicReference seenLog = new AtomicReference<>(); @@ -97,7 +95,6 @@ void logRecordBuilder_maxAttributeLength() { } @Test - @Disabled void logRecordBuilder_maxAttributes() { int maxNumberOfAttrs = 8; AtomicReference seenLog = new AtomicReference<>(); From 9b3f2ac4f8471c2c8ff0a590e538e7b97e756ebc Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Thu, 20 Mar 2025 15:06:33 -0500 Subject: [PATCH 4/8] Add unit tests --- .../common/ExtendedAttributeKeyTest.java | 85 +++++ .../common/ExtendedAttributesTest.java | 360 ++++++++++++++++++ .../logs/ExtendedLogsBridgeApiUsageTest.java | 20 +- .../logging/otlp/TestDataExporter.java | 6 +- .../sdk/internal/ExtendedAttributesMap.java | 5 +- 5 files changed, 462 insertions(+), 14 deletions(-) create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java 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 new file mode 100644 index 00000000000..d2f77625d1f --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributeKeyTest.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtendedAttributeKeyTest { + + @ParameterizedTest + @MethodSource("attributeKeyArgs") + void test( + ExtendedAttributeKey key, + String expectedKey, + ExtendedAttributeType expectedType, + @Nullable AttributeKey expectedAttributeKey) { + assertThat(key.getKey()).isEqualTo(expectedKey); + assertThat(key.getType()).isEqualTo(expectedType); + assertThat(key.asAttributeKey()).isEqualTo(expectedAttributeKey); + + if (expectedAttributeKey != null) { + ExtendedAttributeKey extendedAttributeKey = + ExtendedAttributeKey.fromAttributeKey(expectedAttributeKey); + assertThat(extendedAttributeKey).isEqualTo(key); + } + } + + private static Stream attributeKeyArgs() { + return Stream.of( + Arguments.of( + ExtendedAttributeKey.stringKey("key"), + "key", + ExtendedAttributeType.STRING, + AttributeKey.stringKey("key")), + Arguments.of( + ExtendedAttributeKey.booleanKey("key"), + "key", + ExtendedAttributeType.BOOLEAN, + AttributeKey.booleanKey("key")), + Arguments.of( + ExtendedAttributeKey.longKey("key"), + "key", + ExtendedAttributeType.LONG, + AttributeKey.longKey("key")), + Arguments.of( + ExtendedAttributeKey.doubleKey("key"), + "key", + ExtendedAttributeType.DOUBLE, + AttributeKey.doubleKey("key")), + Arguments.of( + ExtendedAttributeKey.stringArrayKey("key"), + "key", + ExtendedAttributeType.STRING_ARRAY, + AttributeKey.stringArrayKey("key")), + Arguments.of( + ExtendedAttributeKey.booleanArrayKey("key"), + "key", + ExtendedAttributeType.BOOLEAN_ARRAY, + AttributeKey.booleanArrayKey("key")), + Arguments.of( + ExtendedAttributeKey.longArrayKey("key"), + "key", + ExtendedAttributeType.LONG_ARRAY, + AttributeKey.longArrayKey("key")), + Arguments.of( + ExtendedAttributeKey.doubleArrayKey("key"), + "key", + ExtendedAttributeType.DOUBLE_ARRAY, + AttributeKey.doubleArrayKey("key")), + Arguments.of( + ExtendedAttributeKey.extendedAttributesKey("key"), + "key", + ExtendedAttributeType.EXTENDED_ATTRIBUTES, + null)); + } +} 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 new file mode 100644 index 00000000000..4481cead3d0 --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/common/ExtendedAttributesTest.java @@ -0,0 +1,360 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.common; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ExtendedAttributesTest { + + @ParameterizedTest + @MethodSource("attributesArgs") + void get_ExtendedAttributeKey( + ExtendedAttributes extendedAttributes, Map expectedMap) { + expectedMap.forEach( + (key, value) -> { + ExtendedAttributeKey extendedAttributeKey = getKey(key, value); + Object actualValue = extendedAttributes.get(extendedAttributeKey); + if (actualValue instanceof ExtendedAttributes) { + Map mapValue = toMap((ExtendedAttributes) actualValue); + actualValue = mapValue; + } + + assertThat(actualValue) + .describedAs(key + "(" + extendedAttributeKey.getType() + ")") + .isEqualTo(value); + }); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void get_AttributeKey(ExtendedAttributes extendedAttributes, Map expectedMap) { + expectedMap.forEach( + (key, value) -> { + ExtendedAttributeKey extendedAttributeKey = getKey(key, value); + AttributeKey attributeKey = extendedAttributeKey.asAttributeKey(); + + // Skip attribute keys which cannot be represented as AttributeKey + if (attributeKey == null) { + return; + } + + Object actualValue = extendedAttributes.get(attributeKey); + + assertThat(actualValue) + .describedAs(key + "(" + attributeKey.getType() + ")") + .isEqualTo(value); + }); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void forEach(ExtendedAttributes extendedAttributes, Map expectedMap) { + // toMap uses .forEach to convert + Map seenEntries = toMap(extendedAttributes); + + assertThat(seenEntries).isEqualTo(expectedMap); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void size(ExtendedAttributes extendedAttributes, Map expectedMap) { + assertThat(extendedAttributes.size()).isEqualTo(expectedMap.size()); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void isEmpty(ExtendedAttributes extendedAttributes, Map expectedMap) { + assertThat(extendedAttributes.isEmpty()).isEqualTo(expectedMap.isEmpty()); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void asMap(ExtendedAttributes extendedAttributes, Map expectedMap) { + assertEquals(extendedAttributes.asMap(), expectedMap); + } + + @SuppressWarnings("unchecked") + private static void assertEquals( + Map, Object> actual, Map expected) { + assertThat(actual.size()).isEqualTo(expected.size()); + actual.forEach( + (key, value) -> { + if (key.getType() == ExtendedAttributeType.EXTENDED_ATTRIBUTES) { + assertEquals( + ((ExtendedAttributes) value).asMap(), + (Map) expected.get(key.getKey())); + return; + } + assertThat(expected.get(key.getKey())).isEqualTo(value); + }); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void asAttributes(ExtendedAttributes extendedAttributes, Map expectedMap) { + Attributes attributes = extendedAttributes.asAttributes(); + + attributes.forEach( + (key, value) -> { + assertThat(value).isEqualTo(expectedMap.get(key.getKey())); + }); + + long expectedSize = + expectedMap.values().stream().filter(value -> !(value instanceof Map)).count(); + assertThat(attributes.size()).isEqualTo(expectedSize); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void toBuilder(ExtendedAttributes extendedAttributes, Map expectedMap) { + ExtendedAttributesBuilder builder = extendedAttributes.toBuilder(); + + builder.put("extraKey", "value"); + + ExtendedAttributes extendedAttributes1 = builder.build(); + assertThat(extendedAttributes1.size()).isEqualTo(expectedMap.size() + 1); + + ExtendedAttributes extendedAttributes2 = + extendedAttributes1.toBuilder().remove(ExtendedAttributeKey.stringKey("extraKey")).build(); + + assertThat(extendedAttributes2).isEqualTo(extendedAttributes); + assertThat(extendedAttributes2.size()).isEqualTo(expectedMap.size()); + } + + @ParameterizedTest + @MethodSource("attributesArgs") + void equalsAndHashcode(ExtendedAttributes extendedAttributes, Map expectedMap) { + ExtendedAttributes withExtraEntry = + extendedAttributes.toBuilder().put("extraKey", "value").build(); + assertThat(extendedAttributes).isNotEqualTo(withExtraEntry); + assertThat(extendedAttributes.hashCode()).isNotEqualTo(withExtraEntry.hashCode()); + + ExtendedAttributes copy1 = + extendedAttributes.toBuilder().remove(ExtendedAttributeKey.stringKey("extraKey")).build(); + assertThat(extendedAttributes).isEqualTo(copy1); + assertThat(extendedAttributes.hashCode()).isEqualTo(copy1.hashCode()); + + ExtendedAttributes copy2 = fromMap(expectedMap); + assertThat(extendedAttributes).isEqualTo(copy2); + assertThat(extendedAttributes.hashCode()).isEqualTo(copy2.hashCode()); + } + + @SuppressWarnings("unchecked") + private static ExtendedAttributes fromMap(Map map) { + ExtendedAttributesBuilder builder = ExtendedAttributes.builder(); + map.forEach( + (key, value) -> { + ExtendedAttributeKey extendedAttributeKey = getKey(key, value); + if (extendedAttributeKey.getType() == ExtendedAttributeType.EXTENDED_ATTRIBUTES) { + builder.put( + (ExtendedAttributeKey) extendedAttributeKey, + fromMap((Map) value)); + return; + } + putInBuilder((ExtendedAttributeKey) extendedAttributeKey, value, builder); + }); + return builder.build(); + } + + private static void putInBuilder( + ExtendedAttributeKey key, Object value, ExtendedAttributesBuilder builder) { + builder.put(key, value); + } + + private static Stream attributesArgs() { + return Stream.of( + // Single entry attributes + Arguments.of(ExtendedAttributes.builder().build(), Collections.emptyMap()), + Arguments.of( + ExtendedAttributes.builder().put("key", "value").build(), + ImmutableMap.builder().put("key", "value").build()), + Arguments.of( + ExtendedAttributes.builder().put("key", true).build(), + ImmutableMap.builder().put("key", true).build()), + Arguments.of( + ExtendedAttributes.builder().put("key", 1L).build(), + ImmutableMap.builder().put("key", 1L).build()), + Arguments.of( + ExtendedAttributes.builder().put("key", 1.1).build(), + ImmutableMap.builder().put("key", 1.1).build()), + Arguments.of( + ExtendedAttributes.builder().put("key", "value1", "value2").build(), + ImmutableMap.builder().put("key", Arrays.asList("value1", "value2")).build()), + Arguments.of( + ExtendedAttributes.builder().put("key", true, false).build(), + ImmutableMap.builder().put("key", Arrays.asList(true, false)).build()), + Arguments.of( + ExtendedAttributes.builder().put("key", 1L, 2L).build(), + ImmutableMap.builder().put("key", Arrays.asList(1L, 2L)).build()), + Arguments.of( + ExtendedAttributes.builder().put("key", 1.1, 2.2).build(), + ImmutableMap.builder().put("key", Arrays.asList(1.1, 2.2)).build()), + Arguments.of( + ExtendedAttributes.builder() + .put("key", ExtendedAttributes.builder().put("child", "value").build()) + .build(), + ImmutableMap.builder() + .put("key", ImmutableMap.builder().put("child", "value").build()) + .build()), + Arguments.of( + ExtendedAttributes.builder() + .put(ExtendedAttributeKey.stringKey("key"), "value") + .build(), + ImmutableMap.builder().put("key", "value").build()), + Arguments.of( + ExtendedAttributes.builder().put(ExtendedAttributeKey.booleanKey("key"), true).build(), + ImmutableMap.builder().put("key", true).build()), + Arguments.of( + ExtendedAttributes.builder().put(ExtendedAttributeKey.longKey("key"), 1L).build(), + ImmutableMap.builder().put("key", 1L).build()), + Arguments.of( + ExtendedAttributes.builder().put(ExtendedAttributeKey.doubleKey("key"), 1.1).build(), + ImmutableMap.builder().put("key", 1.1).build()), + Arguments.of( + ExtendedAttributes.builder() + .put(ExtendedAttributeKey.stringArrayKey("key"), Arrays.asList("value1", "value2")) + .build(), + ImmutableMap.builder().put("key", Arrays.asList("value1", "value2")).build()), + Arguments.of( + ExtendedAttributes.builder() + .put(ExtendedAttributeKey.booleanArrayKey("key"), Arrays.asList(true, false)) + .build(), + ImmutableMap.builder().put("key", Arrays.asList(true, false)).build()), + Arguments.of( + ExtendedAttributes.builder() + .put(ExtendedAttributeKey.longArrayKey("key"), Arrays.asList(1L, 2L)) + .build(), + ImmutableMap.builder().put("key", Arrays.asList(1L, 2L)).build()), + Arguments.of( + ExtendedAttributes.builder() + .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( + ExtendedAttributeKey.extendedAttributesKey("key"), + ExtendedAttributes.builder().put("child", "value").build()) + .build(), + ImmutableMap.builder() + .put("key", ImmutableMap.builder().put("child", "value").build()) + .build()), + // Multiple entries + Arguments.of( + ExtendedAttributes.builder() + .put("key1", "value1") + .put("key2", "value2") + .put("key3", true) + .put("key4", 1L) + .put("key5", 1.1) + .put("key6", "value1", "value2") + .put("key7", true, false) + .put("key8", 1L, 2L) + .put("key9", 1.1, 2.2) + .put("key10", ExtendedAttributes.builder().put("child", "value").build()) + .build(), + ImmutableMap.builder() + .put("key1", "value1") + .put("key2", "value2") + .put("key3", true) + .put("key4", 1L) + .put("key5", 1.1) + .put("key6", Arrays.asList("value1", "value2")) + .put("key7", Arrays.asList(true, false)) + .put("key8", Arrays.asList(1L, 2L)) + .put("key9", Arrays.asList(1.1, 2.2)) + .put("key10", ImmutableMap.builder().put("child", "value").build()) + .build())); + } + + private static Map toMap(ExtendedAttributes extendedAttributes) { + Map map = new HashMap<>(); + extendedAttributes.forEach( + (key, value) -> { + if (key.getType() == ExtendedAttributeType.EXTENDED_ATTRIBUTES) { + map.put(key.getKey(), toMap((ExtendedAttributes) value)); + return; + } + map.put(key.getKey(), value); + }); + return map; + } + + private static ExtendedAttributeKey getKey(String key, Object value) { + switch (getType(value)) { + case STRING: + return ExtendedAttributeKey.stringKey(key); + case BOOLEAN: + return ExtendedAttributeKey.booleanKey(key); + case LONG: + return ExtendedAttributeKey.longKey(key); + case DOUBLE: + return ExtendedAttributeKey.doubleKey(key); + case STRING_ARRAY: + return ExtendedAttributeKey.stringArrayKey(key); + case BOOLEAN_ARRAY: + return ExtendedAttributeKey.booleanArrayKey(key); + case LONG_ARRAY: + return ExtendedAttributeKey.longArrayKey(key); + case DOUBLE_ARRAY: + return ExtendedAttributeKey.doubleArrayKey(key); + case EXTENDED_ATTRIBUTES: + return ExtendedAttributeKey.extendedAttributesKey(key); + } + throw new IllegalArgumentException(); + } + + @SuppressWarnings("unchecked") + private static ExtendedAttributeType getType(Object value) { + if (value instanceof String) { + return ExtendedAttributeType.STRING; + } + if (value instanceof Boolean) { + return ExtendedAttributeType.BOOLEAN; + } + if ((value instanceof Long) || (value instanceof Integer)) { + return ExtendedAttributeType.LONG; + } + if ((value instanceof Double) || (value instanceof Float)) { + return ExtendedAttributeType.DOUBLE; + } + if (value instanceof List) { + List list = (List) value; + if (list.isEmpty()) { + throw new IllegalArgumentException("Empty list"); + } + if (list.get(0) instanceof String) { + return ExtendedAttributeType.STRING_ARRAY; + } + if (list.get(0) instanceof Boolean) { + return ExtendedAttributeType.BOOLEAN_ARRAY; + } + if ((list.get(0) instanceof Long) || (list.get(0) instanceof Integer)) { + return ExtendedAttributeType.LONG_ARRAY; + } + if ((list.get(0) instanceof Double) || (list.get(0) instanceof Float)) { + return ExtendedAttributeType.DOUBLE_ARRAY; + } + } + if ((value instanceof Map)) { + return ExtendedAttributeType.EXTENDED_ATTRIBUTES; + } + 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 dd0fae33624..f7fdb511280 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 @@ -14,6 +14,7 @@ import io.opentelemetry.api.incubator.common.ExtendedAttributeKey; import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.internal.testing.slf4j.SuppressLogger; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; @@ -24,12 +25,14 @@ import java.util.Arrays; import java.util.List; import java.util.Random; -import java.util.function.BiConsumer; import org.junit.jupiter.api.Test; /** Demonstrating usage of extended Logs Bridge API. */ class ExtendedLogsBridgeApiUsageTest { + private static final java.util.logging.Logger logger = + java.util.logging.Logger.getLogger(ExtendedLogsBridgeApiUsageTest.class.getName()); + @Test void loggerEnabled() { // Setup SdkLoggerProvider @@ -102,7 +105,7 @@ private static String flipCoin() { ExtendedAttributeKey.extendedAttributesKey("acme.map"); @Test - @SuppressWarnings("SystemOut") + @SuppressLogger(ExtendedLogsBridgeApiUsageTest.class) void extendedAttributesUsage() { // Initialize from builder. Varargs style initialization (ExtendedAttributes.of(...) not // supported. @@ -146,14 +149,11 @@ void extendedAttributesUsage() { // acme.string(STRING): value // acme.string_array(STRING_ARRAY): [value1, value2] extendedAttributes.forEach( - new BiConsumer, Object>() { - @Override - public void accept(ExtendedAttributeKey extendedAttributeKey, Object object) { - System.out.format( - "%s(%s): %s\n", - extendedAttributeKey.getKey(), extendedAttributeKey.getType(), object); - } - }); + (extendedAttributeKey, object) -> + logger.info( + String.format( + "%s(%s): %s", + extendedAttributeKey.getKey(), extendedAttributeKey.getType(), object))); } @Test diff --git a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java index 02269fc3e2a..f6f13467887 100644 --- a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java +++ b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/TestDataExporter.java @@ -27,7 +27,7 @@ import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; +import io.opentelemetry.sdk.testing.logs.internal.TestExtendedLogRecordData; import io.opentelemetry.sdk.testing.trace.TestSpanData; import io.opentelemetry.sdk.trace.data.EventData; import io.opentelemetry.sdk.trace.data.SpanData; @@ -47,7 +47,7 @@ abstract class TestDataExporter { Resource.create(Attributes.builder().put("key", "value").build()); private static final LogRecordData LOG1 = - TestLogRecordData.builder() + TestExtendedLogRecordData.builder() .setResource(RESOURCE) .setInstrumentationScopeInfo( InstrumentationScopeInfo.builder("instrumentation") @@ -70,7 +70,7 @@ abstract class TestDataExporter { .build(); private static final LogRecordData LOG2 = - TestLogRecordData.builder() + TestExtendedLogRecordData.builder() .setResource(RESOURCE) .setInstrumentationScopeInfo( InstrumentationScopeInfo.builder("instrumentation2").setVersion("2").build()) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java index 027314ca3fd..440e472666e 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java @@ -16,7 +16,10 @@ import javax.annotation.Nullable; /** - * TODO. + * A map with a fixed capacity that drops attributes when the map gets full, and which truncates + * string and array string attribute values to the {@link #lengthLimit}. + * + *

{@link ExtendedAttributes} analog of {@link AttributesMap}. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. From f313fd838d3a631221a3c4ed9b1c6774ed1bab57 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Thu, 20 Mar 2025 15:11:31 -0500 Subject: [PATCH 5/8] Drop unbounded cache --- .../incubator/common/ExtendedAttributeKey.java | 2 +- .../InternalExtendedAttributeKeyImpl.java | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) 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 d1c62648a0d..13357a31a56 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 @@ -50,7 +50,7 @@ default AttributeKey asAttributeKey() { /** Return an ExtendedAttributeKey equivalent to the {@code attributeKey}. */ // TODO (jack-berg): remove once AttributeKey.asExtendedAttributeKey is available static ExtendedAttributeKey fromAttributeKey(AttributeKey attributeKey) { - return InternalExtendedAttributeKeyImpl.fromAttributeKey(attributeKey); + return InternalExtendedAttributeKeyImpl.toExtendedAttributeKey(attributeKey); } /** Returns a new ExtendedAttributeKey for String valued attributes. */ 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 e4aa5534724..e07f72f0121 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 @@ -11,7 +11,6 @@ import io.opentelemetry.api.incubator.common.ExtendedAttributeType; import io.opentelemetry.api.internal.InternalAttributeKeyImpl; import java.nio.charset.StandardCharsets; -import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; /** @@ -20,9 +19,6 @@ */ public final class InternalExtendedAttributeKeyImpl implements ExtendedAttributeKey { - private static final ConcurrentHashMap, ExtendedAttributeKey> - ATTRIBUTE_KEY_CACHE = new ConcurrentHashMap<>(); - private final ExtendedAttributeType type; private final String key; private final int hashCode; @@ -47,17 +43,6 @@ public static ExtendedAttributeKey create( return new InternalExtendedAttributeKeyImpl<>(type, key != null ? key : ""); } - /** - * Return an {@link ExtendedAttributeKey} equivalent to the {@code attributeKey}, caching entries - * in {@link #ATTRIBUTE_KEY_CACHE}. - */ - @SuppressWarnings("unchecked") - public static ExtendedAttributeKey fromAttributeKey(AttributeKey attributeKey) { - return (ExtendedAttributeKey) - ATTRIBUTE_KEY_CACHE.computeIfAbsent( - attributeKey, InternalExtendedAttributeKeyImpl::toExtendedAttributeKey); - } - @Override public ExtendedAttributeType getType() { return type; From 99d6cf294e35bc53e1b12d5f72e152efc4a67a43 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Tue, 25 Mar 2025 11:49:33 -0500 Subject: [PATCH 6/8] PR feedback --- .../internal/otlp/logs/LogStatelessMarshaler.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java index 868688d1c9b..aa987059646 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java @@ -55,22 +55,20 @@ public void writeTo(Serializer output, LogRecordData log, MarshalerContext conte output.serializeMessageWithContext( LogRecord.BODY, log.getBodyValue(), AnyValueStatelessMarshaler.INSTANCE, context); } + + int droppedAttributesCount; if (INCUBATOR_AVAILABLE) { IncubatingUtil.serializeExtendedAttributes(output, log, context); - - int droppedAttributesCount = - log.getTotalAttributeCount() - IncubatingUtil.extendedAttributesSize(log); - output.serializeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + droppedAttributesCount = log.getTotalAttributeCount() - IncubatingUtil.extendedAttributesSize(log); } else { output.serializeRepeatedMessageWithContext( LogRecord.ATTRIBUTES, log.getAttributes(), AttributeKeyValueStatelessMarshaler.INSTANCE, context); - - int droppedAttributesCount = log.getTotalAttributeCount() - log.getAttributes().size(); - output.serializeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); + droppedAttributesCount = log.getTotalAttributeCount() - log.getAttributes().size(); } + output.serializeUInt32(LogRecord.DROPPED_ATTRIBUTES_COUNT, droppedAttributesCount); SpanContext spanContext = log.getSpanContext(); output.serializeFixed32(LogRecord.FLAGS, spanContext.getTraceFlags().asByte()); From ff974643405a05d2a01aa99a3ce85161aba0373c Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Tue, 25 Mar 2025 13:07:02 -0500 Subject: [PATCH 7/8] spot --- .../exporter/internal/otlp/logs/LogStatelessMarshaler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java index aa987059646..331f7440a04 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogStatelessMarshaler.java @@ -59,7 +59,8 @@ public void writeTo(Serializer output, LogRecordData log, MarshalerContext conte int droppedAttributesCount; if (INCUBATOR_AVAILABLE) { IncubatingUtil.serializeExtendedAttributes(output, log, context); - droppedAttributesCount = log.getTotalAttributeCount() - IncubatingUtil.extendedAttributesSize(log); + droppedAttributesCount = + log.getTotalAttributeCount() - IncubatingUtil.extendedAttributesSize(log); } else { output.serializeRepeatedMessageWithContext( LogRecord.ATTRIBUTES, From 2ff90767e6278c7e3c2af5d82bbc73be18eed93a Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Fri, 11 Apr 2025 10:05:24 -0500 Subject: [PATCH 8/8] Add missing dependency to fix build --- exporters/otlp/common/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/exporters/otlp/common/build.gradle.kts b/exporters/otlp/common/build.gradle.kts index 6167179629a..5ad623561b2 100644 --- a/exporters/otlp/common/build.gradle.kts +++ b/exporters/otlp/common/build.gradle.kts @@ -50,6 +50,7 @@ testing { implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.google.protobuf:protobuf-java-util") + implementation("com.google.guava:guava") implementation("io.opentelemetry.proto:opentelemetry-proto") } }