Skip to content

Commit 866b314

Browse files
feat: Update error 413 handling (#4863)
Co-authored-by: Stefan Pölz <38893694+Flash0ver@users.noreply.github.com>
1 parent a2c61d4 commit 866b314

4 files changed

Lines changed: 138 additions & 6 deletions

File tree

CHANGELOG.md

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

99
### Fixes
1010

11+
- The SDK now logs a specific error message when envelopes are rejected due to size limits (HTTP 413) ([#4863](https://github.com/getsentry/sentry-dotnet/pull/4863))
1112
- Fixed thread-safety issue on Android when multiple events are captured concurrently ([#4814](https://github.com/getsentry/sentry-dotnet/pull/4814))
1213

1314
### Dependencies

src/Sentry/Http/HttpTransportBase.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ private void IncrementDiscardsForHttpFailure(HttpStatusCode responseStatusCode,
506506
return;
507507
}
508508

509-
_options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.NetworkError, envelope);
509+
_options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.SendError, envelope);
510510

511511
// Also restore any counts that were trying to be sent, so they are not lost.
512512
var clientReportItems = envelope.Items.Where(x => x.TryGetType() == "client_report");
@@ -519,6 +519,12 @@ private void IncrementDiscardsForHttpFailure(HttpStatusCode responseStatusCode,
519519

520520
private void LogFailure(string responseString, HttpStatusCode responseStatusCode, SentryId? eventId)
521521
{
522+
if (responseStatusCode == HttpStatusCode.RequestEntityTooLarge)
523+
{
524+
LogRequestTooLarge(eventId, responseString);
525+
return;
526+
}
527+
522528
_options.LogError("{0}: Sentry rejected the envelope '{1}'. Status code: {2}. Error detail: {3}.",
523529
_typeName,
524530
eventId,
@@ -536,6 +542,15 @@ private void LogFailure(JsonElement responseJson, HttpStatusCode responseStatusC
536542
responseJson.GetPropertyOrNull("causes")?.EnumerateArray().Select(j => j.GetString()).ToArray()
537543
?? Array.Empty<string>();
538544

545+
if (responseStatusCode == HttpStatusCode.RequestEntityTooLarge)
546+
{
547+
var responseDetail = errorCauses.Length > 0
548+
? $"{errorMessage} ({string.Join(", ", errorCauses)})"
549+
: errorMessage;
550+
LogRequestTooLarge(eventId, responseDetail);
551+
return;
552+
}
553+
539554
_options.LogError("{0}: Sentry rejected the envelope '{1}'. Status code: {2}. Error detail: {3}. Error causes: {4}.",
540555
_typeName,
541556
eventId,
@@ -544,6 +559,17 @@ private void LogFailure(JsonElement responseJson, HttpStatusCode responseStatusC
544559
string.Join(", ", errorCauses));
545560
}
546561

562+
private void LogRequestTooLarge(SentryId? eventId, string responseDetail)
563+
{
564+
_options.LogError(
565+
"{0}: Sentry rejected the envelope '{1}' because it exceeded the maximum allowed size. " +
566+
"Consider reducing attachment sizes, removing unnecessary data, or splitting large payloads into smaller requests. " +
567+
"Server response: {2}",
568+
_typeName,
569+
eventId,
570+
responseDetail);
571+
}
572+
547573
private static bool HasJsonContent(HttpContent content) =>
548574
string.Equals(content.Headers.ContentType?.MediaType, "application/json",
549575
StringComparison.OrdinalIgnoreCase);

src/Sentry/Internal/DiscardReason.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace Sentry.Internal;
99
public static DiscardReason EventProcessor = new("event_processor");
1010
public static DiscardReason NetworkError = new("network_error");
1111
public static DiscardReason QueueOverflow = new("queue_overflow");
12+
public static DiscardReason SendError = new("send_error");
1213
public static DiscardReason RateLimitBackoff = new("ratelimit_backoff");
1314
public static DiscardReason SampleRate = new("sample_rate");
1415
public static DiscardReason Backpressure = new("backpressure");

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

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ public async Task SendEnvelopeAsync_ResponseRequestEntityTooLargeWithoutPathDefi
202202
public async Task SendEnvelopeAsync_ResponseNotOkWithStringMessage_LogsError()
203203
{
204204
// Arrange
205-
const HttpStatusCode expectedCode = HttpStatusCode.RequestEntityTooLarge;
206-
const string expectedMessage = "413 Request Entity Too Large";
205+
const HttpStatusCode expectedCode = HttpStatusCode.InternalServerError;
206+
const string expectedMessage = "500 Internal Server Error";
207207

208208
var httpHandler = Substitute.For<MockableHttpMessageHandler>();
209209

@@ -483,9 +483,9 @@ public async Task SendEnvelopeAsync_Fails_RestoresDiscardedEventCounts()
483483
{DiscardReason.EventProcessor.WithCategory(DataCategory.Error), 2},
484484
{DiscardReason.QueueOverflow.WithCategory(DataCategory.Security), 3},
485485

486-
// We also expect two new items recorded, due to the forced network failure.
487-
{DiscardReason.NetworkError.WithCategory(DataCategory.Error), 1}, // from the event
488-
{DiscardReason.NetworkError.WithCategory(DataCategory.Default), 1} // from the client report
486+
// We also expect two new items recorded, due to the forced HTTP failure.
487+
{DiscardReason.SendError.WithCategory(DataCategory.Error), 1}, // from the event
488+
{DiscardReason.SendError.WithCategory(DataCategory.Default), 1} // from the client report
489489
});
490490
}
491491

@@ -885,4 +885,108 @@ public async Task SendEnvelopeAsync_RateLimited_CallsBackpressureMonitor()
885885
backpressureMonitor.LastRateLimitEventTicks.Should().Be(_fakeClock.GetUtcNow().Ticks);
886886
backpressureMonitor.IsHealthy.Should().BeFalse();
887887
}
888+
889+
[Fact]
890+
public async Task SendEnvelopeAsync_Response413WithJsonMessage_LogsSizeLimitError()
891+
{
892+
// Arrange
893+
const string expectedDetail = "Envelope too large";
894+
var expectedCauses = new[] { "max size exceeded" };
895+
896+
var httpHandler = Substitute.For<MockableHttpMessageHandler>();
897+
898+
httpHandler.VerifiableSendAsync(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
899+
.Returns(_ => SentryResponses.GetJsonErrorResponse(HttpStatusCode.RequestEntityTooLarge, expectedDetail, expectedCauses));
900+
901+
var logger = new InMemoryDiagnosticLogger();
902+
903+
var httpTransport = new HttpTransport(
904+
new SentryOptions
905+
{
906+
Dsn = ValidDsn,
907+
Debug = true,
908+
DiagnosticLogger = logger
909+
},
910+
new HttpClient(httpHandler));
911+
912+
var envelope = Envelope.FromEvent(new SentryEvent());
913+
914+
// Act
915+
await httpTransport.SendEnvelopeAsync(envelope);
916+
917+
// Assert
918+
var errorEntry = logger.Entries.FirstOrDefault(e =>
919+
e.Level == SentryLevel.Error &&
920+
e.Message.Contains("exceeded the maximum allowed size"));
921+
922+
errorEntry.Should().NotBeNull();
923+
errorEntry!.Message.Should().Contain("Consider reducing attachment sizes");
924+
errorEntry.Args[2].ToString().Should().Contain(expectedDetail);
925+
errorEntry.Args[2].ToString().Should().Contain(expectedCauses[0]);
926+
}
927+
928+
[Fact]
929+
public async Task SendEnvelopeAsync_Response413WithTextMessage_LogsSizeLimitError()
930+
{
931+
// Arrange
932+
const string expectedMessage = "413 Request Entity Too Large";
933+
934+
var httpHandler = Substitute.For<MockableHttpMessageHandler>();
935+
936+
httpHandler.VerifiableSendAsync(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
937+
.Returns(_ => SentryResponses.GetTextErrorResponse(HttpStatusCode.RequestEntityTooLarge, expectedMessage));
938+
939+
var logger = new InMemoryDiagnosticLogger();
940+
941+
var httpTransport = new HttpTransport(
942+
new SentryOptions
943+
{
944+
Dsn = ValidDsn,
945+
Debug = true,
946+
DiagnosticLogger = logger
947+
},
948+
new HttpClient(httpHandler));
949+
950+
var envelope = Envelope.FromEvent(new SentryEvent());
951+
952+
// Act
953+
await httpTransport.SendEnvelopeAsync(envelope);
954+
955+
// Assert
956+
var errorEntry = logger.Entries.FirstOrDefault(e =>
957+
e.Level == SentryLevel.Error &&
958+
e.Message.Contains("exceeded the maximum allowed size"));
959+
960+
errorEntry.Should().NotBeNull();
961+
errorEntry!.Message.Should().Contain("Consider reducing attachment sizes");
962+
errorEntry.Args[2].ToString().Should().Contain(expectedMessage);
963+
}
964+
965+
[Fact]
966+
public async Task SendEnvelopeAsync_Response413_RecordsSendErrorDiscard()
967+
{
968+
// Arrange
969+
using var httpHandler = new RecordingHttpMessageHandler(
970+
new FakeHttpMessageHandler(
971+
() => SentryResponses.GetJsonErrorResponse(HttpStatusCode.RequestEntityTooLarge, "Too large")));
972+
973+
var options = new SentryOptions
974+
{
975+
Dsn = ValidDsn,
976+
DiagnosticLogger = _testOutputLogger,
977+
SendClientReports = true,
978+
Debug = true
979+
};
980+
981+
var httpTransport = new HttpTransport(options, new HttpClient(httpHandler));
982+
983+
var recorder = (ClientReportRecorder)options.ClientReportRecorder;
984+
985+
// Act
986+
await httpTransport.SendEnvelopeAsync(Envelope.FromEvent(new SentryEvent()));
987+
988+
// Assert - should use SendError, not NetworkError
989+
recorder.DiscardedEvents.Should().ContainKey(DiscardReason.SendError.WithCategory(DataCategory.Error));
990+
recorder.DiscardedEvents.Should().NotContainKey(DiscardReason.NetworkError.WithCategory(DataCategory.Error));
991+
}
888992
}

0 commit comments

Comments
 (0)