Skip to content

Commit f27638a

Browse files
committed
Implement limits
1 parent 198faa2 commit f27638a

File tree

4 files changed

+150
-4
lines changed

4 files changed

+150
-4
lines changed

sdk/common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies {
1919

2020
testAnnotationProcessor("com.google.auto.value:auto-value")
2121

22+
testImplementation(project(":api:incubator")) // for ExtendedAttributesValueTest
2223
testImplementation(project(":sdk:testing"))
2324
testImplementation("com.google.guava:guava-testlib")
2425
testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")

sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import io.opentelemetry.api.common.AttributeKey;
99
import io.opentelemetry.api.common.Attributes;
1010
import io.opentelemetry.api.common.AttributesBuilder;
11+
import io.opentelemetry.api.common.KeyValue;
12+
import io.opentelemetry.api.common.Value;
13+
import io.opentelemetry.api.common.ValueType;
14+
import java.nio.ByteBuffer;
1115
import java.util.ArrayList;
1216
import java.util.List;
1317
import java.util.Map;
@@ -60,6 +64,27 @@ private static boolean isValidLength(Object value, int lengthLimit) {
6064
return allMatch((List<?>) value, entry -> isValidLength(entry, lengthLimit));
6165
} else if (value instanceof String) {
6266
return ((String) value).length() < lengthLimit;
67+
} else if (value instanceof Value) {
68+
return isValidLengthValue((Value<?>) value, lengthLimit);
69+
}
70+
return true;
71+
}
72+
73+
private static boolean isValidLengthValue(Value<?> value, int lengthLimit) {
74+
ValueType type = value.getType();
75+
if (type == ValueType.STRING) {
76+
return ((String) value.getValue()).length() < lengthLimit;
77+
} else if (type == ValueType.BYTES) {
78+
ByteBuffer buffer = (ByteBuffer) value.getValue();
79+
return buffer.remaining() <= lengthLimit;
80+
} else if (type == ValueType.ARRAY) {
81+
@SuppressWarnings("unchecked")
82+
List<Value<?>> array = (List<Value<?>>) value.getValue();
83+
return allMatch(array, element -> isValidLengthValue(element, lengthLimit));
84+
} else if (type == ValueType.KEY_VALUE_LIST) {
85+
@SuppressWarnings("unchecked")
86+
List<KeyValue> kvList = (List<KeyValue>) value.getValue();
87+
return allMatch(kvList, kv -> isValidLengthValue(kv.getValue(), lengthLimit));
6388
}
6489
return true;
6590
}
@@ -74,8 +99,19 @@ private static <T> boolean allMatch(Iterable<T> iterable, Predicate<T> predicate
7499
}
75100

76101
/**
77-
* Apply the {@code lengthLimit} to the attribute {@code value}. Strings and strings in lists
78-
* which exceed the length limit are truncated.
102+
* Apply the {@code lengthLimit} to the attribute {@code value}. Strings, byte arrays, and nested
103+
* values which exceed the length limit are truncated.
104+
*
105+
* <p>Applies to:
106+
*
107+
* <ul>
108+
* <li>String values
109+
* <li>Each string within an array of strings
110+
* <li>String values within {@link Value} objects
111+
* <li>Byte array values within {@link Value} objects
112+
* <li>Recursively, each element in an array of {@link Value}s
113+
* <li>Recursively, each value in a {@link Value} key-value list
114+
* </ul>
79115
*/
80116
public static Object applyAttributeLengthLimit(Object value, int lengthLimit) {
81117
if (lengthLimit == Integer.MAX_VALUE) {
@@ -93,6 +129,48 @@ public static Object applyAttributeLengthLimit(Object value, int lengthLimit) {
93129
String str = (String) value;
94130
return str.length() < lengthLimit ? value : str.substring(0, lengthLimit);
95131
}
132+
if (value instanceof Value) {
133+
return applyValueLengthLimit((Value<?>) value, lengthLimit);
134+
}
135+
return value;
136+
}
137+
138+
@SuppressWarnings("unchecked")
139+
private static Value<?> applyValueLengthLimit(Value<?> value, int lengthLimit) {
140+
ValueType type = value.getType();
141+
142+
if (type == ValueType.STRING) {
143+
String str = (String) value.getValue();
144+
if (str.length() <= lengthLimit) {
145+
return value;
146+
}
147+
return Value.of(str.substring(0, lengthLimit));
148+
} else if (type == ValueType.BYTES) {
149+
ByteBuffer buffer = (ByteBuffer) value.getValue();
150+
int length = buffer.remaining();
151+
if (length <= lengthLimit) {
152+
return value;
153+
}
154+
byte[] truncated = new byte[lengthLimit];
155+
buffer.get(truncated);
156+
return Value.of(truncated);
157+
} else if (type == ValueType.ARRAY) {
158+
List<Value<?>> array = (List<Value<?>>) value.getValue();
159+
List<Value<?>> result = new ArrayList<>(array.size());
160+
for (Value<?> element : array) {
161+
result.add(applyValueLengthLimit(element, lengthLimit));
162+
}
163+
return Value.of(result);
164+
} else if (type == ValueType.KEY_VALUE_LIST) {
165+
List<KeyValue> kvList = (List<KeyValue>) value.getValue();
166+
List<KeyValue> result = new ArrayList<>(kvList.size());
167+
for (KeyValue kv : kvList) {
168+
result.add(KeyValue.of(kv.getKey(), applyValueLengthLimit(kv.getValue(), lengthLimit)));
169+
}
170+
return Value.of(result.toArray(new KeyValue[0]));
171+
}
172+
173+
// For BOOLEAN, LONG, DOUBLE - no truncation needed
96174
return value;
97175
}
98176
}

