Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
19c0b76
Add new HttpStatusCodeRangeExtensions and tests.
ericsampson Nov 11, 2025
8b3ea27
Improve tests.
ericsampson Nov 11, 2025
56e3bf8
Add test that currently fails to prove the fix works.
ericsampson Nov 11, 2025
b97a6bd
Enhance the HttpFailedRequestHandler so that it captures a full excep…
ericsampson Nov 11, 2025
fd65e67
Merge branch 'main' into SentryHttpMessageHandler-issue-grouping
ericsampson Nov 17, 2025
02e69b1
Delete tests for deleted functionality.
ericsampson Nov 17, 2025
c9da3c7
Update test/Sentry.Tests/HttpStatusCodeRangeExtensionsTests.cs
ericsampson Nov 17, 2025
a07ed5d
Switch to using ExceptionDispatchInfo.SetCurrentStackTrace
ericsampson Nov 17, 2025
c4b981d
Update re PR feedback
ericsampson Nov 17, 2025
ced8585
Update with PR feedback
ericsampson Nov 17, 2025
7a1573a
PR feedback
ericsampson Nov 17, 2025
02a16b4
PR feedback
ericsampson Nov 17, 2025
77132f5
Add note to changelog
ericsampson Nov 17, 2025
46bcc41
Update src/Sentry/SentryHttpFailedRequestHandler.cs
ericsampson Nov 17, 2025
70232c3
Update src/Sentry/SentryHttpFailedRequestHandler.cs re method availab…
ericsampson Nov 18, 2025
7a90dac
format test/Sentry.Tests/SentryHttpFailedRequestHandlerTests.cs
ericsampson Nov 18, 2025
f8edf37
Update test for running on .NET5+
ericsampson Nov 18, 2025
e3fc2bc
Merge branch 'main' into SentryHttpMessageHandler-issue-grouping
ericsampson Nov 18, 2025
c76afd1
update Changelog re PR feedback
ericsampson Nov 18, 2025
885d728
Update CHANGELOG.md
ericsampson Nov 19, 2025
3901dd0
Merge branch 'main' into SentryHttpMessageHandler-issue-grouping
ericsampson Nov 19, 2025
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
25 changes: 25 additions & 0 deletions src/Sentry/HttpStatusCodeRangeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Sentry;

/// <summary>
/// Extension methods for collections of <see cref="HttpStatusCodeRange"/>.
/// </summary>
internal static class HttpStatusCodeRangeExtensions
{
/// <summary>
/// Checks if any range in the collection contains the given status code.
/// </summary>
/// <param name="ranges">Collection of ranges to check.</param>
/// <param name="statusCode">Status code to check.</param>
/// <returns>True if any range contains the given status code.</returns>
internal static bool ContainsStatusCode(this IEnumerable<HttpStatusCodeRange> ranges, int statusCode)
=> ranges.Any(range => range.Contains(statusCode));

/// <summary>
/// Checks if any range in the collection contains the given status code.
/// </summary>
/// <param name="ranges">Collection of ranges to check.</param>
/// <param name="statusCode">Status code to check.</param>
/// <returns>True if any range contains the given status code.</returns>
internal static bool ContainsStatusCode(this IEnumerable<HttpStatusCodeRange> ranges, HttpStatusCode statusCode)
=> ranges.ContainsStatusCode((int)statusCode);
}
28 changes: 0 additions & 28 deletions src/Sentry/Internal/Extensions/HttpStatusExtensions.cs

This file was deleted.

