Skip to content

Log a warning when dropping envelope items #3165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/dart/lib/src/transport/rate_limiter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class RateLimiter {
DiscardReason.rateLimitBackoff,
DataCategory.fromItemType(item.header.type),
);
_logDebugWarning(
'Envelope item of type "${item.header.type}" was dropped due to rate limiting.',
);

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

// no reason to continue
if (toSend.isEmpty) {
_logDebugWarning(
'Envelope was dropped due to rate limiting.',
);
return null;
}

Expand Down Expand Up @@ -121,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;
}
}
}
117 changes: 117 additions & 0 deletions packages/dart/test/protocol/rate_limiter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,116 @@ void main() {
expect(DataCategory.fromItemType('unknown'), DataCategory.unknown);
});
});

group('RateLimiter logging', () {
test('logs warning for dropped item and full envelope', () {
final options = defaultTestOptions();
options.debug = false;
options.diagnosticLevel = SentryLevel.warning;

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 warning logs: item dropped + all items dropped
expect(logCalls.length, 2);

final itemLog = logCalls[0];
expect(itemLog.level, SentryLevel.warning);
expect(
itemLog.message,
contains(
'Envelope item of type "event" was dropped due to rate limiting'),
);

final fullDropLog = logCalls[1];
expect(fullDropLog.level, SentryLevel.warning);
expect(
fullDropLog.message,
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 = false;
options.diagnosticLevel = SentryLevel.warning;

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 only 1 warning log: per-item drop (no summary)
expect(logCalls.length, 1);

final itemLog = logCalls.first;
expect(itemLog.level, SentryLevel.warning);
expect(
itemLog.message,
contains(
'Envelope item of type "event" was dropped due to rate limiting'),
);

expect(options.debug, isFalse);
});
});
}

class Fixture {
Expand Down Expand Up @@ -348,3 +458,10 @@ class Fixture {
return SentryTransaction(tracer);
}
}

class _LogCall {
final SentryLevel level;
final String message;

_LogCall(this.level, this.message);
}
Loading