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 * 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}. * 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 * <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time. * at any time.
*/ */
@ -44,13 +52,18 @@ public final class AttributesMap extends HashMap<AttributeKey<?>, Object> implem
return new AttributesMap(capacity, lengthLimit); 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++; totalAddedValues++;
if (size() >= capacity && !containsKey(key)) { 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. */ /** 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 @Override
@SuppressWarnings("unchecked")
public ReadWriteSpan recordException(Throwable exception, Attributes additionalAttributes) { public ReadWriteSpan recordException(Throwable exception, Attributes additionalAttributes) {
if (exception == null) { if (exception == null) {
return this; 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 @Test
void badArgsIgnored() { void badArgsIgnored() {
SdkSpan span = createTestRootSpan(); SdkSpan span = createTestRootSpan();
@ -1519,7 +1534,9 @@ class SdkSpanTest {
Resource resource = this.resource; Resource resource = this.resource;
Attributes attributes = TestUtils.generateRandomAttributes(); Attributes attributes = TestUtils.generateRandomAttributes();
AttributesMap attributesWithCapacity = AttributesMap.create(32, Integer.MAX_VALUE); 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 event1Attributes = TestUtils.generateRandomAttributes();
Attributes event2Attributes = TestUtils.generateRandomAttributes(); Attributes event2Attributes = TestUtils.generateRandomAttributes();
SpanContext context = SpanContext context =