sdk/common/src/main/java/io/opentelemetry/sdk/internal/ExtendedAttributesMap.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,9 @@ public Object put(ExtendedAttributeKey<?> key, @Nullable Object value) {
5656
return null;
5757
}
5858
totalAddedValues++;
59-
// TODO(jack-berg): apply capacity to nested entries
6059
if (size() >= capacity && !containsKey(key)) {
6160
return null;
6261
}
63-
// TODO(jack-berg): apply limits to nested entries
6462
return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
6563
}
6664

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.internal;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.api.common.KeyValue;
11+
import io.opentelemetry.api.common.Value;
12+
import io.opentelemetry.api.incubator.common.ExtendedAttributeKey;
13+
import java.nio.ByteBuffer;
14+
import java.util.List;
15+
import org.junit.jupiter.api.Test;
16+
17+
class ExtendedAttributesValueTest {
18+
19+
@Test
20+
void put_ByteArrayTruncation() {
21+
ExtendedAttributesMap map = ExtendedAttributesMap.create(128, 5);
22+
byte[] bytes = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
23+
Value<ByteBuffer> value = Value.of(bytes);
24+
25+
map.put(ExtendedAttributeKey.valueKey("key"), value);
26+
27+
Value<?> result = map.get(ExtendedAttributeKey.valueKey("key"));
28+
ByteBuffer buffer = (ByteBuffer) result.getValue();
29+
byte[] resultBytes = new byte[buffer.remaining()];
30+
buffer.get(resultBytes);
31+
assertThat(resultBytes).containsExactly(1, 2, 3, 4, 5);
32+
}
33+
34+
@Test
35+
void put_ValueArrayTruncation() {
36+
ExtendedAttributesMap map = ExtendedAttributesMap.create(128, 5);
37+
38+
Value<?> arrayValue = Value.of(
39+
Value.of("short"),
40+
Value.of("this is too long"));
41+
42+
map.put(ExtendedAttributeKey.valueKey("key"), arrayValue);
43+
44+
Value<?> result = map.get(ExtendedAttributeKey.valueKey("key"));
45+
@SuppressWarnings("unchecked")
46+
List<Value<?>> resultList = (List<Value<?>>) result.getValue();
47+
assertThat(resultList).hasSize(2);
48+
assertThat(resultList.get(0).getValue()).isEqualTo("short");
49+
assertThat(resultList.get(1).getValue()).isEqualTo("this ");
50+
}
51+
52+
@Test
53+
void put_ValueKeyValueListTruncation() {
54+
ExtendedAttributesMap map = ExtendedAttributesMap.create(128, 5);
55+
56+
Value<?> kvListValue = Value.of(
57+
KeyValue.of("key1", Value.of("short")),
58+
KeyValue.of("key2", Value.of("this is too long")));
59+
60+
map.put(ExtendedAttributeKey.valueKey("key"), kvListValue);
61+
62+
Value<?> result = map.get(ExtendedAttributeKey.valueKey("key"));
63+
@SuppressWarnings("unchecked")
64+
List<KeyValue> resultList = (List<KeyValue>) result.getValue();
65+
assertThat(resultList).hasSize(2);
66+
assertThat(resultList.get(0).getValue().getValue()).isEqualTo("short");
67+
assertThat(resultList.get(1).getValue().getValue()).isEqualTo("this ");
68+
}
69+
}

0 commit comments

Comments
 (0)