Skip to content

Commit 4298701

Browse files
authored
Log a warning when dropping envelope items (#3165)
1 parent 7cfee3b commit 4298701

File tree

5 files changed

+139
-1
lines changed

5 files changed

+139
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
- Add `DioException` response data to error breadcrumb ([#3164](https://github.com/getsentry/sentry-dart/pull/3164))
1818
- Bumped `dio` min verion to `5.2.0`
19+
- Log a warning when dropping envelope items ([#3165](https://github.com/getsentry/sentry-dart/pull/3165))
1920

2021
### Dependencies
2122

metrics/metrics-ios.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ startupTimeTest:
1111

1212
binarySizeTest:
1313
diffMin: 1400 KiB
14-
diffMax: 1800 KiB
14+
diffMax: 1825 KiB

packages/dart/lib/src/transport/rate_limiter.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class RateLimiter {
2525
DiscardReason.rateLimitBackoff,
2626
DataCategory.fromItemType(item.header.type),
2727
);
28+
_logDebugWarning(
29+
'Envelope item of type "${item.header.type}" was dropped due to rate limiting.',
30+
);
2831

2932
final originalObject = item.originalObject;
3033
if (originalObject is SentryTransaction) {
@@ -48,6 +51,9 @@ class RateLimiter {
4851

4952
// no reason to continue
5053
if (toSend.isEmpty) {
54+
_logDebugWarning(
55+
'Envelope was dropped due to rate limiting.',
56+
);
5157
return null;
5258
}
5359

@@ -121,4 +127,17 @@ class RateLimiter {
121127
_rateLimitedUntil[dataCategory] = date;
122128
}
123129
}
130+
131+
// Enable debug mode to log warning messages
132+
void _logDebugWarning(String message) {
133+
var debug = _options.debug;
134+
if (!debug) {
135+
// Surface the log even if debug is disabled
136+
_options.debug = true;
137+
}
138+
_options.log(SentryLevel.warning, message);
139+
if (debug != _options.debug) {
140+
_options.debug = debug;
141+
}
142+
}
124143
}

packages/dart/test/protocol/rate_limiter_test.dart

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,116 @@ void main() {
319319
expect(DataCategory.fromItemType('unknown'), DataCategory.unknown);
320320
});
321321
});
322+
323+
group('RateLimiter logging', () {
324+
test('logs warning for dropped item and full envelope', () {
325+
final options = defaultTestOptions();
326+
options.debug = false;
327+
options.diagnosticLevel = SentryLevel.warning;
328+
329+
final logCalls = <_LogCall>[];
330+
void mockLogger(
331+
SentryLevel level,
332+
String message, {
333+
String? logger,
334+
Object? exception,
335+
StackTrace? stackTrace,
336+
}) {
337+
logCalls.add(_LogCall(level, message));
338+
}
339+
340+
options.log = mockLogger;
341+
342+
final rateLimiter = RateLimiter(options);
343+
344+
final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent());
345+
final envelope = SentryEnvelope(
346+
SentryEnvelopeHeader.newEventId(),
347+
[eventItem],
348+
);
349+
350+
// Apply rate limit for error (event)
351+
rateLimiter.updateRetryAfterLimits(
352+
'1:error:key, 5:error:organization', null, 1);
353+
354+
// Filter should drop the entire envelope
355+
final result = rateLimiter.filter(envelope);
356+
expect(result, isNull);
357+
358+
// Expect 2 warning logs: item dropped + all items dropped
359+
expect(logCalls.length, 2);
360+
361+
final itemLog = logCalls[0];
362+
expect(itemLog.level, SentryLevel.warning);
363+
expect(
364+
itemLog.message,
365+
contains(
366+
'Envelope item of type "event" was dropped due to rate limiting'),
367+
);
368+
369+
final fullDropLog = logCalls[1];
370+
expect(fullDropLog.level, SentryLevel.warning);
371+
expect(
372+
fullDropLog.message,
373+
contains('Envelope was dropped due to rate limiting'),
374+
);
375+
376+
expect(options.debug, isFalse);
377+
});
378+
379+
test('logs warning for each dropped item only when some items are sent',
380+
() {
381+
final options = defaultTestOptions();
382+
options.debug = false;
383+
options.diagnosticLevel = SentryLevel.warning;
384+
385+
final logCalls = <_LogCall>[];
386+
void mockLogger(
387+
SentryLevel level,
388+
String message, {
389+
String? logger,
390+
Object? exception,
391+
StackTrace? stackTrace,
392+
}) {
393+
logCalls.add(_LogCall(level, message));
394+
}
395+
396+
options.log = mockLogger;
397+
398+
final rateLimiter = RateLimiter(options);
399+
400+
// One event (error) and one transaction
401+
final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent());
402+
final transaction = fixture.getTransaction();
403+
final transactionItem = SentryEnvelopeItem.fromTransaction(transaction);
404+
405+
final envelope = SentryEnvelope(
406+
SentryEnvelopeHeader.newEventId(),
407+
[eventItem, transactionItem],
408+
);
409+
410+
// Apply rate limit only for errors so the transaction can still be sent
411+
rateLimiter.updateRetryAfterLimits('60:error:key', null, 1);
412+
413+
final result = rateLimiter.filter(envelope);
414+
expect(result, isNotNull);
415+
expect(result!.items.length, 1);
416+
expect(result.items.first.header.type, 'transaction');
417+
418+
// Expect only 1 warning log: per-item drop (no summary)
419+
expect(logCalls.length, 1);
420+
421+
final itemLog = logCalls.first;
422+
expect(itemLog.level, SentryLevel.warning);
423+
expect(
424+
itemLog.message,
425+
contains(
426+
'Envelope item of type "event" was dropped due to rate limiting'),
427+
);
428+
429+
expect(options.debug, isFalse);
430+
});
431+
});
322432
}
323433

324434
class Fixture {
@@ -348,3 +458,10 @@ class Fixture {
348458
return SentryTransaction(tracer);
349459
}
350460
}
461+
462+
class _LogCall {
463+
final SentryLevel level;
464+
final String message;
465+
466+
_LogCall(this.level, this.message);
467+
}

packages/flutter/lib/src/native/cocoa/binding.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ external ffi.Pointer<objc.ObjCBlockImpl> _SentryCocoa_wrapBlockingBlock_xtuoz7(
2626
@ffi.Native<
2727
ffi.Pointer<objc.ObjCObject> Function(
2828
ffi.Pointer<objc.ObjCObject>, ffi.Pointer<ffi.Void>)>()
29+
// ignore: unused_element
2930
external ffi.Pointer<objc.ObjCObject> _SentryCocoa_protocolTrampoline_1mbt9g9(
3031
ffi.Pointer<objc.ObjCObject> target,
3132
ffi.Pointer<ffi.Void> arg0,

0 commit comments

Comments
 (0)