diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttribute.java b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttribute.java new file mode 100644 index 00000000000..562ebae2604 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttribute.java @@ -0,0 +1,62 @@ +/* + * 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 ArrayBackedComplexAttribute extends ImmutableKeyValuePairs, Object> + implements ComplexAttribute { + + // 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(AttributeKey::getKey); + + static final ComplexAttribute EMPTY = ComplexAttribute.builder().build(); + + private ArrayBackedComplexAttribute(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 + */ + ArrayBackedComplexAttribute(Object[] data) { + super(data); + } + + @Override + public ComplexAttributeBuilder toBuilder() { + return new ArrayBackedComplexAttributeBuilder(new ArrayList<>(data())); + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + public T get(AttributeKey key) { + return (T) super.get(key); + } + + static ComplexAttribute 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) { + AttributeKey key = (AttributeKey) data[i]; + if (key != null && key.getKey().isEmpty()) { + data[i] = null; + } + } + return new ArrayBackedComplexAttribute(data, KEY_COMPARATOR_FOR_CONSTRUCTION); + } +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttributeBuilder.java b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttributeBuilder.java new file mode 100644 index 00000000000..e07d63db5be --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttributeBuilder.java @@ -0,0 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +class ArrayBackedComplexAttributeBuilder implements ComplexAttributeBuilder { + private final List data; + + ArrayBackedComplexAttributeBuilder() { + data = new ArrayList<>(); + } + + ArrayBackedComplexAttributeBuilder(List data) { + this.data = data; + } + + @Override + public ComplexAttribute build() { + // If only one key-value pair AND the entry hasn't been set to null (by + // #remove(ComplexAttributeKey) + // or #removeIf(Predicate>)), then we can bypass sorting and filtering + if (data.size() == 2 && data.get(0) != null) { + return new ArrayBackedComplexAttribute(data.toArray()); + } + return ArrayBackedComplexAttribute.sortAndFilterToAttributes(data.toArray()); + } + + @Override + public ComplexAttributeBuilder put(ComplexAttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + data.add(key); + data.add(value); + return this; + } + + @Override + public ComplexAttributeBuilder remove(ComplexAttributeKey key) { + if (key == null || key.getKey().isEmpty()) { + return this; + } + return removeIf( + entryKey -> + key.getKey().equals(entryKey.getKey()) && key.getType().equals(entryKey.getType())); + } + + @Override + public ComplexAttributeBuilder 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 ComplexAttributeKey && predicate.test((ComplexAttributeKey) entry)) { + // null items are filtered out in ArrayBackedAttributes + data.set(i, null); + data.set(i + 1, null); + } + } + 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/ComplexAttribute.java b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttribute.java new file mode 100644 index 00000000000..57c5ebb51be --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttribute.java @@ -0,0 +1,180 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import static io.opentelemetry.api.common.ArrayBackedComplexAttribute.sortAndFilterToAttributes; + +import java.util.Map; +import java.util.function.BiConsumer; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * An immutable container for a complex attribute. + * + *

The keys are {@link AttributeKey}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 ComplexAttributeBuilder}. + */ +@Immutable +public interface ComplexAttribute { + + /** Returns the value for the given {@link AttributeKey}, or {@code null} if not found. */ + @Nullable + T get(AttributeKey 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 ComplexAttribute} as a {@link Map}. */ + Map, Object> asMap(); + + /** Returns a {@link ComplexAttribute} instance with no attributes. */ + static ComplexAttribute empty() { + return ArrayBackedComplexAttribute.EMPTY; + } + + /** Returns a {@link ComplexAttribute} instance with a single key-value pair. */ + static ComplexAttribute of(AttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return empty(); + } + return new ArrayBackedComplexAttribute(new Object[] {key, value}); + } + + /** + * Returns a {@link ComplexAttribute} instance with two key-value pairs. Order of the keys is not + * preserved. Duplicate keys will be removed. + */ + static ComplexAttribute of( + AttributeKey key1, T value1, AttributeKey key2, U value2) { + if (key1 == null || key1.getKey().isEmpty() || value1 == null) { + return of(key2, value2); + } + if (key2 == null || key2.getKey().isEmpty() || value2 == null) { + return of(key1, value1); + } + if (key1.getKey().equals(key2.getKey())) { + // last one in wins + return of(key2, value2); + } + if (key1.getKey().compareTo(key2.getKey()) > 0) { + return new ArrayBackedComplexAttribute(new Object[] {key2, value2, key1, value1}); + } + return new ArrayBackedComplexAttribute(new Object[] {key1, value1, key2, value2}); + } + + /** + * Returns a {@link ComplexAttribute} instance with three key-value pairs. Order of the keys is + * not preserved. Duplicate keys will be removed. + */ + static ComplexAttribute of( + AttributeKey key1, + T value1, + AttributeKey key2, + U value2, + AttributeKey key3, + V value3) { + return sortAndFilterToAttributes(key1, value1, key2, value2, key3, value3); + } + + /** + * Returns a {@link ComplexAttribute} instance with four key-value pairs. Order of the keys is not + * preserved. Duplicate keys will be removed. + */ + static ComplexAttribute of( + AttributeKey key1, + T value1, + AttributeKey key2, + U value2, + AttributeKey key3, + V value3, + AttributeKey key4, + W value4) { + return sortAndFilterToAttributes(key1, value1, key2, value2, key3, value3, key4, value4); + } + + /** + * Returns a {@link ComplexAttribute} instance with five key-value pairs. Order of the keys is not + * preserved. Duplicate keys will be removed. + */ + @SuppressWarnings("TooManyParameters") + static ComplexAttribute of( + AttributeKey key1, + T value1, + AttributeKey key2, + U value2, + AttributeKey key3, + V value3, + AttributeKey key4, + W value4, + AttributeKey key5, + X value5) { + return sortAndFilterToAttributes( + key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5); + } + + /** + * Returns a {@link ComplexAttribute} instance with the given key-value pairs. Order of the keys + * is not preserved. Duplicate keys will be removed. + */ + @SuppressWarnings("TooManyParameters") + static ComplexAttribute of( + AttributeKey key1, + T value1, + AttributeKey key2, + U value2, + AttributeKey key3, + V value3, + AttributeKey key4, + W value4, + AttributeKey key5, + X value5, + AttributeKey key6, + Y value6) { + return sortAndFilterToAttributes( + key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5, + key6, value6); + } + + /** + * Returns a new {@link ComplexAttributeBuilder} instance for creating arbitrary {@link + * ComplexAttribute}. + */ + static ComplexAttributeBuilder builder() { + return new ArrayBackedComplexAttributeBuilder(); + } + + /** + * Returns a new {@link ComplexAttributeBuilder} instance populated with the data of this {@link + * ComplexAttribute}. + */ + ComplexAttributeBuilder toBuilder(); +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeBuilder.java b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeBuilder.java new file mode 100644 index 00000000000..e7e6ce392e1 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeBuilder.java @@ -0,0 +1,175 @@ +/* + * 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.ComplexAttributeKey.booleanArrayKey; +import static io.opentelemetry.api.common.ComplexAttributeKey.booleanKey; +import static io.opentelemetry.api.common.ComplexAttributeKey.doubleArrayKey; +import static io.opentelemetry.api.common.ComplexAttributeKey.doubleKey; +import static io.opentelemetry.api.common.ComplexAttributeKey.longArrayKey; +import static io.opentelemetry.api.common.ComplexAttributeKey.longKey; +import static io.opentelemetry.api.common.ComplexAttributeKey.stringArrayKey; +import static io.opentelemetry.api.common.ComplexAttributeKey.stringKey; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +/** A builder of {@link ComplexAttribute} supporting an arbitrary number of key-value pairs. */ +public interface ComplexAttributeBuilder { + /** Create the {@link ComplexAttribute} from this. */ + ComplexAttribute build(); + + /** + * Puts a {@link ComplexAttributeKey} with associated value into this. + * + *

The type parameter is unused. + */ + default ComplexAttributeBuilder put(ComplexAttributeKey key, int value) { + return put(key, (long) value); + } + + /** Puts a {@link ComplexAttributeKey} with associated value into this. */ + ComplexAttributeBuilder put(ComplexAttributeKey key, T value); + + /** + * Puts a String attribute into this. + * + *

Note: It is strongly recommended to use {@link #put(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder 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(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder 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(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder 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(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder put(String key, boolean value) { + return put(booleanKey(key), value); + } + + /** + * Puts a String array attribute into this. + * + *

Note: It is strongly recommended to use {@link #put(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder 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 ComplexAttributeBuilder put(ComplexAttributeKey> 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(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder 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(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder 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(ComplexAttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @return this Builder + */ + default ComplexAttributeBuilder put(String key, boolean... value) { + if (value == null) { + return this; + } + return put(booleanArrayKey(key), toList(value)); + } + + /** + * Remove all attributes where {@link ComplexAttributeKey#getKey()} and {@link + * ComplexAttributeKey#getType()} match the {@code key}. + * + * @return this Builder + */ + ComplexAttributeBuilder remove(ComplexAttributeKey key); + + /** + * Remove all attributes that satisfy the given predicate. Errors or runtime exceptions thrown by + * the predicate are relayed to the caller. + * + * @return this Builder + */ + ComplexAttributeBuilder removeIf(Predicate> filter); +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeKey.java b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeKey.java new file mode 100644 index 00000000000..d705dd31b3d --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeKey.java @@ -0,0 +1,72 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +import io.opentelemetry.api.internal.InternalComplexAttributeKeyImpl; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * This interface provides a handle for setting the values of {@link Attributes}. 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. + * + * @param The type of value that can be set with the key. + */ +@Immutable +public interface ComplexAttributeKey { + /** Returns the underlying String representation of the key. */ + String getKey(); + + /** Returns the type of attribute for this key. Useful for building switch statements. */ + ComplexAttributeType getType(); + + /** Returns a new AttributeKey for String valued attributes. */ + static ComplexAttributeKey stringKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.STRING); + } + + /** Returns a new AttributeKey for Boolean valued attributes. */ + static ComplexAttributeKey booleanKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.BOOLEAN); + } + + /** Returns a new AttributeKey for Long valued attributes. */ + static ComplexAttributeKey longKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.LONG); + } + + /** Returns a new AttributeKey for Double valued attributes. */ + static ComplexAttributeKey doubleKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.DOUBLE); + } + + /** Returns a new AttributeKey for List<String> valued attributes. */ + static ComplexAttributeKey> stringArrayKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.STRING_ARRAY); + } + + /** Returns a new AttributeKey for List<Boolean> valued attributes. */ + static ComplexAttributeKey> booleanArrayKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.BOOLEAN_ARRAY); + } + + /** Returns a new AttributeKey for List<Long> valued attributes. */ + static ComplexAttributeKey> longArrayKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.LONG_ARRAY); + } + + /** Returns a new AttributeKey for List<Double> valued attributes. */ + static ComplexAttributeKey> doubleArrayKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.DOUBLE_ARRAY); + } + + /** Returns a new AttributeKey for Long valued attributes. */ + static ComplexAttributeKey complexKey(String key) { + return InternalComplexAttributeKeyImpl.create(key, ComplexAttributeType.COMPLEX); + } +} diff --git a/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeType.java b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeType.java new file mode 100644 index 00000000000..b87f639a8b5 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/common/ComplexAttributeType.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.common; + +/** + * An enum that represents all the possible value types for an {@code ComplexAttributeKey} and hence + * the types of values that are allowed for {@link ComplexAttributes}. + */ +public enum ComplexAttributeType { + STRING, + BOOLEAN, + LONG, + DOUBLE, + STRING_ARRAY, + BOOLEAN_ARRAY, + LONG_ARRAY, + DOUBLE_ARRAY, + COMPLEX +} diff --git a/api/all/src/main/java/io/opentelemetry/api/internal/InternalComplexAttributeKeyImpl.java b/api/all/src/main/java/io/opentelemetry/api/internal/InternalComplexAttributeKeyImpl.java new file mode 100644 index 00000000000..0a3034b7ca2 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/internal/InternalComplexAttributeKeyImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.internal; + +import io.opentelemetry.api.common.ComplexAttributeKey; +import io.opentelemetry.api.common.ComplexAttributeType; +import java.nio.charset.StandardCharsets; +import javax.annotation.Nullable; + +/** + * Default AttributeKey implementation which preencodes to UTF8 for OTLP export. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class InternalComplexAttributeKeyImpl implements ComplexAttributeKey { + + private final ComplexAttributeType type; + private final String key; + private final int hashCode; + + @Nullable private byte[] keyUtf8; + + private InternalComplexAttributeKeyImpl(ComplexAttributeType 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); + } + + // Used by auto-instrumentation agent. Check with auto-instrumentation before making changes to + // this method. + // + // In particular, do not change this return type to AttributeKeyImpl because auto-instrumentation + // hijacks this method and returns a bridged implementation of Context. + // + // Ideally auto-instrumentation would hijack the public AttributeKey.*Key() instead of this + // method, but auto-instrumentation also needs to inject its own implementation of AttributeKey + // into the class loader at the same time, which causes a problem because injecting a class into + // the class loader automatically resolves its super classes (interfaces), which in this case is + // Context, which would be the same class (interface) being instrumented at that time, + // which would lead to the JVM throwing a LinkageError "attempted duplicate interface definition" + public static ComplexAttributeKey create(@Nullable String key, ComplexAttributeType type) { + return new InternalComplexAttributeKeyImpl<>(type, key != null ? key : ""); + } + + @Override + public ComplexAttributeType getType() { + return type; + } + + @Override + public String getKey() { + return key; + } + + /** 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 InternalComplexAttributeKeyImpl) { + InternalComplexAttributeKeyImpl that = (InternalComplexAttributeKeyImpl) 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(ComplexAttributeType type, String key) { + int result = 1; + result *= 1000003; + result ^= type.hashCode(); + result *= 1000003; + result ^= key.hashCode(); + return result; + } +} 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..5a73135971b 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,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.ComplexAttribute; import io.opentelemetry.api.common.Value; import io.opentelemetry.context.Context; import java.time.Instant; @@ -85,8 +86,12 @@ default LogRecordBuilder setBody(Value body) { } /** - * Sets attributes. If the {@link LogRecordBuilder} previously contained a mapping for any of the - * keys, the old values are replaced by the specified values. + * Sets attributes to the newly created {@code LogRecord}. If the {@link LogRecordBuilder} + * previously contained a mapping for any of the keys, the old values are replaced by the + * specified values. + * + * @param attributes the attributes + * @return this. */ @SuppressWarnings("unchecked") default LogRecordBuilder setAllAttributes(Attributes attributes) { @@ -98,9 +103,111 @@ default LogRecordBuilder setAllAttributes(Attributes attributes) { return this; } - /** Sets an attribute. */ + /** + * Sets an attribute to the newly created {@code LogRecord}. If {@code LogRecordBuilder} + * 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. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @return this. + */ LogRecordBuilder setAttribute(AttributeKey key, T value); + /** + * Sets an attribute to the newly created {@code LogRecord}. If {@code LogRecordBuilder} + * previously contained a mapping for the key, the old value is replaced by the specified value. + * + *

If a null or empty String {@code value} is passed in, the behavior is undefined, and hence + * strongly discouraged. + * + *

Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @return this. + */ + default LogRecordBuilder setAttribute(String key, String value) { + return setAttribute(AttributeKey.stringKey(key), value); + } + + /** + * Sets an attribute to the newly created {@code LogRecord}. If {@code LogRecordBuilder} + * previously contained a mapping for the key, the old value is replaced by the specified value. + * + *

Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @return this. + */ + default LogRecordBuilder setAttribute(String key, long value) { + return setAttribute(AttributeKey.longKey(key), value); + } + + /** + * Sets an attribute to the newly created {@code LogRecord}. If {@code LogRecordBuilder} + * previously contained a mapping for the key, the old value is replaced by the specified value. + * + *

Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @return this. + */ + default LogRecordBuilder setAttribute(String key, double value) { + return setAttribute(AttributeKey.doubleKey(key), value); + } + + /** + * Sets an attribute to the newly created {@code LogRecord}. If {@code LogRecordBuilder} + * previously contained a mapping for the key, the old value is replaced by the specified value. + * + *

Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @return this. + */ + default LogRecordBuilder setAttribute(String key, boolean value) { + return setAttribute(AttributeKey.booleanKey(key), value); + } + + /** + * Sets an attribute to the newly created {@code LogRecord}. If {@code LogRecordBuilder} + * previously contained a mapping for the key, the old value is replaced by the specified value. + * + *

Note: It is strongly recommended to use {@link #setAttribute(AttributeKey, Object)}, and + * pre-allocate your keys, if possible. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @return this. + */ + default LogRecordBuilder setAttribute(AttributeKey key, int value) { + return setAttribute(key, (long) value); + } + + /** + * Sets an attribute to the newly created {@code LogRecord}. If {@code LogRecordBuilder} + * previously contained a mapping for the key, the old value is replaced by the specified value. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @return this. + */ + // TODO (trask) how to use pre-encoded key? + // - is ComplexAttributeKey too broad (covers all nested attribute types)? + default LogRecordBuilder setAttribute(String key, ComplexAttribute value) { + // default implementation is no-op + return this; + } + /** Emit the log record. */ void emit(); } 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..f43622cd654 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.ComplexAttribute; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.common.ValueType; import io.opentelemetry.api.logs.Severity; @@ -13,6 +14,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.LogLimits; import io.opentelemetry.sdk.resources.Resource; +import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -74,6 +76,11 @@ default Value getBodyValue() { /** Returns the attributes for this log, or {@link Attributes#empty()} if unset. */ Attributes getAttributes(); + /** Returns the attributes for this log, or {@link Attributes#empty()} if unset. */ + // TODO (trask) + // - is ComplexAttributes too broad (covers all nested attribute types)? + Map getComplexAttributes(); + /** * Returns the total number of attributes that were recorded on this log. *