Skip to content

Commit f201b65

Browse files
authored
fix: Log Warning instead of Error when ratelimited (#4927)
1 parent 6a068c9 commit f201b65

3 files changed

Lines changed: 120 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Fixes
6+
7+
- The SDK now logs a `Warning` instead of an `Error` when being ratelimited ([#4927](https://github.com/getsentry/sentry-dotnet/pull/4927))
8+
39
## 6.2.0-alpha.0
410

511
### Features

src/Sentry/Http/HttpTransportBase.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,8 @@ private void HandleFailure(HttpResponseMessage response, Envelope envelope)
359359
var eventId = envelope.TryGetEventId(_options.DiagnosticLogger);
360360

361361
// Spare the overhead if level is not enabled
362-
if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Error) is true && response.Content is { } content)
362+
var minLogLevel = response.StatusCode == (HttpStatusCode)429 ? SentryLevel.Warning : SentryLevel.Error;
363+
if (_options.DiagnosticLogger?.IsEnabled(minLogLevel) is true && response.Content is { } content)
363364
{
364365
if (HasJsonContent(content))
365366
{
@@ -428,7 +429,8 @@ private async Task HandleFailureAsync(HttpResponseMessage response, Envelope env
428429

429430
var eventId = envelope.TryGetEventId(_options.DiagnosticLogger);
430431
// Spare the overhead if level is not enabled
431-
if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Error) is true && response.Content is { } content)
432+
var minLogLevel = response.StatusCode == (HttpStatusCode)429 ? SentryLevel.Warning : SentryLevel.Error;
433+
if (_options.DiagnosticLogger?.IsEnabled(minLogLevel) is true && response.Content is { } content)
432434
{
433435
if (HasJsonContent(content))
434436
{
@@ -525,6 +527,12 @@ private void LogFailure(string responseString, HttpStatusCode responseStatusCode
525527
return;
526528
}
527529

530+
if (responseStatusCode == (HttpStatusCode)429)
531+
{
532+
LogRateLimited(eventId, responseString);
533+
return;
534+
}
535+
528536
_options.LogError("{0}: Sentry rejected the envelope '{1}'. Status code: {2}. Error detail: {3}.",
529537
_typeName,
530538
eventId,
@@ -551,6 +559,15 @@ private void LogFailure(JsonElement responseJson, HttpStatusCode responseStatusC
551559
return;
552560
}
553561

562+
if (responseStatusCode == (HttpStatusCode)429)
563+
{
564+
var responseDetail = errorCauses.Length > 0
565+
? $"{errorMessage} ({string.Join(", ", errorCauses)})"
566+
: errorMessage;
567+
LogRateLimited(eventId, responseDetail);
568+
return;
569+
}
570+
554571
_options.LogError("{0}: Sentry rejected the envelope '{1}'. Status code: {2}. Error detail: {3}. Error causes: {4}.",
555572
_typeName,
556573
eventId,
@@ -570,6 +587,18 @@ private void LogRequestTooLarge(SentryId? eventId, string responseDetail)
570587
responseDetail);
571588
}
572589

590+
private void LogRateLimited(SentryId? eventId, string responseDetail)
591+
{
592+
_options.LogWarning(
593+
"{0}: Sentry rejected the envelope '{1}' due to rate limiting. " +
594+
"This may indicate that you are sending too much data or have exceeded your quota. " +
595+
"See https://docs.sentry.io/product/accounts/quotas/ for more information. " +
596+
"Server response: {2}",
597+
_typeName,
598+
eventId,
599+
responseDetail);
600+
}
601+
573602
private static bool HasJsonContent(HttpContent content) =>
574603
string.Equals(content.Headers.ContentType?.MediaType, "application/json",
575604
StringComparison.OrdinalIgnoreCase);

test/Sentry.Tests/Internals/Http/HttpTransportTests.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,4 +989,87 @@ public async Task SendEnvelopeAsync_Response413_RecordsSendErrorDiscard()
989989
recorder.DiscardedEvents.Should().ContainKey(DiscardReason.SendError.WithCategory(DataCategory.Error));
990990
recorder.DiscardedEvents.Should().NotContainKey(DiscardReason.NetworkError.WithCategory(DataCategory.Error));
991991
}
992+
993+
[Fact]
994+
public async Task SendEnvelopeAsync_Response429WithJsonMessage_LogsWarning()
995+
{
996+
// Arrange
997+
const string expectedDetail = "Sentry dropped data due to a quota or internal rate limit being reached.";
998+
999+
var httpHandler = Substitute.For<MockableHttpMessageHandler>();
1000+
1001+
httpHandler.VerifiableSendAsync(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
1002+
.Returns(_ => SentryResponses.GetJsonErrorResponse((HttpStatusCode)429, expectedDetail));
1003+
1004+
var logger = new InMemoryDiagnosticLogger();
1005+
1006+
var httpTransport = new HttpTransport(
1007+
new SentryOptions
1008+
{
1009+
Dsn = ValidDsn,
1010+
Debug = true,
1011+
DiagnosticLogger = logger
1012+
},
1013+
new HttpClient(httpHandler));
1014+
1015+
var envelope = Envelope.FromEvent(new SentryEvent());
1016+
1017+
// Act
1018+
await httpTransport.SendEnvelopeAsync(envelope);
1019+
1020+
// Assert
1021+
var warningEntry = logger.Entries.FirstOrDefault(e =>
1022+
e.Level == SentryLevel.Warning &&
1023+
e.Message.Contains("due to rate limiting"));
1024+
1025+
warningEntry.Should().NotBeNull();
1026+
warningEntry!.Message.Should().Contain("exceeded your quota");
1027+
warningEntry.Args[2].ToString().Should().Contain(expectedDetail);
1028+
1029+
// Should NOT have an error-level log for this
1030+
logger.Entries.Should().NotContain(e =>
1031+
e.Level == SentryLevel.Error &&
1032+
e.Message.Contains("Sentry rejected the envelope"));
1033+
}
1034+
1035+
[Fact]
1036+
public async Task SendEnvelopeAsync_Response429WithTextMessage_LogsWarning()
1037+
{
1038+
// Arrange
1039+
const string expectedMessage = "Rate limited";
1040+
1041+
var httpHandler = Substitute.For<MockableHttpMessageHandler>();
1042+
1043+
httpHandler.VerifiableSendAsync(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
1044+
.Returns(_ => SentryResponses.GetTextErrorResponse((HttpStatusCode)429, expectedMessage));
1045+
1046+
var logger = new InMemoryDiagnosticLogger();
1047+
1048+
var httpTransport = new HttpTransport(
1049+
new SentryOptions
1050+
{
1051+
Dsn = ValidDsn,
1052+
Debug = true,
1053+
DiagnosticLogger = logger
1054+
},
1055+
new HttpClient(httpHandler));
1056+
1057+
var envelope = Envelope.FromEvent(new SentryEvent());
1058+
1059+
// Act
1060+
await httpTransport.SendEnvelopeAsync(envelope);
1061+
1062+
// Assert
1063+
var warningEntry = logger.Entries.FirstOrDefault(e =>
1064+
e.Level == SentryLevel.Warning &&
1065+
e.Message.Contains("due to rate limiting"));
1066+
1067+
warningEntry.Should().NotBeNull();
1068+
warningEntry!.Args[2].ToString().Should().Contain(expectedMessage);
1069+
1070+
// Should NOT have an error-level log for this
1071+
logger.Entries.Should().NotContain(e =>
1072+
e.Level == SentryLevel.Error &&
1073+
e.Message.Contains("Sentry rejected the envelope"));
1074+
}
9921075
}

0 commit comments

Comments
 (0)