Add remove method to AttributesBuilder (#3771)
* Add remove method to AttributesBuilder * Improve test coverage * Remove attributes by nulling out instead of removing from list * feedback wip * Add AttributesBuilder#remove(String) method * Add removeIf() method * AbstractFixedSizeExemplarReservoir uses more efficient removeIf() attribute filter
This commit is contained in:
parent
1e073fcff2
commit
5f259ee4ee
|
@ -2,6 +2,11 @@
|
|||
|
||||
## Version 1.8.0 (unreleased):
|
||||
|
||||
### API
|
||||
|
||||
- New `AttributesBuilder#remove(String)` and `AttributeBuilder#removeIf(Predicate<AttributeKey<?>>)`
|
||||
methods improve ergonomics of modifying attributes.
|
||||
|
||||
### Auto-configuration (alpha)
|
||||
|
||||
- BREAKING CHANGE: Remove deprecated `otel.experimental.exporter.otlp.protocol`,
|
||||
|
|
|
@ -8,6 +8,7 @@ package io.opentelemetry.api.common;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
class ArrayBackedAttributesBuilder implements AttributesBuilder {
|
||||
private final List<Object> data;
|
||||
|
@ -22,7 +23,9 @@ class ArrayBackedAttributesBuilder implements AttributesBuilder {
|
|||
|
||||
@Override
|
||||
public Attributes build() {
|
||||
if (data.size() == 2) {
|
||||
// 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 ArrayBackedAttributes(data.toArray());
|
||||
}
|
||||
return ArrayBackedAttributes.sortAndFilterToAttributes(data.toArray());
|
||||
|
@ -55,6 +58,32 @@ class ArrayBackedAttributesBuilder implements AttributesBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> AttributesBuilder 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 AttributesBuilder 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++) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
|
|||
import static io.opentelemetry.api.common.AttributeKey.stringKey;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** A builder of {@link Attributes} supporting an arbitrary number of key-value pairs. */
|
||||
public interface AttributesBuilder {
|
||||
|
@ -150,4 +151,26 @@ public interface AttributesBuilder {
|
|||
* @return this Builder
|
||||
*/
|
||||
AttributesBuilder putAll(Attributes attributes);
|
||||
|
||||
/**
|
||||
* Remove all attributes where {@link AttributeKey#getKey()} and {@link AttributeKey#getType()}
|
||||
* match the {@code key}.
|
||||
*
|
||||
* @return this Builder
|
||||
*/
|
||||
default <T> AttributesBuilder remove(AttributeKey<T> key) {
|
||||
// default implementation is no-op
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all attributes that satisfy the given predicate. Errors or runtime exceptions thrown by
|
||||
* the predicate are relayed to the caller.
|
||||
*
|
||||
* @return this Builder
|
||||
*/
|
||||
default AttributesBuilder removeIf(Predicate<AttributeKey<?>> filter) {
|
||||
// default implementation is no-op
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
|
|||
import static io.opentelemetry.api.common.AttributeKey.stringKey;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
|
@ -420,4 +421,115 @@ class AttributesTest {
|
|||
assertThat(attributes.get(stringKey("animal"))).isEqualTo("cat");
|
||||
assertThat(attributes.get(longKey("animal"))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove() {
|
||||
AttributesBuilder builder = Attributes.builder();
|
||||
assertThat(builder.remove(stringKey(""))).isEqualTo(builder);
|
||||
|
||||
Attributes attributes = Attributes.builder().remove(stringKey("key1")).build();
|
||||
assertThat(attributes).isEqualTo(Attributes.builder().build());
|
||||
|
||||
attributes =
|
||||
Attributes.builder().put("key1", "value1").build().toBuilder()
|
||||
.remove(stringKey("key1"))
|
||||
.remove(stringKey("key1"))
|
||||
.build();
|
||||
assertThat(attributes).isEqualTo(Attributes.builder().build());
|
||||
|
||||
attributes =
|
||||
Attributes.builder()
|
||||
.put("key1", "value1")
|
||||
.put("key1", "value2")
|
||||
.put("key2", "value2")
|
||||
.put("key3", "value3")
|
||||
.remove(stringKey("key1"))
|
||||
.build();
|
||||
assertThat(attributes)
|
||||
.isEqualTo(Attributes.builder().put("key2", "value2").put("key3", "value3").build());
|
||||
|
||||
attributes =
|
||||
Attributes.builder()
|
||||
.put("key1", "value1")
|
||||
.put("key1", true)
|
||||
.remove(stringKey("key1"))
|
||||
.remove(stringKey("key1"))
|
||||
.build();
|
||||
assertThat(attributes).isEqualTo(Attributes.builder().put("key1", true).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeIf() {
|
||||
AttributesBuilder builder = Attributes.builder();
|
||||
assertThat(builder.removeIf(unused -> true)).isEqualTo(builder);
|
||||
|
||||
Attributes attributes =
|
||||
Attributes.builder().removeIf(key -> key.getKey().equals("key1")).build();
|
||||
assertThat(attributes).isEqualTo(Attributes.builder().build());
|
||||
|
||||
attributes =
|
||||
Attributes.builder().put("key1", "value1").build().toBuilder()
|
||||
.removeIf(key -> key.getKey().equals("key1"))
|
||||
.removeIf(key -> key.getKey().equals("key1"))
|
||||
.build();
|
||||
assertThat(attributes).isEqualTo(Attributes.builder().build());
|
||||
|
||||
attributes =
|
||||
Attributes.builder()
|
||||
.put("key1", "value1")
|
||||
.put("key1", "value2")
|
||||
.put("key2", "value2")
|
||||
.put("key3", "value3")
|
||||
.removeIf(key -> key.getKey().equals("key1"))
|
||||
.build();
|
||||
assertThat(attributes)
|
||||
.isEqualTo(Attributes.builder().put("key2", "value2").put("key3", "value3").build());
|
||||
|
||||
attributes =
|
||||
Attributes.builder()
|
||||
.put("key1", "value1A")
|
||||
.put("key1", true)
|
||||
.removeIf(
|
||||
key -> key.getKey().equals("key1") && key.getType().equals(AttributeType.STRING))
|
||||
.build();
|
||||
assertThat(attributes).isEqualTo(Attributes.builder().put("key1", true).build());
|
||||
|
||||
attributes =
|
||||
Attributes.builder()
|
||||
.put("key1", "value1")
|
||||
.put("key2", "value2")
|
||||
.put("foo", "bar")
|
||||
.removeIf(key -> key.getKey().matches("key.*"))
|
||||
.build();
|
||||
assertThat(attributes).isEqualTo(Attributes.builder().put("foo", "bar").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove_defaultImplementationDoesNotThrow() {
|
||||
AttributesBuilder myAttributesBuilder =
|
||||
new AttributesBuilder() {
|
||||
@Override
|
||||
public Attributes build() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> AttributesBuilder put(AttributeKey<Long> key, int value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> AttributesBuilder put(AttributeKey<T> key, T value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributesBuilder putAll(Attributes attributes) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
assertThatCode(() -> myAttributesBuilder.remove(stringKey("foo"))).doesNotThrowAnyException();
|
||||
assertThatCode(() -> myAttributesBuilder.removeIf(unused -> false)).doesNotThrowAnyException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
Comparing source compatibility of against
|
||||
No changes.
|
||||
***! MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.common.AttributesBuilder (not serializable)
|
||||
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
|
||||
+++! NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributesBuilder remove(io.opentelemetry.api.common.AttributeKey)
|
||||
+++! NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.AttributesBuilder removeIf(java.util.function.Predicate)
|
||||
|
|
|
@ -7,7 +7,6 @@ package io.opentelemetry.sdk.metrics.exemplar;
|
|||
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.api.common.AttributesBuilder;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.sdk.common.Clock;
|
||||
|
@ -134,19 +133,11 @@ abstract class AbstractFixedSizeExemplarReservoir implements ExemplarReservoir {
|
|||
}
|
||||
|
||||
/** Returns filtered attributes for exemplars. */
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Attributes filtered(Attributes original, Attributes metricPoint) {
|
||||
if (metricPoint.isEmpty()) {
|
||||
return original;
|
||||
}
|
||||
AttributesBuilder result = Attributes.builder();
|
||||
Set<AttributeKey<?>> keys = metricPoint.asMap().keySet();
|
||||
original.forEach(
|
||||
(k, v) -> {
|
||||
if (!keys.contains(k)) {
|
||||
result.<Object>put((AttributeKey<Object>) k, v);
|
||||
}
|
||||
});
|
||||
return result.build();
|
||||
Set<AttributeKey<?>> metricPointKeys = metricPoint.asMap().keySet();
|
||||
return original.toBuilder().removeIf(metricPointKeys::contains).build();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue