@@ -319,6 +319,121 @@ void main() {
319319 expect (DataCategory .fromItemType ('unknown' ), DataCategory .unknown);
320320 });
321321 });
322+
323+ group ('RateLimiter logging' , () {
324+ test ('logs debug for dropped item and full envelope' , () {
325+ final options = defaultTestOptions ();
326+ options.debug = true ;
327+ options.diagnosticLevel = SentryLevel .debug;
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 debug logs: item dropped + all items dropped
359+ expect (logCalls.length, 2 );
360+
361+ final itemLog = logCalls[0 ];
362+ expect (itemLog.level, SentryLevel .debug);
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 .debug);
371+ expect (
372+ fullDropLog.message,
373+ contains ('All envelope items were dropped due to rate limiting' ),
374+ );
375+ });
376+
377+ test ('logs debug when some items dropped and some sent' , () {
378+ final options = defaultTestOptions ();
379+ options.debug = true ;
380+ options.diagnosticLevel = SentryLevel .debug;
381+
382+ final logCalls = < _LogCall > [];
383+ void mockLogger (
384+ SentryLevel level,
385+ String message, {
386+ String ? logger,
387+ Object ? exception,
388+ StackTrace ? stackTrace,
389+ }) {
390+ logCalls.add (_LogCall (level, message));
391+ }
392+
393+ options.log = mockLogger;
394+
395+ final rateLimiter = RateLimiter (options);
396+
397+ // One event (error) and one transaction
398+ final eventItem = SentryEnvelopeItem .fromEvent (SentryEvent ());
399+ final transaction = fixture.getTransaction ();
400+ final transactionItem = SentryEnvelopeItem .fromTransaction (transaction);
401+
402+ final envelope = SentryEnvelope (
403+ SentryEnvelopeHeader .newEventId (),
404+ [eventItem, transactionItem],
405+ );
406+
407+ // Apply rate limit only for errors so the transaction can still be sent
408+ rateLimiter.updateRetryAfterLimits ('60:error:key' , null , 1 );
409+
410+ final result = rateLimiter.filter (envelope);
411+ expect (result, isNotNull);
412+ expect (result! .items.length, 1 );
413+ expect (result.items.first.header.type, 'transaction' );
414+
415+ // Expect 2 debug logs: per-item drop + summary
416+ expect (logCalls.length, 2 );
417+
418+ final itemLog = logCalls[0 ];
419+ expect (itemLog.level, SentryLevel .debug);
420+ expect (
421+ itemLog.message,
422+ contains (
423+ 'Envelope item of type "event" was dropped due to rate limiting' ),
424+ );
425+
426+ final summaryLog = logCalls[1 ];
427+ expect (summaryLog.level, SentryLevel .debug);
428+ expect (
429+ summaryLog.message,
430+ allOf ([
431+ contains ('1 envelope item(s) were dropped due to rate limiting' ),
432+ contains ('but 1 item(s) will still be sent' ),
433+ ]),
434+ );
435+ });
436+ });
322437}
323438
324439class Fixture {
@@ -348,3 +463,10 @@ class Fixture {
348463 return SentryTransaction (tracer);
349464 }
350465}
466+
467+ class _LogCall {
468+ final SentryLevel level;
469+ final String message;
470+
471+ _LogCall (this .level, this .message);
472+ }
0 commit comments