diff --git a/CHANGELOG.md b/CHANGELOG.md index 92cfd115361..a00df9a2904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - Name the device-info caching thread `SentryDeviceInfoCache` so all threads spawned by the SDK are identifiable ([#5684](https://github.com/getsentry/sentry-java/pull/5684)) +- Apply byte-category rate limits to log and trace metric envelope items ([#5716](https://github.com/getsentry/sentry-java/pull/5716)) ## 8.47.0 diff --git a/sentry/src/main/java/io/sentry/transport/RateLimiter.java b/sentry/src/main/java/io/sentry/transport/RateLimiter.java index 549a794db90..a0cd96abba9 100644 --- a/sentry/src/main/java/io/sentry/transport/RateLimiter.java +++ b/sentry/src/main/java/io/sentry/transport/RateLimiter.java @@ -212,11 +212,11 @@ private boolean isRetryAfter(final @NotNull String itemType) { case "feedback": return Collections.singletonList(DataCategory.Feedback); case "log": - return Collections.singletonList(DataCategory.LogItem); + return Arrays.asList(DataCategory.LogItem, DataCategory.LogByte); case "span": return Collections.singletonList(DataCategory.Span); case "trace_metric": - return Collections.singletonList(DataCategory.TraceMetric); + return Arrays.asList(DataCategory.TraceMetric, DataCategory.TraceMetricByte); default: return Collections.singletonList(DataCategory.Unknown); } diff --git a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt index b3a20ddde76..33cda17106f 100644 --- a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt +++ b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt @@ -457,6 +457,31 @@ class RateLimiterTest { verifyNoMoreInteractions(fixture.clientReportRecorder) } + @Test + fun `drop log items as lost when log byte is rate limited`() { + val rateLimiter = fixture.getSUT() + val scopes = mock() + whenever(scopes.options).thenReturn(SentryOptions()) + + val logEventItem = + SentryEnvelopeItem.fromLogs( + fixture.serializer, + SentryLogEvents( + listOf(SentryLogEvent(SentryId(), SentryLongDate(0), "hello", SentryLogLevel.INFO)) + ), + ) + val envelope = SentryEnvelope(SentryEnvelopeHeader(null), arrayListOf(logEventItem)) + + rateLimiter.updateRetryAfterLimits("60:log_byte:key", null, 1) + val result = rateLimiter.filter(envelope, Hint()) + + assertNull(result) + + verify(fixture.clientReportRecorder, times(1)) + .recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(logEventItem)) + verifyNoMoreInteractions(fixture.clientReportRecorder) + } + @Test fun `drop profileChunkUi items as lost`() { val rateLimiter = fixture.getSUT() @@ -590,6 +615,33 @@ class RateLimiterTest { verifyNoMoreInteractions(fixture.clientReportRecorder) } + @Test + fun `drop trace metric items as lost when trace metric byte is rate limited`() { + 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 trace_metric envelope items + val spanPayload = """{"items":[]}""" + val metricItemHeader = + """{"type":"trace_metric","length":${spanPayload.length},"content_type":"application/vnd.sentry.items.trace-metric+json","item_count":1}""" + val envelopeHeader = """{}""" + val rawEnvelope = "$envelopeHeader\n$metricItemHeader\n$spanPayload" + + val options = SentryOptions() + val envelopeReader = EnvelopeReader(JsonSerializer(options)) + val metricEnvelope = envelopeReader.read(rawEnvelope.byteInputStream())!! + val metricItem = metricEnvelope.items.first() + + rateLimiter.updateRetryAfterLimits("60:trace_metric_byte:key", null, 1) + val result = rateLimiter.filter(metricEnvelope, Hint()) + + assertNull(result) + + verify(fixture.clientReportRecorder, times(1)) + .recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(metricItem)) + verifyNoMoreInteractions(fixture.clientReportRecorder) + } + @Test fun `apply rate limits notifies observers`() { val rateLimiter = fixture.getSUT()