105 changes: 61 additions & 44 deletions src/Sentry/SentryHttpFailedRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,67 +15,84 @@ internal SentryHttpFailedRequestHandler(IHub hub, SentryOptions options)
protected internal override void DoEnsureSuccessfulResponse([NotNull] HttpRequestMessage request, [NotNull] HttpResponseMessage response)
{
// Don't capture events for successful requests
if (!Options.FailedRequestStatusCodes.Any(range => range.Contains(response.StatusCode)))
if (!Options.FailedRequestStatusCodes.ContainsStatusCode(response.StatusCode))
{
return;
}

// Capture the event
try

var statusCode = (int)response.StatusCode;
// Match behavior of HttpResponseMessage.EnsureSuccessStatusCode
if (statusCode >= 200 && statusCode <= 299)
{
#if NET5_0_OR_GREATER
response.EnsureSuccessStatusCode();
return;
}

var exception = new HttpRequestException(
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"Response status code does not indicate success: {0}",
statusCode)
);

#if NET6_0_OR_GREATER
// Add a full stack trace into the exception to improve Issue grouping,
// see https://github.com/getsentry/sentry-dotnet/issues/3582
ExceptionDispatchInfo.SetCurrentStackTrace(exception);
#else
// Use our own implementation of EnsureSuccessStatusCode because older implementations of
// EnsureSuccessStatusCode disposes the content.
// See https://github.com/dotnet/runtime/issues/24845
response.StatusCode.EnsureSuccessStatusCode();
#endif
// Where SetRemoteStackTrace is not available, throw and catch to get a basic stack trace
try
{
throw exception;
}
catch (HttpRequestException exception)
catch (HttpRequestException ex)
{
exception.SetSentryMechanism(MechanismType);
exception = ex;
}
#endif

var @event = new SentryEvent(exception);
var hint = new SentryHint(HintTypes.HttpResponseMessage, response);
exception.SetSentryMechanism(MechanismType);

var uri = response.RequestMessage?.RequestUri;
var sentryRequest = new SentryRequest
{
QueryString = uri?.Query,
Method = response.RequestMessage?.Method.Method.ToUpperInvariant()
};
var @event = new SentryEvent(exception);
var hint = new SentryHint(HintTypes.HttpResponseMessage, response);

var responseContext = new Response
{
StatusCode = (short)response.StatusCode,
var uri = response.RequestMessage?.RequestUri;
var sentryRequest = new SentryRequest
{
QueryString = uri?.Query,
Method = response.RequestMessage?.Method.Method.ToUpperInvariant()
};

var responseContext = new Response
{
StatusCode = (short)response.StatusCode,
#if NET5_0_OR_GREATER
// Starting with .NET 5, the content and headers are guaranteed to not be null.
BodySize = response.Content.Headers.ContentLength
// Starting with .NET 5, the content and headers are guaranteed to not be null.
BodySize = response.Content.Headers.ContentLength
#else
// The ContentLength might be null (but that's ok).
// See https://github.com/dotnet/runtime/issues/16162
BodySize = response.Content?.Headers?.ContentLength
// The ContentLength might be null (but that's ok).
// See https://github.com/dotnet/runtime/issues/16162
BodySize = response.Content?.Headers?.ContentLength
#endif
};
};

if (!Options.SendDefaultPii)
{
sentryRequest.Url = uri?.HttpRequestUrl();
}
else
{
sentryRequest.Url = uri?.AbsoluteUri;
sentryRequest.Cookies = request.Headers.GetCookies();
sentryRequest.AddHeaders(request.Headers);
responseContext.Cookies = response.Headers.GetCookies();
responseContext.AddHeaders(response.Headers);
}
if (!Options.SendDefaultPii)
{
sentryRequest.Url = uri?.HttpRequestUrl();
}
else
{
sentryRequest.Url = uri?.AbsoluteUri;
sentryRequest.Cookies = request.Headers.GetCookies();
sentryRequest.AddHeaders(request.Headers);
responseContext.Cookies = response.Headers.GetCookies();
responseContext.AddHeaders(response.Headers);
}

@event.Request = sentryRequest;
@event.Contexts[Response.Type] = responseContext;
@event.Request = sentryRequest;
@event.Contexts[Response.Type] = responseContext;

Hub.CaptureEvent(@event, hint: hint);
}
Hub.CaptureEvent(@event, hint: hint);
}
}
171 changes: 171 additions & 0 deletions test/Sentry.Tests/HttpStatusCodeRangeExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
namespace Sentry.Tests;

