Fix bug preventing accurate reporting of dropped attribute count (#7142)

This commit is contained in:
jack-berg 2025-02-26 16:26:59 -06:00 committed by GitHub
parent 31f484f39f
commit 3d3bff5bda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 36 additions and 5 deletions

View File

@ -18,6 +18,14 @@ import javax.annotation.Nullable;
* 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}.
*
* <p>WARNING: In order to reduce memory allocation, this class extends {@link HashMap} when it
* would be more appropriate to delegate. The problem with extending is that we don't enforce that
* all {@link HashMap} methods for reading / writing data conform to the configured attribute
* limits. Therefore, it's easy to accidentally call something like {@link Map#putAll(Map)} or
* {@link Map#put(Object, Object)} and bypass the restrictions (see <a
* href="https://github.com/open-telemetry/opentelemetry-java/issues/7135">#7135</a>). Callers MUST
* take care to only call methods from {@link AttributesMap}, and not {@link HashMap}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
@ -44,13 +52,18 @@ public final class AttributesMap extends HashMap<AttributeKey<?>, Object> implem
return new AttributesMap(capacity, lengthLimit);
}
/** Add the attribute key value pair, applying capacity and length limits. */
public <T> void put(AttributeKey<T> key, T value) {
/**
* Add the attribute key value pair, applying capacity and length limits. Callers MUST ensure the
* {@code value} type matches the type required by {@code key}.
*/
@Override
@Nullable
public Object put(AttributeKey<?> key, Object value) {
totalAddedValues++;
if (size() >= capacity && !containsKey(key)) {
return;
return null;
}
super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit));
}
/** Get the total number of attributes added, including those dropped for capcity limits. */

View File

@ -466,6 +466,7 @@ final class SdkSpan implements ReadWriteSpan {
}
@Override
@SuppressWarnings("unchecked")
public ReadWriteSpan recordException(Throwable exception, Attributes additionalAttributes) {
if (exception == null) {
return this;

View File

@ -1277,6 +1277,21 @@ class SdkSpanTest {
});
}
@Test
void recordException_SpanLimits() {
SdkSpan span = createTestSpan(SpanLimits.builder().setMaxNumberOfAttributes(2).build());
span.recordException(
new IllegalStateException("error"),
Attributes.builder().put("key1", "value").put("key2", "value").build());
List<EventData> events = span.toSpanData().getEvents();
assertThat(events.size()).isEqualTo(1);
EventData event = events.get(0);
assertThat(event.getAttributes().size()).isEqualTo(2);
assertThat(event.getTotalAttributeCount()).isEqualTo(5);
assertThat(event.getTotalAttributeCount() - event.getAttributes().size()).isPositive();
}
@Test
void badArgsIgnored() {
SdkSpan span = createTestRootSpan();
@ -1519,7 +1534,9 @@ class SdkSpanTest {
Resource resource = this.resource;
Attributes attributes = TestUtils.generateRandomAttributes();
AttributesMap attributesWithCapacity = AttributesMap.create(32, Integer.MAX_VALUE);
attributes.forEach(attributesWithCapacity::put);
attributes.forEach(
(attributeKey, object) ->
attributesWithCapacity.put((AttributeKey<Object>) attributeKey, object));
Attributes event1Attributes = TestUtils.generateRandomAttributes();
Attributes event2Attributes = TestUtils.generateRandomAttributes();
SpanContext context =