Add attribute length limits to SpanLimits. (#3551)

* Add attribute length limits to SpanLimits.

* Respond to PR feedback

* Respond to PR feedback

* Remove test utility function

* Rename method to getMaxAttributeValueLength
This commit is contained in:
jack-berg 2021-09-09 19:49:55 -05:00 committed by GitHub
parent 47fd8814e6
commit 060e50ed5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 337 additions and 63 deletions

View File

@ -3,3 +3,11 @@ Comparing source compatibility of against
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++* NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object getAttribute(io.opentelemetry.api.common.AttributeKey)
+++ NEW ANNOTATION: javax.annotation.Nullable
***! MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.SpanLimits (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
***! MODIFIED CONSTRUCTOR: PROTECTED (<- PUBLIC) SpanLimits()
+++ NEW ANNOTATION: java.lang.Deprecated
+++ NEW METHOD: PUBLIC(+) int getMaxAttributeValueLength()
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SpanLimitsBuilder (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SpanLimitsBuilder setMaxAttributeLength(int)

View File

@ -0,0 +1,94 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.trace;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
final class AttributeUtil {
private AttributeUtil() {}
/**
* Apply the {@code countLimit} and {@code lengthLimit} to the attributes.
*
* <p>If all attributes fall within the limits, return as is. Else, return an attributes instance
* with the limits applied. {@code countLimit} limits the number of unique attribute keys. {@code
* lengthLimit} limits the length of attribute string and string list values.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
static Attributes applyAttributesLimit(
final Attributes attributes, final int countLimit, int lengthLimit) {
if (attributes.isEmpty() || attributes.size() <= countLimit) {
if (lengthLimit == Integer.MAX_VALUE) {
return attributes;
}
boolean allValidLength =
allMatch(attributes.asMap().values(), value -> isValidLength(value, lengthLimit));
if (allValidLength) {
return attributes;
}
}
AttributesBuilder result = Attributes.builder();
int i = 0;
for (Map.Entry<AttributeKey<?>, Object> entry : attributes.asMap().entrySet()) {
if (i >= countLimit) {
break;
}
result.put(
(AttributeKey) entry.getKey(), applyAttributeLengthLimit(entry.getValue(), lengthLimit));
i++;
}
return result.build();
}
private static boolean isValidLength(Object value, int lengthLimit) {
if (value instanceof List) {
return allMatch((List<?>) value, entry -> isValidLength(entry, lengthLimit));
} else if (value instanceof String) {
return ((String) value).length() < lengthLimit;
}
return true;
}
private static <T> boolean allMatch(Iterable<T> iterable, Predicate<T> predicate) {
for (T value : iterable) {
if (!predicate.test(value)) {
return false;
}
}
return true;
}
/**
* Apply the {@code lengthLimit} to the attribute {@code value}. Strings and strings in lists
* which exceed the length limit are truncated.
*/
static Object applyAttributeLengthLimit(Object value, int lengthLimit) {
if (lengthLimit == Integer.MAX_VALUE) {
return value;
}
if (value instanceof List) {
List<?> values = (List<?>) value;
List<Object> response = new ArrayList<>(values.size());
for (Object entry : values) {
response.add(applyAttributeLengthLimit(entry, lengthLimit));
}
return response;
}
if (value instanceof String) {
String str = (String) value;
return str.length() < lengthLimit ? value : str.substring(0, lengthLimit);
}
return value;
}
}

View File

@ -13,16 +13,21 @@ import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
/** A map with a fixed capacity that drops attributes when the map gets full. */
/**
* 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}.
*/
final class AttributesMap extends HashMap<AttributeKey<?>, Object> implements Attributes {
private static final long serialVersionUID = -5072696312123632376L;
private final long capacity;
private final int lengthLimit;
private int totalAddedValues = 0;
AttributesMap(long capacity) {
AttributesMap(long capacity, int lengthLimit) {
this.capacity = capacity;
this.lengthLimit = lengthLimit;
}
<T> void put(AttributeKey<T> key, T value) {
@ -30,7 +35,7 @@ final class AttributesMap extends HashMap<AttributeKey<?>, Object> implements At
if (size() >= capacity && !containsKey(key)) {
return;
}
super.put(key, value);
super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
}
int getTotalAddedValues() {

View File

@ -27,7 +27,6 @@ import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -289,7 +288,9 @@ final class RecordEventsReadableSpan implements ReadWriteSpan {
return this;
}
if (attributes == null) {
attributes = new AttributesMap(spanLimits.getMaxNumberOfAttributes());
attributes =
new AttributesMap(
spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength());
}
attributes.put(key, value);
@ -328,7 +329,10 @@ final class RecordEventsReadableSpan implements ReadWriteSpan {
EventData.create(
clock.now(),
name,
applyAttributesLimit(attributes, spanLimits.getMaxNumberOfAttributesPerEvent()),
AttributeUtil.applyAttributesLimit(
attributes,
spanLimits.getMaxNumberOfAttributesPerEvent(),
spanLimits.getMaxAttributeValueLength()),
totalAttributeCount));
return this;
}
@ -346,29 +350,14 @@ final class RecordEventsReadableSpan implements ReadWriteSpan {
EventData.create(
unit.toNanos(timestamp),
name,
applyAttributesLimit(attributes, spanLimits.getMaxNumberOfAttributesPerEvent()),
AttributeUtil.applyAttributesLimit(
attributes,
spanLimits.getMaxNumberOfAttributesPerEvent(),
spanLimits.getMaxAttributeValueLength()),
totalAttributeCount));
return this;
}
@SuppressWarnings({"unchecked", "rawtypes"})
static Attributes applyAttributesLimit(final Attributes attributes, final int limit) {
if (attributes.isEmpty() || attributes.size() <= limit) {
return attributes;
}
AttributesBuilder result = Attributes.builder();
int i = 0;
for (Map.Entry<AttributeKey<?>, Object> entry : attributes.asMap().entrySet()) {
if (i >= limit) {
break;
}
result.put((AttributeKey) entry.getKey(), entry.getValue());
i++;
}
return result.build();
}
private void addTimedEvent(EventData timedEvent) {
synchronized (lock) {
if (hasEnded) {

View File

@ -101,8 +101,10 @@ final class SdkSpanBuilder implements SpanBuilder {
addLink(
LinkData.create(
spanContext,
RecordEventsReadableSpan.applyAttributesLimit(
attributes, spanLimits.getMaxNumberOfAttributesPerLink()),
AttributeUtil.applyAttributesLimit(
attributes,
spanLimits.getMaxNumberOfAttributesPerLink(),
spanLimits.getMaxAttributeValueLength()),
totalAttributeCount));
return this;
}
@ -232,7 +234,9 @@ final class SdkSpanBuilder implements SpanBuilder {
private AttributesMap attributes() {
AttributesMap attributes = this.attributes;
if (attributes == null) {
this.attributes = new AttributesMap(spanLimits.getMaxNumberOfAttributes());
this.attributes =
new AttributesMap(
spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength());
attributes = this.attributes;
}
return attributes;

View File

@ -17,10 +17,10 @@ import javax.annotation.concurrent.Immutable;
* io.opentelemetry.sdk.trace.SdkTracerProviderBuilder#setSpanLimits(java.util.function.Supplier)}
* which supplies dynamic configs when queried.
*/
@AutoValue
@Immutable
public abstract class SpanLimits {
static final int DEFAULT_SPAN_MAX_ATTRIBUTE_LENGTH = Integer.MAX_VALUE;
private static final SpanLimits DEFAULT = new SpanLimitsBuilder().build();
/** Returns the default {@link SpanLimits}. */
@ -38,50 +38,70 @@ public abstract class SpanLimits {
int maxNumEvents,
int maxNumLinks,
int maxNumAttributesPerEvent,
int maxNumAttributesPerLink) {
return new AutoValue_SpanLimits(
int maxNumAttributesPerLink,
int maxAttributeLength) {
return new AutoValue_SpanLimits_SpanLimitsValue(
maxNumAttributes,
maxNumEvents,
maxNumLinks,
maxNumAttributesPerEvent,
maxNumAttributesPerLink);
maxNumAttributesPerLink,
maxAttributeLength);
}
/**
* Returns the global default max number of attributes per {@link Span}.
* Create an instance.
*
* @return the global default max number of attributes per {@link Span}.
* @deprecated Will be made package private in 2.0.0.
*/
@Deprecated
protected SpanLimits() {}
/**
* Returns the max number of attributes per {@link Span}.
*
* @return the max number of attributes per {@link Span}.
*/
public abstract int getMaxNumberOfAttributes();
/**
* Returns the global default max number of events per {@link Span}.
* Returns the max number of events per {@link Span}.
*
* @return the global default max number of events per {@code Span}.
* @return the max number of events per {@code Span}.
*/
public abstract int getMaxNumberOfEvents();
/**
* Returns the global default max number of links per {@link Span}.
* Returns the max number of links per {@link Span}.
*
* @return the global default max number of links per {@code Span}.
* @return the max number of links per {@code Span}.
*/
public abstract int getMaxNumberOfLinks();
/**
* Returns the global default max number of attributes per event.
* Returns the max number of attributes per event.
*
* @return the global default max number of attributes per event.
* @return the max number of attributes per event.
*/
public abstract int getMaxNumberOfAttributesPerEvent();
/**
* Returns the global default max number of attributes per link.
* Returns the max number of attributes per link.
*
* @return the global default max number of attributes per link.
* @return the max number of attributes per link.
*/
public abstract int getMaxNumberOfAttributesPerLink();
/**
* Returns the max number of characters for string attribute values. For string array attributes
* values, applies to each entry individually.
*
* @return the max number of characters for attribute strings.
*/
public int getMaxAttributeValueLength() {
return DEFAULT_SPAN_MAX_ATTRIBUTE_LENGTH;
}
/**
* Returns a {@link SpanLimitsBuilder} initialized to the same property values as the current
* instance.
@ -95,6 +115,19 @@ public abstract class SpanLimits {
.setMaxNumberOfEvents(getMaxNumberOfEvents())
.setMaxNumberOfLinks(getMaxNumberOfLinks())
.setMaxNumberOfAttributesPerEvent(getMaxNumberOfAttributesPerEvent())
.setMaxNumberOfAttributesPerLink(getMaxNumberOfAttributesPerLink());
.setMaxNumberOfAttributesPerLink(getMaxNumberOfAttributesPerLink())
.setMaxAttributeLength(getMaxAttributeValueLength());
}
@AutoValue
@Immutable
abstract static class SpanLimitsValue extends SpanLimits {
/**
* Override {@link SpanLimits#getMaxAttributeValueLength()} to be abstract so autovalue can
* implement it.
*/
@Override
public abstract int getMaxAttributeValueLength();
}
}

View File

@ -22,15 +22,16 @@ public final class SpanLimitsBuilder {
private int maxNumLinks = DEFAULT_SPAN_MAX_NUM_LINKS;
private int maxNumAttributesPerEvent = DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_EVENT;
private int maxNumAttributesPerLink = DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_LINK;
private int maxAttributeLength = SpanLimits.DEFAULT_SPAN_MAX_ATTRIBUTE_LENGTH;
SpanLimitsBuilder() {}
/**
* Sets the global default max number of attributes per {@link Span}.
* Sets the max number of attributes per {@link Span}.
*
* @param maxNumberOfAttributes the global default max number of attributes per {@link Span}. It
* must be positive otherwise {@link #build()} will throw an exception.
* @param maxNumberOfAttributes the max number of attributes per {@link Span}. Must be positive.
* @return this.
* @throws IllegalArgumentException if {@code maxNumberOfAttributes} is not positive.
*/
public SpanLimitsBuilder setMaxNumberOfAttributes(int maxNumberOfAttributes) {
Utils.checkArgument(maxNumberOfAttributes > 0, "maxNumberOfAttributes must be greater than 0");
@ -39,11 +40,11 @@ public final class SpanLimitsBuilder {
}
/**
* Sets the global default max number of events per {@link Span}.
* Sets the max number of events per {@link Span}.
*
* @param maxNumberOfEvents the global default max number of events per {@link Span}. It must be
* positive otherwise {@link #build()} will throw an exception.
* @param maxNumberOfEvents the max number of events per {@link Span}. Must be positive.
* @return this.
* @throws IllegalArgumentException if {@code maxNumberOfEvents} is not positive.
*/
public SpanLimitsBuilder setMaxNumberOfEvents(int maxNumberOfEvents) {
Utils.checkArgument(maxNumberOfEvents > 0, "maxNumberOfEvents must be greater than 0");
@ -52,11 +53,11 @@ public final class SpanLimitsBuilder {
}
/**
* Sets the global default max number of links per {@link Span}.
* Sets the max number of links per {@link Span}.
*
* @param maxNumberOfLinks the global default max number of links per {@link Span}. It must be
* positive otherwise {@link #build()} will throw an exception.
* @param maxNumberOfLinks the max number of links per {@link Span}. Must be positive.
* @return this.
* @throws IllegalArgumentException if {@code maxNumberOfLinks} is not positive.
*/
public SpanLimitsBuilder setMaxNumberOfLinks(int maxNumberOfLinks) {
Utils.checkArgument(maxNumberOfLinks > 0, "maxNumberOfLinks must be greater than 0");
@ -65,11 +66,11 @@ public final class SpanLimitsBuilder {
}
/**
* Sets the global default max number of attributes per event.
* Sets the max number of attributes per event.
*
* @param maxNumberOfAttributesPerEvent the global default max number of attributes per event. It
* must be positive otherwise {@link #build()} will throw an exception.
* @param maxNumberOfAttributesPerEvent the max number of attributes per event. Must be positive.
* @return this.
* @throws IllegalArgumentException if {@code maxNumberOfAttributesPerEvent} is not positive.
*/
public SpanLimitsBuilder setMaxNumberOfAttributesPerEvent(int maxNumberOfAttributesPerEvent) {
Utils.checkArgument(
@ -79,11 +80,11 @@ public final class SpanLimitsBuilder {
}
/**
* Sets the global default max number of attributes per link.
* Sets the max number of attributes per link.
*
* @param maxNumberOfAttributesPerLink the global default max number of attributes per link. It
* must be positive otherwise {@link #build()} will throw an exception.
* @param maxNumberOfAttributesPerLink the max number of attributes per link. Must be positive.
* @return this.
* @throws IllegalArgumentException if {@code maxNumberOfAttributesPerLink} is not positive.
*/
public SpanLimitsBuilder setMaxNumberOfAttributesPerLink(int maxNumberOfAttributesPerLink) {
Utils.checkArgument(
@ -92,6 +93,20 @@ public final class SpanLimitsBuilder {
return this;
}
/**
* Sets the max number of characters for string attribute values. For string array attributes
* values, applies to each entry individually.
*
* @param maxAttributeLength the max characters for string attribute values. Must not be negative.
* @return this.
* @throws IllegalArgumentException if {@code maxAttributeLength} is negative.
*/
public SpanLimitsBuilder setMaxAttributeLength(int maxAttributeLength) {
Utils.checkArgument(maxAttributeLength > -1, "maxAttributeLength must be non-negative");
this.maxAttributeLength = maxAttributeLength;
return this;
}
/** Builds and returns a {@link SpanLimits} with the values of this builder. */
public SpanLimits build() {
return SpanLimits.create(
@ -99,6 +114,7 @@ public final class SpanLimitsBuilder {
maxNumEvents,
maxNumLinks,
maxNumAttributesPerEvent,
maxNumAttributesPerLink);
maxNumAttributesPerLink,
maxAttributeLength);
}
}

View File

@ -15,7 +15,7 @@ class AttributesMapTest {
@Test
void asMap() {
AttributesMap attributesMap = new AttributesMap(2);
AttributesMap attributesMap = new AttributesMap(2, Integer.MAX_VALUE);
attributesMap.put(longKey("one"), 1L);
attributesMap.put(longKey("two"), 2L);

View File

@ -13,6 +13,7 @@ import static io.opentelemetry.api.common.AttributeKey.longArrayKey;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.stream.Collectors.joining;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -49,6 +50,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -687,6 +689,82 @@ class RecordEventsReadableSpanTest {
});
}
@Test
void attributeLength() {
int maxLength = 25;
RecordEventsReadableSpan span =
createTestSpan(SpanLimits.builder().setMaxAttributeLength(maxLength).build());
try {
String strVal = IntStream.range(0, maxLength).mapToObj(i -> "a").collect(joining());
String tooLongStrVal = strVal + strVal;
Attributes attributes =
Attributes.builder()
.put("string", tooLongStrVal)
.put("boolean", true)
.put("long", 1L)
.put("double", 1.0)
.put(stringArrayKey("stringArray"), Arrays.asList(strVal, tooLongStrVal))
.put(booleanArrayKey("booleanArray"), Arrays.asList(true, false))
.put(longArrayKey("longArray"), Arrays.asList(1L, 2L))
.put(doubleArrayKey("doubleArray"), Arrays.asList(1.0, 2.0))
.build();
span.setAllAttributes(attributes);
attributes = span.toSpanData().getAttributes();
assertThat(attributes.get(stringKey("string"))).isEqualTo(strVal);
assertThat(attributes.get(booleanKey("boolean"))).isEqualTo(true);
assertThat(attributes.get(longKey("long"))).isEqualTo(1L);
assertThat(attributes.get(doubleKey("double"))).isEqualTo(1.0);
assertThat(attributes.get(stringArrayKey("stringArray")))
.isEqualTo(Arrays.asList(strVal, strVal));
assertThat(attributes.get(booleanArrayKey("booleanArray")))
.isEqualTo(Arrays.asList(true, false));
assertThat(attributes.get(longArrayKey("longArray"))).isEqualTo(Arrays.asList(1L, 2L));
assertThat(attributes.get(doubleArrayKey("doubleArray"))).isEqualTo(Arrays.asList(1.0, 2.0));
} finally {
span.end();
}
}
@Test
void eventAttributeLength() {
int maxLength = 25;
RecordEventsReadableSpan span =
createTestSpan(SpanLimits.builder().setMaxAttributeLength(maxLength).build());
try {
String strVal = IntStream.range(0, maxLength).mapToObj(i -> "a").collect(joining());
String tooLongStrVal = strVal + strVal;
Attributes attributes =
Attributes.builder()
.put("string", tooLongStrVal)
.put("boolean", true)
.put("long", 1L)
.put("double", 1.0)
.put(stringArrayKey("stringArray"), Arrays.asList(strVal, tooLongStrVal))
.put(booleanArrayKey("booleanArray"), Arrays.asList(true, false))
.put(longArrayKey("longArray"), Arrays.asList(1L, 2L))
.put(doubleArrayKey("doubleArray"), Arrays.asList(1.0, 2.0))
.build();
span.setAllAttributes(attributes);
attributes = span.toSpanData().getAttributes();
assertThat(attributes.get(stringKey("string"))).isEqualTo(strVal);
assertThat(attributes.get(booleanKey("boolean"))).isEqualTo(true);
assertThat(attributes.get(longKey("long"))).isEqualTo(1L);
assertThat(attributes.get(doubleKey("double"))).isEqualTo(1.0);
assertThat(attributes.get(stringArrayKey("stringArray")))
.isEqualTo(Arrays.asList(strVal, strVal));
assertThat(attributes.get(booleanArrayKey("booleanArray")))
.isEqualTo(Arrays.asList(true, false));
assertThat(attributes.get(longArrayKey("longArray"))).isEqualTo(Arrays.asList(1L, 2L));
assertThat(attributes.get(doubleArrayKey("doubleArray"))).isEqualTo(Arrays.asList(1.0, 2.0));
} finally {
span.end();
}
}
@Test
void droppingAttributes() {
final int maxNumberOfAttributes = 8;
@ -915,8 +993,10 @@ class RecordEventsReadableSpanTest {
private RecordEventsReadableSpan createTestSpanWithAttributes(
Map<AttributeKey, Object> attributes) {
SpanLimits spanLimits = SpanLimits.getDefault();
AttributesMap attributesMap =
new AttributesMap(SpanLimits.getDefault().getMaxNumberOfAttributes());
new AttributesMap(
spanLimits.getMaxNumberOfAttributes(), spanLimits.getMaxAttributeValueLength());
attributes.forEach(attributesMap::put);
return createTestSpan(
SpanKind.INTERNAL,
@ -1033,7 +1113,7 @@ class RecordEventsReadableSpanTest {
TestClock clock = TestClock.create();
Resource resource = this.resource;
Attributes attributes = TestUtils.generateRandomAttributes();
final AttributesMap attributesWithCapacity = new AttributesMap(32);
final AttributesMap attributesWithCapacity = new AttributesMap(32, Integer.MAX_VALUE);
attributes.forEach((key, value) -> attributesWithCapacity.put((AttributeKey) key, value));
Attributes event1Attributes = TestUtils.generateRandomAttributes();
Attributes event2Attributes = TestUtils.generateRandomAttributes();

View File

@ -14,6 +14,7 @@ import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.joining;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@ -39,6 +40,7 @@ import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -152,6 +154,49 @@ class SdkSpanBuilderTest {
}
}
@Test
void linkAttributeLength() {
int maxLength = 25;
TracerProvider tracerProvider =
SdkTracerProvider.builder()
.setSpanLimits(SpanLimits.builder().setMaxAttributeLength(maxLength).build())
.build();
SpanBuilder spanBuilder = tracerProvider.get("test").spanBuilder(SPAN_NAME);
String strVal = IntStream.range(0, maxLength).mapToObj(i -> "a").collect(joining());
String tooLongStrVal = strVal + strVal;
Attributes attributes =
Attributes.builder()
.put("string", tooLongStrVal)
.put("boolean", true)
.put("long", 1L)
.put("double", 1.0)
.put(stringArrayKey("stringArray"), Arrays.asList(strVal, tooLongStrVal))
.put(booleanArrayKey("booleanArray"), Arrays.asList(true, false))
.put(longArrayKey("longArray"), Arrays.asList(1L, 2L))
.put(doubleArrayKey("doubleArray"), Arrays.asList(1.0, 2.0))
.build();
spanBuilder.addLink(sampledSpanContext, attributes);
RecordEventsReadableSpan span = (RecordEventsReadableSpan) spanBuilder.startSpan();
try {
attributes = span.toSpanData().getLinks().get(0).getAttributes();
assertThat(attributes.get(stringKey("string"))).isEqualTo(strVal);
assertThat(attributes.get(booleanKey("boolean"))).isEqualTo(true);
assertThat(attributes.get(longKey("long"))).isEqualTo(1L);
assertThat(attributes.get(doubleKey("double"))).isEqualTo(1.0);
assertThat(attributes.get(stringArrayKey("stringArray")))
.isEqualTo(Arrays.asList(strVal, strVal));
assertThat(attributes.get(booleanArrayKey("booleanArray")))
.isEqualTo(Arrays.asList(true, false));
assertThat(attributes.get(longArrayKey("longArray"))).isEqualTo(Arrays.asList(1L, 2L));
assertThat(attributes.get(doubleArrayKey("doubleArray"))).isEqualTo(Arrays.asList(1.0, 2.0));
} finally {
span.end();
}
}
@Test
void addLink_NoEffectAfterStartSpan() {
SpanBuilder spanBuilder = sdkTracer.spanBuilder(SPAN_NAME);