diff --git a/CHANGELOG.md b/CHANGELOG.md index 2783b9a3952..0d1082fa9d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,10 @@ SentryAndroid.init( - Fix missing thread stacks for ANRv1 events ([#4918](https://github.com/getsentry/sentry-java/pull/4918)) +### Internal + +- Support `span` envelope item type ([#4935](https://github.com/getsentry/sentry-java/pull/4935)) + ## 8.27.1 ### Fixes diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index dedb3b0e80f..bee8b9e343e 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3137,6 +3137,7 @@ public final class io/sentry/SentryItemType : java/lang/Enum, io/sentry/JsonSeri public static final field ReplayRecording Lio/sentry/SentryItemType; public static final field ReplayVideo Lio/sentry/SentryItemType; public static final field Session Lio/sentry/SentryItemType; + public static final field Span Lio/sentry/SentryItemType; public static final field Transaction Lio/sentry/SentryItemType; public static final field Unknown Lio/sentry/SentryItemType; public static final field UserFeedback Lio/sentry/SentryItemType; diff --git a/sentry/src/main/java/io/sentry/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java index e6c25322fd2..709c2b52e5a 100644 --- a/sentry/src/main/java/io/sentry/SentryItemType.java +++ b/sentry/src/main/java/io/sentry/SentryItemType.java @@ -23,6 +23,7 @@ public enum SentryItemType implements JsonSerializable { CheckIn("check_in"), Feedback("feedback"), Log("log"), + Span("span"), Unknown("__unknown__"); // DataCategory.Unknown private final String itemType; diff --git a/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java b/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java index 8da4360dcb2..fc793a4c744 100644 --- a/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java +++ b/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java @@ -215,6 +215,9 @@ private DataCategory categoryFromItemType(SentryItemType itemType) { if (SentryItemType.Log.equals(itemType)) { return DataCategory.LogItem; } + if (SentryItemType.Span.equals(itemType)) { + return DataCategory.Span; + } return DataCategory.Default; } diff --git a/sentry/src/main/java/io/sentry/transport/RateLimiter.java b/sentry/src/main/java/io/sentry/transport/RateLimiter.java index fc07e59c063..3139ccb1ce5 100644 --- a/sentry/src/main/java/io/sentry/transport/RateLimiter.java +++ b/sentry/src/main/java/io/sentry/transport/RateLimiter.java @@ -213,6 +213,8 @@ private boolean isRetryAfter(final @NotNull String itemType) { return Collections.singletonList(DataCategory.Feedback); case "log": return Collections.singletonList(DataCategory.LogItem); + case "span": + return Collections.singletonList(DataCategory.Span); default: return Collections.singletonList(DataCategory.Unknown); } diff --git a/sentry/src/test/java/io/sentry/protocol/SentryItemTypeSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryItemTypeSerializationTest.kt index 481c40ed81e..16eec5c5e7e 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryItemTypeSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryItemTypeSerializationTest.kt @@ -32,6 +32,7 @@ class SentryItemTypeSerializationTest { assertEquals(serialize(SentryItemType.ReplayVideo), json("replay_video")) assertEquals(serialize(SentryItemType.CheckIn), json("check_in")) assertEquals(serialize(SentryItemType.Feedback), json("feedback")) + assertEquals(serialize(SentryItemType.Span), json("span")) } @Test @@ -49,6 +50,7 @@ class SentryItemTypeSerializationTest { assertEquals(deserialize(json("replay_video")), SentryItemType.ReplayVideo) assertEquals(deserialize(json("check_in")), SentryItemType.CheckIn) assertEquals(deserialize(json("feedback")), SentryItemType.Feedback) + assertEquals(deserialize(json("span")), SentryItemType.Span) } private fun json(type: String): String = "{\"type\":\"${type}\"}" diff --git a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt index ab997a26f76..404e22949cb 100644 --- a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt +++ b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt @@ -4,10 +4,12 @@ import io.sentry.Attachment import io.sentry.CheckIn import io.sentry.CheckInStatus import io.sentry.DataCategory.Replay +import io.sentry.EnvelopeReader import io.sentry.Hint import io.sentry.ILogger import io.sentry.IScopes import io.sentry.ISerializer +import io.sentry.JsonSerializer import io.sentry.NoOpLogger import io.sentry.ProfileChunk import io.sentry.ProfilingTraceData @@ -534,6 +536,33 @@ class RateLimiterTest { verifyNoMoreInteractions(fixture.clientReportRecorder) } + @Test + fun `drop span items as lost`() { + val rateLimiter = fixture.getSUT() + + // There is no span API yet so we'll create the envelope manually using EnvelopeReader + // This mimics how hybrid SDKs would send span v2 envelope items + val spanPayload = """{"items":[]}""" + val spanItemHeader = + """{"type":"span","length":${spanPayload.length},"content_type":"application/vnd.sentry.items.span.v2+json","item_count":1}""" + val envelopeHeader = """{}""" + val rawEnvelope = "$envelopeHeader\n$spanItemHeader\n$spanPayload" + + val options = SentryOptions() + val envelopeReader = EnvelopeReader(JsonSerializer(options)) + val spanEnvelope = envelopeReader.read(rawEnvelope.byteInputStream())!! + val spanItem = spanEnvelope.items.first() + + rateLimiter.updateRetryAfterLimits("60:span:key", null, 1) + val result = rateLimiter.filter(spanEnvelope, Hint()) + + assertNull(result) + + verify(fixture.clientReportRecorder, times(1)) + .recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(spanItem)) + verifyNoMoreInteractions(fixture.clientReportRecorder) + } + @Test fun `apply rate limits notifies observers`() { val rateLimiter = fixture.getSUT()