Skip to content

Commit 8007b45

Browse files
authored
Merge 00ba78d into 8b0dfba
2 parents 8b0dfba + 00ba78d commit 8007b45

11 files changed

Lines changed: 203 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Report dropped spans ([#3528](https://github.com/getsentry/sentry-java/pull/3528))
8+
59
### Fixes
610

711
- Fix duplicate session start for React Native ([#3504](https://github.com/getsentry/sentry-java/pull/3504))

sentry/api/sentry.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ public final class io/sentry/DataCategory : java/lang/Enum {
229229
public static final field Profile Lio/sentry/DataCategory;
230230
public static final field Security Lio/sentry/DataCategory;
231231
public static final field Session Lio/sentry/DataCategory;
232+
public static final field Span Lio/sentry/DataCategory;
232233
public static final field Transaction Lio/sentry/DataCategory;
233234
public static final field Unknown Lio/sentry/DataCategory;
234235
public static final field UserReport Lio/sentry/DataCategory;
@@ -3142,6 +3143,7 @@ public final class io/sentry/clientreport/ClientReportRecorder : io/sentry/clien
31423143
public fun recordLostEnvelope (Lio/sentry/clientreport/DiscardReason;Lio/sentry/SentryEnvelope;)V
31433144
public fun recordLostEnvelopeItem (Lio/sentry/clientreport/DiscardReason;Lio/sentry/SentryEnvelopeItem;)V
31443145
public fun recordLostEvent (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;)V
3146+
public fun recordLostEvent (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;J)V
31453147
}
31463148

31473149
public final class io/sentry/clientreport/DiscardReason : java/lang/Enum {
@@ -3187,6 +3189,7 @@ public abstract interface class io/sentry/clientreport/IClientReportRecorder {
31873189
public abstract fun recordLostEnvelope (Lio/sentry/clientreport/DiscardReason;Lio/sentry/SentryEnvelope;)V
31883190
public abstract fun recordLostEnvelopeItem (Lio/sentry/clientreport/DiscardReason;Lio/sentry/SentryEnvelopeItem;)V
31893191
public abstract fun recordLostEvent (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;)V
3192+
public abstract fun recordLostEvent (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;J)V
31903193
}
31913194

31923195
public abstract interface class io/sentry/clientreport/IClientReportStorage {
@@ -3200,6 +3203,7 @@ public final class io/sentry/clientreport/NoOpClientReportRecorder : io/sentry/c
32003203
public fun recordLostEnvelope (Lio/sentry/clientreport/DiscardReason;Lio/sentry/SentryEnvelope;)V
32013204
public fun recordLostEnvelopeItem (Lio/sentry/clientreport/DiscardReason;Lio/sentry/SentryEnvelopeItem;)V
32023205
public fun recordLostEvent (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;)V
3206+
public fun recordLostEvent (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;J)V
32033207
}
32043208

32053209
public abstract interface class io/sentry/config/PropertiesProvider {

sentry/src/main/java/io/sentry/DataCategory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public enum DataCategory {
1414
Profile("profile"),
1515
MetricBucket("metric_bucket"),
1616
Transaction("transaction"),
17+
Span("span"),
1718
Security("security"),
1819
UserReport("user_report"),
1920
Unknown("unknown");

sentry/src/main/java/io/sentry/Hub.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,10 +686,22 @@ public void flush(long timeoutMillis) {
686686
options
687687
.getClientReportRecorder()
688688
.recordLostEvent(DiscardReason.BACKPRESSURE, DataCategory.Transaction);
689+
options
690+
.getClientReportRecorder()
691+
.recordLostEvent(
692+
DiscardReason.BACKPRESSURE,
693+
DataCategory.Span,
694+
transaction.getSpans().size() + 1);
689695
} else {
690696
options
691697
.getClientReportRecorder()
692698
.recordLostEvent(DiscardReason.SAMPLE_RATE, DataCategory.Transaction);
699+
options
700+
.getClientReportRecorder()
701+
.recordLostEvent(
702+
DiscardReason.SAMPLE_RATE,
703+
DataCategory.Span,
704+
transaction.getSpans().size() + 1);
693705
}
694706
} else {
695707
StackItem item = null;

sentry/src/main/java/io/sentry/SentryClient.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ private SentryTransaction processTransaction(
412412
final @NotNull Hint hint,
413413
final @NotNull List<EventProcessor> eventProcessors) {
414414
for (final EventProcessor processor : eventProcessors) {
415+
final int spanCountBeforeProcessor = transaction.getSpans().size();
415416
try {
416417
transaction = processor.process(transaction, hint);
417418
} catch (Throwable e) {
@@ -423,6 +424,7 @@ private SentryTransaction processTransaction(
423424
"An exception occurred while processing transaction by processor: %s",
424425
processor.getClass().getName());
425426
}
427+
final int spanCountAfterProcessor = transaction == null ? 0 : transaction.getSpans().size();
426428

427429
if (transaction == null) {
428430
options
@@ -434,7 +436,25 @@ private SentryTransaction processTransaction(
434436
options
435437
.getClientReportRecorder()
436438
.recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Transaction);
439+
// If we drop a transaction, we are also dropping all its spans (+1 for the root span)
440+
options
441+
.getClientReportRecorder()
442+
.recordLostEvent(
443+
DiscardReason.EVENT_PROCESSOR, DataCategory.Span, spanCountBeforeProcessor + 1);
437444
break;
445+
} else if (spanCountAfterProcessor < spanCountBeforeProcessor) {
446+
// If the callback removed some spans, we report it
447+
final int droppedSpanCount = spanCountBeforeProcessor - spanCountAfterProcessor;
448+
options
449+
.getLogger()
450+
.log(
451+
SentryLevel.DEBUG,
452+
"%d spans were dropped by a processor: %s",
453+
droppedSpanCount,
454+
processor.getClass().getName());
455+
options
456+
.getClientReportRecorder()
457+
.recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Span, droppedSpanCount);
438458
}
439459
}
440460
return transaction;
@@ -666,7 +686,9 @@ public void captureSession(final @NotNull Session session, final @Nullable Hint
666686
return SentryId.EMPTY_ID;
667687
}
668688

689+
final int spanCountBeforeCallback = transaction.getSpans().size();
669690
transaction = executeBeforeSendTransaction(transaction, hint);
691+
final int spanCountAfterCallback = transaction == null ? 0 : transaction.getSpans().size();
670692

671693
if (transaction == null) {
672694
options
@@ -675,7 +697,24 @@ public void captureSession(final @NotNull Session session, final @Nullable Hint
675697
options
676698
.getClientReportRecorder()
677699
.recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Transaction);
700+
// If we drop a transaction, we are also dropping all its spans (+1 for the root span)
701+
options
702+
.getClientReportRecorder()
703+
.recordLostEvent(
704+
DiscardReason.BEFORE_SEND, DataCategory.Span, spanCountBeforeCallback + 1);
678705
return SentryId.EMPTY_ID;
706+
} else if (spanCountAfterCallback < spanCountBeforeCallback) {
707+
// If the callback removed some spans, we report it
708+
final int droppedSpanCount = spanCountBeforeCallback - spanCountAfterCallback;
709+
options
710+
.getLogger()
711+
.log(
712+
SentryLevel.DEBUG,
713+
"%d spans were dropped by beforeSendTransaction.",
714+
droppedSpanCount);
715+
options
716+
.getClientReportRecorder()
717+
.recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Span, droppedSpanCount);
679718
}
680719

681720
try {

sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import io.sentry.SentryItemType;
88
import io.sentry.SentryLevel;
99
import io.sentry.SentryOptions;
10+
import io.sentry.protocol.SentrySpan;
11+
import io.sentry.protocol.SentryTransaction;
1012
import java.util.ArrayList;
1113
import java.util.Date;
1214
import java.util.List;
@@ -84,8 +86,19 @@ public void recordLostEnvelopeItem(
8486
.log(SentryLevel.ERROR, "Unable to restore counts from previous client report.");
8587
}
8688
} else {
87-
recordLostEventInternal(
88-
reason.getReason(), categoryFromItemType(itemType).getCategory(), 1L);
89+
final @NotNull DataCategory itemCategory = categoryFromItemType(itemType);
90+
if (itemCategory.equals(DataCategory.Transaction)) {
91+
final @Nullable SentryTransaction transaction =
92+
envelopeItem.getTransaction(options.getSerializer());
93+
if (transaction != null) {
94+
final @NotNull List<SentrySpan> spans = transaction.getSpans();
95+
// When a transaction is dropped, we also record its spans as dropped plus one,
96+
// since Relay extracts an additional span from the transaction.
97+
recordLostEventInternal(
98+
reason.getReason(), DataCategory.Span.getCategory(), spans.size() + 1L);
99+
}
100+
}
101+
recordLostEventInternal(reason.getReason(), itemCategory.getCategory(), 1L);
89102
}
90103
} catch (Throwable e) {
91104
options.getLogger().log(SentryLevel.ERROR, e, "Unable to record lost envelope item.");
@@ -94,8 +107,14 @@ public void recordLostEnvelopeItem(
94107

95108
@Override
96109
public void recordLostEvent(@NotNull DiscardReason reason, @NotNull DataCategory category) {
110+
recordLostEvent(reason, category, 1);
111+
}
112+
113+
@Override
114+
public void recordLostEvent(
115+
@NotNull DiscardReason reason, @NotNull DataCategory category, long count) {
97116
try {
98-
recordLostEventInternal(reason.getReason(), category.getCategory(), 1L);
117+
recordLostEventInternal(reason.getReason(), category.getCategory(), count);
99118
} catch (Throwable e) {
100119
options.getLogger().log(SentryLevel.ERROR, e, "Unable to record lost event.");
101120
}

sentry/src/main/java/io/sentry/clientreport/IClientReportRecorder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ void recordLostEnvelopeItem(
1616

1717
void recordLostEvent(@NotNull DiscardReason reason, @NotNull DataCategory category);
1818

19+
void recordLostEvent(@NotNull DiscardReason reason, @NotNull DataCategory category, long count);
20+
1921
@NotNull
2022
SentryEnvelope attachReportToEnvelope(@NotNull SentryEnvelope envelope);
2123
}

sentry/src/main/java/io/sentry/clientreport/NoOpClientReportRecorder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ public void recordLostEvent(@NotNull DiscardReason reason, @NotNull DataCategory
2525
// do nothing
2626
}
2727

28+
@Override
29+
public void recordLostEvent(
30+
@NotNull DiscardReason reason, @NotNull DataCategory category, long count) {
31+
// do nothing
32+
}
33+
2834
@Override
2935
public @NotNull SentryEnvelope attachReportToEnvelope(@NotNull SentryEnvelope envelope) {
3036
return envelope;

sentry/src/test/java/io/sentry/HubTest.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,11 +1450,16 @@ class HubTest {
14501450
sut.bindClient(mockClient)
14511451

14521452
val sentryTracer = SentryTracer(TransactionContext("name", "op", TracesSamplingDecision(false)), sut)
1453+
// Unsampled spans are not added to the transaction, so they are not recorded
1454+
sentryTracer.startChild("dropped span", "span 1").finish()
14531455
sentryTracer.finish()
14541456

14551457
assertClientReport(
14561458
options.clientReportRecorder,
1457-
listOf(DiscardedEvent(DiscardReason.SAMPLE_RATE.reason, DataCategory.Transaction.category, 1))
1459+
listOf(
1460+
DiscardedEvent(DiscardReason.SAMPLE_RATE.reason, DataCategory.Transaction.category, 1),
1461+
DiscardedEvent(DiscardReason.SAMPLE_RATE.reason, DataCategory.Span.category, 1)
1462+
)
14581463
)
14591464
}
14601465

@@ -1472,11 +1477,16 @@ class HubTest {
14721477
whenever(mockBackpressureMonitor.downsampleFactor).thenReturn(1)
14731478

14741479
val sentryTracer = SentryTracer(TransactionContext("name", "op", TracesSamplingDecision(false)), sut)
1480+
// Unsampled spans are not added to the transaction, so they are not recorded
1481+
sentryTracer.startChild("dropped span", "span 1").finish()
14751482
sentryTracer.finish()
14761483

14771484
assertClientReport(
14781485
options.clientReportRecorder,
1479-
listOf(DiscardedEvent(DiscardReason.BACKPRESSURE.reason, DataCategory.Transaction.category, 1))
1486+
listOf(
1487+
DiscardedEvent(DiscardReason.BACKPRESSURE.reason, DataCategory.Transaction.category, 1),
1488+
DiscardedEvent(DiscardReason.BACKPRESSURE.reason, DataCategory.Span.category, 1)
1489+
)
14801490
)
14811491
}
14821492
//endregion

sentry/src/test/java/io/sentry/SentryClientTest.kt

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ class SentryClientTest {
8989
init {
9090
whenever(factory.create(any(), any())).thenReturn(transport)
9191
whenever(hub.options).thenReturn(sentryOptions)
92-
sentryTracer = SentryTracer(TransactionContext("a-transaction", "op"), hub)
92+
sentryTracer = SentryTracer(TransactionContext("a-transaction", "op", TracesSamplingDecision(true)), hub)
93+
sentryTracer.startChild("a-span", "span 1").finish()
9394
}
9495

9596
var attachment = Attachment("hello".toByteArray(), "hello.txt", "text/plain", true)
@@ -807,7 +808,10 @@ class SentryClientTest {
807808

808809
assertClientReport(
809810
fixture.sentryOptions.clientReportRecorder,
810-
listOf(DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Transaction.category, 1))
811+
listOf(
812+
DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Transaction.category, 1),
813+
DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Span.category, 2)
814+
)
811815
)
812816
}
813817

@@ -893,7 +897,10 @@ class SentryClientTest {
893897

894898
assertClientReport(
895899
fixture.sentryOptions.clientReportRecorder,
896-
listOf(DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Transaction.category, 1))
900+
listOf(
901+
DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Transaction.category, 1),
902+
DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Span.category, 2)
903+
)
897904
)
898905
}
899906

@@ -909,7 +916,36 @@ class SentryClientTest {
909916

910917
assertClientReport(
911918
fixture.sentryOptions.clientReportRecorder,
912-
listOf(DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Transaction.category, 1))
919+
listOf(
920+
DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Transaction.category, 1),
921+
DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Span.category, 2)
922+
)
923+
)
924+
}
925+
926+
@Test
927+
fun `span dropped by event processor is recorded`() {
928+
fixture.sentryTracer.startChild("dropped span", "span1").finish()
929+
fixture.sentryTracer.startChild("dropped span", "span2").finish()
930+
val transaction = SentryTransaction(fixture.sentryTracer)
931+
val scope = createScope()
932+
scope.addEventProcessor(object : EventProcessor {
933+
override fun process(transaction: SentryTransaction, hint: Hint): SentryTransaction? {
934+
// we are removing span1 and a-span from the fixture
935+
transaction.spans.removeIf { it.description != "span2" }
936+
return transaction
937+
}
938+
})
939+
940+
fixture.getSut().captureTransaction(transaction, scope, null)
941+
942+
verify(fixture.transport).send(any(), anyOrNull())
943+
944+
assertClientReport(
945+
fixture.sentryOptions.clientReportRecorder,
946+
listOf(
947+
DiscardedEvent(DiscardReason.EVENT_PROCESSOR.reason, DataCategory.Span.category, 2)
948+
)
913949
)
914950
}
915951

@@ -969,7 +1005,10 @@ class SentryClientTest {
9691005

9701006
assertClientReport(
9711007
fixture.sentryOptions.clientReportRecorder,
972-
listOf(DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Transaction.category, 1))
1008+
listOf(
1009+
DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Transaction.category, 1),
1010+
DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Span.category, 2)
1011+
)
9731012
)
9741013
}
9751014