public class HttpStatusCodeRangeExtensionsTests
{
[Fact]
public void ContainsStatusCode_EmptyList_ReturnsFalse()
{
// Arrange
var ranges = new List<HttpStatusCodeRange>();

// Act
var result = ranges.ContainsStatusCode(404);

// Assert
result.Should().BeFalse();
}

[Theory]
[InlineData(400)]
[InlineData(450)]
[InlineData(499)]
public void ContainsStatusCode_SingleRangeInRange_ReturnsTrue(int statusCode)
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 499) };

// Act
var result = ranges.ContainsStatusCode(statusCode);

// Assert
result.Should().BeTrue();
}

[Theory]
[InlineData(200)]
[InlineData(399)]
[InlineData(500)]
public void ContainsStatusCode_SingleRangeOutOfRange_ReturnsFalse(int statusCode)
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 499) };

// Act
var result = ranges.ContainsStatusCode(statusCode);

// Assert
result.Should().BeFalse();
}

[Theory]
[InlineData(400)] // In first range
[InlineData(404)] // In first range
[InlineData(500)] // In second range
[InlineData(503)] // In second range
public void ContainsStatusCode_MultipleRangesInAnyRange_ReturnsTrue(int statusCode)
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 404), (500, 503) };

// Act
var result = ranges.ContainsStatusCode(statusCode);

// Assert
result.Should().BeTrue();
}

[Theory]
[InlineData(200)] // Below ranges
[InlineData(405)] // Between ranges
[InlineData(499)] // Between ranges
[InlineData(504)] // Above ranges
public void ContainsStatusCode_MultipleRangesNotInAnyRange_ReturnsFalse(int statusCode)
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 404), (500, 503) };

// Act
var result = ranges.ContainsStatusCode(statusCode);

// Assert
result.Should().BeFalse();
}

[Theory]
[InlineData(400)] // In first range only
[InlineData(425)] // In overlap
[InlineData(450)] // In overlap
[InlineData(475)] // In second range only
public void ContainsStatusCode_OverlappingRangesInUnion_ReturnsTrue(int statusCode)
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 450), (425, 475) };

// Act
var result = ranges.ContainsStatusCode(statusCode);

// Assert
result.Should().BeTrue();
}

[Theory]
[InlineData(200)] // Below first range
[InlineData(399)] // Below first range
[InlineData(476)] // Above second range
[InlineData(500)] // Above second range
public void ContainsStatusCode_OverlappingRangesOutsideUnion_ReturnsFalse(int statusCode)
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 450), (425, 475) };

// Act
var result = ranges.ContainsStatusCode(statusCode);

// Assert
result.Should().BeFalse();
}

[Fact]
public void ContainsStatusCode_SingleValueRangeExactMatch_ReturnsTrue()
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { 404 };

// Act
var result = ranges.ContainsStatusCode(404);

// Assert
result.Should().BeTrue();
}

[Theory]
[InlineData(403)]
[InlineData(405)]
public void ContainsStatusCode_SingleValueRangeNoMatch_ReturnsFalse(int statusCode)
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { 404 };

// Act
var result = ranges.ContainsStatusCode(statusCode);

// Assert
result.Should().BeFalse();
}

[Fact]
public void ContainsStatusCode_HttpStatusCodeEnumInRange_ReturnsTrue()
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 499) };

// Act
var result = ranges.ContainsStatusCode(HttpStatusCode.NotFound); // 404

// Assert
result.Should().BeTrue();
}

[Fact]
public void ContainsStatusCode_HttpStatusCodeEnumOutOfRange_ReturnsFalse()
{
// Arrange
var ranges = new List<HttpStatusCodeRange> { (400, 499) };

// Act
var result = ranges.ContainsStatusCode(HttpStatusCode.OK); // 200

// Assert
result.Should().BeFalse();
}
}

This file was deleted.

Loading