-
Notifications
You must be signed in to change notification settings - Fork 849
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 C) #6960
Open
trask
wants to merge
2
commits into
open-telemetry:main
Choose a base branch
from
trask:complex-attributes-option-c
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
62 changes: 62 additions & 0 deletions
62
api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttribute.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
api/all/src/main/java/io/opentelemetry/api/common/ArrayBackedComplexAttributeBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* 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(AttributeKey<T>) | ||
// or #removeIf(Predicate<AttributeKey<?>>)), 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(AttributeKey<T> key, T value) { | ||
if (key == null || key.getKey().isEmpty() || value == null) { | ||
return this; | ||
} | ||
data.add(key); | ||
data.add(value); | ||
return this; | ||
} | ||
|
||
@Override | ||
@SuppressWarnings({"unchecked", "rawtypes"}) | ||
public ComplexAttributeBuilder putAll(Attributes attributes) { | ||
if (attributes == null) { | ||
return this; | ||
} | ||
// Attributes must iterate over their entries with matching types for key / value, so this | ||
// downcast to the raw type is safe. | ||
attributes.forEach((key, value) -> put((AttributeKey) key, value)); | ||
return this; | ||
} | ||
|
||
@Override | ||
public <T> ComplexAttributeBuilder remove(AttributeKey<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<AttributeKey<?>> predicate) { | ||
if (predicate == null) { | ||
return this; | ||
} | ||
for (int i = 0; i < data.size() - 1; i += 2) { | ||
Object entry = data.get(i); | ||
if (entry instanceof AttributeKey && predicate.test((AttributeKey<?>) 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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
181 changes: 181 additions & 0 deletions
181
api/all/src/main/java/io/opentelemetry/api/common/ComplexAttribute.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
/* | ||
* 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}. | ||
*/ | ||
@SuppressWarnings("rawtypes") | ||
@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(); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that since this is so similar to
Attributes
that this should be plural:ComplexAttributes
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems more like a single complex attribute value to me, e.g.
as it's not really a collection of complex attributes, it's a collection of attributes (both standard and complex)
I could see the interface named something like
NestedAttributes
though...