@@ -1007,7 +1046,34 @@ class SentryClientTest {
10071046

10081047
assertClientReport(
10091048
fixture.sentryOptions.clientReportRecorder,
1010-
listOf(DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Transaction.category, 1))
1049+
listOf(
1050+
DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Transaction.category, 1),
1051+
DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Span.category, 2)
1052+
)
1053+
)
1054+
}
1055+
1056+
@Test
1057+
fun `when beforeSendTransaction drops a span, dropped span is recorded`() {
1058+
fixture.sentryTracer.startChild("dropped span", "span1").finish()
1059+
fixture.sentryTracer.startChild("dropped span", "span2").finish()
1060+
fixture.sentryOptions.setBeforeSendTransaction { t: SentryTransaction, _: Any? ->
1061+
t.apply {
1062+
// we are removing span1 and a-span from the fixture
1063+
spans.removeIf { it.description != "span2" }
1064+
}
1065+
}
1066+
1067+
val transaction = SentryTransaction(fixture.sentryTracer)
1068+
fixture.getSut().captureTransaction(transaction, fixture.sentryTracer.traceContext())
1069+
1070+
verify(fixture.transport).send(any(), anyOrNull())
1071+
1072+
assertClientReport(
1073+
fixture.sentryOptions.clientReportRecorder,
1074+
listOf(
1075+
DiscardedEvent(DiscardReason.BEFORE_SEND.reason, DataCategory.Span.category, 2)
1076+
)
10111077
)
10121078
}
10131079

0 commit comments

Comments
 (0)