From b289b5a53d3ce32fd1f05d2c242ac77142e7293d Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 12 Aug 2025 17:06:11 +0200 Subject: [PATCH 1/6] log when dropping envelopes --- .../dart/lib/src/transport/rate_limiter.dart | 17 +++ .../dart/test/protocol/rate_limiter_test.dart | 122 ++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/packages/dart/lib/src/transport/rate_limiter.dart b/packages/dart/lib/src/transport/rate_limiter.dart index fa0f7a9018..18bdb5352e 100644 --- a/packages/dart/lib/src/transport/rate_limiter.dart +++ b/packages/dart/lib/src/transport/rate_limiter.dart @@ -26,6 +26,12 @@ class RateLimiter { DataCategory.fromItemType(item.header.type), ); + // Log debug when individual envelope items are dropped due to rate limiting + _options.log( + SentryLevel.debug, + 'Envelope item of type "${item.header.type}" was dropped due to rate limiting', + ); + final originalObject = item.originalObject; if (originalObject is SentryTransaction) { _options.recorder.recordLostEvent( @@ -48,9 +54,20 @@ class RateLimiter { // no reason to continue if (toSend.isEmpty) { + // Log error when entire envelope is dropped due to rate limiting + _options.log( + SentryLevel.debug, + 'All envelope items were dropped due to rate limiting. No events will be sent to Sentry.', + ); return null; } + // Log debug when some items were dropped but envelope can still be sent + _options.log( + SentryLevel.debug, + '${dropItems.length} envelope item(s) were dropped due to rate limiting, but ${toSend.length} item(s) will still be sent', + ); + return SentryEnvelope(envelope.header, toSend); } else { return envelope; diff --git a/packages/dart/test/protocol/rate_limiter_test.dart b/packages/dart/test/protocol/rate_limiter_test.dart index 9a6079810b..758e24f146 100644 --- a/packages/dart/test/protocol/rate_limiter_test.dart +++ b/packages/dart/test/protocol/rate_limiter_test.dart @@ -319,6 +319,121 @@ void main() { expect(DataCategory.fromItemType('unknown'), DataCategory.unknown); }); }); + + group('RateLimiter logging', () { + test('logs debug for dropped item and full envelope', () { + final options = defaultTestOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final logCalls = <_LogCall>[]; + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + logCalls.add(_LogCall(level, message)); + } + + options.log = mockLogger; + + final rateLimiter = RateLimiter(options); + + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); + + // Apply rate limit for error (event) + rateLimiter.updateRetryAfterLimits( + '1:error:key, 5:error:organization', null, 1); + + // Filter should drop the entire envelope + final result = rateLimiter.filter(envelope); + expect(result, isNull); + + // Expect 2 debug logs: item dropped + all items dropped + expect(logCalls.length, 2); + + final itemLog = logCalls[0]; + expect(itemLog.level, SentryLevel.debug); + expect( + itemLog.message, + contains( + 'Envelope item of type "event" was dropped due to rate limiting'), + ); + + final fullDropLog = logCalls[1]; + expect(fullDropLog.level, SentryLevel.debug); + expect( + fullDropLog.message, + contains('All envelope items were dropped due to rate limiting'), + ); + }); + + test('logs debug when some items dropped and some sent', () { + final options = defaultTestOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final logCalls = <_LogCall>[]; + void mockLogger( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + logCalls.add(_LogCall(level, message)); + } + + options.log = mockLogger; + + final rateLimiter = RateLimiter(options); + + // One event (error) and one transaction + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final transaction = fixture.getTransaction(); + final transactionItem = SentryEnvelopeItem.fromTransaction(transaction); + + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem, transactionItem], + ); + + // Apply rate limit only for errors so the transaction can still be sent + rateLimiter.updateRetryAfterLimits('60:error:key', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNotNull); + expect(result!.items.length, 1); + expect(result.items.first.header.type, 'transaction'); + + // Expect 2 debug logs: per-item drop + summary + expect(logCalls.length, 2); + + final itemLog = logCalls[0]; + expect(itemLog.level, SentryLevel.debug); + expect( + itemLog.message, + contains( + 'Envelope item of type "event" was dropped due to rate limiting'), + ); + + final summaryLog = logCalls[1]; + expect(summaryLog.level, SentryLevel.debug); + expect( + summaryLog.message, + allOf([ + contains('1 envelope item(s) were dropped due to rate limiting'), + contains('but 1 item(s) will still be sent'), + ]), + ); + }); + }); } class Fixture { @@ -348,3 +463,10 @@ class Fixture { return SentryTransaction(tracer); } } + +class _LogCall { + final SentryLevel level; + final String message; + + _LogCall(this.level, this.message); +} From a391f6f64cdb16d3e444e839287d9b981604ad86 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Tue, 12 Aug 2025 17:11:37 +0200 Subject: [PATCH 2/6] log as warning --- .../dart/lib/src/transport/rate_limiter.dart | 15 ++-------- .../dart/test/protocol/rate_limiter_test.dart | 29 +++++++------------ 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/packages/dart/lib/src/transport/rate_limiter.dart b/packages/dart/lib/src/transport/rate_limiter.dart index 18bdb5352e..2f9a4e5a35 100644 --- a/packages/dart/lib/src/transport/rate_limiter.dart +++ b/packages/dart/lib/src/transport/rate_limiter.dart @@ -25,10 +25,8 @@ class RateLimiter { DiscardReason.rateLimitBackoff, DataCategory.fromItemType(item.header.type), ); - - // Log debug when individual envelope items are dropped due to rate limiting _options.log( - SentryLevel.debug, + SentryLevel.warning, 'Envelope item of type "${item.header.type}" was dropped due to rate limiting', ); @@ -54,20 +52,13 @@ class RateLimiter { // no reason to continue if (toSend.isEmpty) { - // Log error when entire envelope is dropped due to rate limiting _options.log( - SentryLevel.debug, - 'All envelope items were dropped due to rate limiting. No events will be sent to Sentry.', + SentryLevel.warning, + 'All envelope items were dropped due to rate limiting.', ); return null; } - // Log debug when some items were dropped but envelope can still be sent - _options.log( - SentryLevel.debug, - '${dropItems.length} envelope item(s) were dropped due to rate limiting, but ${toSend.length} item(s) will still be sent', - ); - return SentryEnvelope(envelope.header, toSend); } else { return envelope; diff --git a/packages/dart/test/protocol/rate_limiter_test.dart b/packages/dart/test/protocol/rate_limiter_test.dart index 758e24f146..4091e800a2 100644 --- a/packages/dart/test/protocol/rate_limiter_test.dart +++ b/packages/dart/test/protocol/rate_limiter_test.dart @@ -321,7 +321,7 @@ void main() { }); group('RateLimiter logging', () { - test('logs debug for dropped item and full envelope', () { + test('logs warning for dropped item and full envelope', () { final options = defaultTestOptions(); options.debug = true; options.diagnosticLevel = SentryLevel.debug; @@ -355,11 +355,11 @@ void main() { final result = rateLimiter.filter(envelope); expect(result, isNull); - // Expect 2 debug logs: item dropped + all items dropped + // Expect 2 warning logs: item dropped + all items dropped expect(logCalls.length, 2); final itemLog = logCalls[0]; - expect(itemLog.level, SentryLevel.debug); + expect(itemLog.level, SentryLevel.warning); expect( itemLog.message, contains( @@ -367,14 +367,15 @@ void main() { ); final fullDropLog = logCalls[1]; - expect(fullDropLog.level, SentryLevel.debug); + expect(fullDropLog.level, SentryLevel.warning); expect( fullDropLog.message, contains('All envelope items were dropped due to rate limiting'), ); }); - test('logs debug when some items dropped and some sent', () { + test('logs warning for each dropped item only when some items are sent', + () { final options = defaultTestOptions(); options.debug = true; options.diagnosticLevel = SentryLevel.debug; @@ -412,26 +413,16 @@ void main() { expect(result!.items.length, 1); expect(result.items.first.header.type, 'transaction'); - // Expect 2 debug logs: per-item drop + summary - expect(logCalls.length, 2); + // Expect only 1 warning log: per-item drop (no summary) + expect(logCalls.length, 1); - final itemLog = logCalls[0]; - expect(itemLog.level, SentryLevel.debug); + final itemLog = logCalls.first; + expect(itemLog.level, SentryLevel.warning); expect( itemLog.message, contains( 'Envelope item of type "event" was dropped due to rate limiting'), ); - - final summaryLog = logCalls[1]; - expect(summaryLog.level, SentryLevel.debug); - expect( - summaryLog.message, - allOf([ - contains('1 envelope item(s) were dropped due to rate limiting'), - contains('but 1 item(s) will still be sent'), - ]), - ); }); }); } From 80df8cf5da3ce09c87f0eb8e778e2554b4e64adc Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Wed, 13 Aug 2025 10:23:53 +0200 Subject: [PATCH 3/6] enable debug if needed --- .../dart/lib/src/transport/rate_limiter.dart | 23 ++++++++++++++----- .../dart/test/protocol/rate_limiter_test.dart | 14 +++++++---- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/dart/lib/src/transport/rate_limiter.dart b/packages/dart/lib/src/transport/rate_limiter.dart index 2f9a4e5a35..1cba8eb1ca 100644 --- a/packages/dart/lib/src/transport/rate_limiter.dart +++ b/packages/dart/lib/src/transport/rate_limiter.dart @@ -25,9 +25,8 @@ class RateLimiter { DiscardReason.rateLimitBackoff, DataCategory.fromItemType(item.header.type), ); - _options.log( - SentryLevel.warning, - 'Envelope item of type "${item.header.type}" was dropped due to rate limiting', + _logDebugWarning( + 'Envelope item of type "${item.header.type}" was dropped due to rate limiting.', ); final originalObject = item.originalObject; @@ -52,9 +51,8 @@ class RateLimiter { // no reason to continue if (toSend.isEmpty) { - _options.log( - SentryLevel.warning, - 'All envelope items were dropped due to rate limiting.', + _logDebugWarning( + 'Envelope was dropped due to rate limiting.', ); return null; } @@ -129,4 +127,17 @@ class RateLimiter { _rateLimitedUntil[dataCategory] = date; } } + + // Enable debug mode to log warning messages + void _logDebugWarning(String message) { + var debug = _options.debug; + if (!debug) { + // Surface the log even if debug is disabled + _options.debug = true; + } + _options.log(SentryLevel.warning, message); + if (debug != _options.debug) { + _options.debug = debug; + } + } } diff --git a/packages/dart/test/protocol/rate_limiter_test.dart b/packages/dart/test/protocol/rate_limiter_test.dart index 4091e800a2..a4845f6430 100644 --- a/packages/dart/test/protocol/rate_limiter_test.dart +++ b/packages/dart/test/protocol/rate_limiter_test.dart @@ -323,8 +323,8 @@ void main() { group('RateLimiter logging', () { test('logs warning for dropped item and full envelope', () { final options = defaultTestOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; + options.debug = false; + options.diagnosticLevel = SentryLevel.warning; final logCalls = <_LogCall>[]; void mockLogger( @@ -370,15 +370,17 @@ void main() { expect(fullDropLog.level, SentryLevel.warning); expect( fullDropLog.message, - contains('All envelope items were dropped due to rate limiting'), + contains('Envelope was dropped due to rate limiting'), ); + + expect(options.debug, isFalse); }); test('logs warning for each dropped item only when some items are sent', () { final options = defaultTestOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; + options.debug = false; + options.diagnosticLevel = SentryLevel.warning; final logCalls = <_LogCall>[]; void mockLogger( @@ -423,6 +425,8 @@ void main() { contains( 'Envelope item of type "event" was dropped due to rate limiting'), ); + + expect(options.debug, isFalse); }); }); } From a59c918065f4edf14790e077a66113e6bd57afe4 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Thu, 21 Aug 2025 13:42:26 +0200 Subject: [PATCH 4/6] add cl entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 763129d137..db299dfd93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Add `DioException` response data to error breadcrumb ([#3164](https://github.com/getsentry/sentry-dart/pull/3164)) - Bumped `dio` min verion to `5.2.0` +- Log a warning when dropping envelope items ([#3165](https://github.com/getsentry/sentry-dart/pull/3165)) ### Dependencies From bb81942d73320383a312b5b5aea2a9512dbe9bc0 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Thu, 21 Aug 2025 14:13:08 +0200 Subject: [PATCH 5/6] ignore unused element --- packages/flutter/lib/src/native/cocoa/binding.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index 789070cf68..310e7ed426 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -26,6 +26,7 @@ external ffi.Pointer _SentryCocoa_wrapBlockingBlock_xtuoz7( @ffi.Native< ffi.Pointer Function( ffi.Pointer, ffi.Pointer)>() +// ignore: unused_element external ffi.Pointer _SentryCocoa_protocolTrampoline_1mbt9g9( ffi.Pointer target, ffi.Pointer arg0, From 5bc1fb0cb3469ceaa64ead4073eb6f3a08d661be Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Thu, 21 Aug 2025 14:38:55 +0200 Subject: [PATCH 6/6] bum diffMax by 25 kB --- metrics/metrics-ios.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/metrics-ios.yml b/metrics/metrics-ios.yml index 92cf114fa4..119455c8b6 100644 --- a/metrics/metrics-ios.yml +++ b/metrics/metrics-ios.yml @@ -11,4 +11,4 @@ startupTimeTest: binarySizeTest: diffMin: 1400 KiB - diffMax: 1800 KiB + diffMax: 1825 KiB