Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Prototype] How to support complex attributes in logs/events? (Option D) #6973

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<AttributeKey<?>, 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<AttributeKey<?>> KEY_COMPARATOR_FOR_CONSTRUCTION =
Comparator.comparing(AttributeKey::getKey);

static final ComplexAttribute EMPTY = ComplexAttribute.builder().build();

private ArrayBackedComplexAttribute(Object[] data, Comparator<AttributeKey<?>> 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> T get(AttributeKey<T> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Object> data;

ArrayBackedComplexAttributeBuilder() {
data = new ArrayList<>();
}

ArrayBackedComplexAttributeBuilder(List<Object> 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<T>)
// or #removeIf(Predicate<ComplexAttributeKey<?>>)), 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 <T> ComplexAttributeBuilder put(ComplexAttributeKey<T> key, T value) {
if (key == null || key.getKey().isEmpty() || value == null) {
return this;
}
data.add(key);
data.add(value);
return this;
}

@Override
public <T> ComplexAttributeBuilder remove(ComplexAttributeKey<T> 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<ComplexAttributeKey<?>> 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<Double> 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<Long> 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<Boolean> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>The keys are {@link AttributeKey}s and the values are Object instances that match the type of
* the provided key.
*
* <p>Null keys will be silently dropped.
*
* <p>Note: The behavior of null-valued attributes is undefined, and hence strongly discouraged.
*
* <p>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.
*
* <p>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> T get(AttributeKey<T> key);

/** Iterates over all the key-value pairs of attributes contained by this instance. */
void forEach(BiConsumer<? super AttributeKey<?>, ? 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<AttributeKey<?>, 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 <T> ComplexAttribute of(AttributeKey<T> 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 <T, U> ComplexAttribute of(
AttributeKey<T> key1, T value1, AttributeKey<U> 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 <T, U, V> ComplexAttribute of(
AttributeKey<T> key1,
T value1,
AttributeKey<U> key2,
U value2,
AttributeKey<V> 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 <T, U, V, W> ComplexAttribute of(
AttributeKey<T> key1,
T value1,
AttributeKey<U> key2,
U value2,
AttributeKey<V> key3,
V value3,
AttributeKey<W> 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 <T, U, V, W, X> ComplexAttribute of(
AttributeKey<T> key1,
T value1,
AttributeKey<U> key2,
U value2,
AttributeKey<V> key3,
V value3,
AttributeKey<W> key4,
W value4,
AttributeKey<X> 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 <T, U, V, W, X, Y> ComplexAttribute of(
AttributeKey<T> key1,
T value1,
AttributeKey<U> key2,
U value2,
AttributeKey<V> key3,
V value3,
AttributeKey<W> key4,
W value4,
AttributeKey<X> key5,
X value5,
AttributeKey<Y> 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();
}
Loading
Loading