diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1291a5ab..6f9192844a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +## Features + +- Add CaptureLastError as an extension method to the Server class on ASP.NET ([#1411](https://github.com/getsentry/sentry-dotnet/pull/1411)) - Add IsDynamicCode* to events ([#1418](https://github.com/getsentry/sentry-dotnet/pull/1418)) ## 3.12.3 diff --git a/src/Sentry.AspNet/SentryHttpServerUtilityExtensions.cs b/src/Sentry.AspNet/SentryHttpServerUtilityExtensions.cs new file mode 100644 index 0000000000..d0b142bca9 --- /dev/null +++ b/src/Sentry.AspNet/SentryHttpServerUtilityExtensions.cs @@ -0,0 +1,30 @@ +using System.Web; +using Sentry.Extensibility; +using Sentry.Protocol; + +namespace Sentry.AspNet; + +/// +/// HttpServerUtility extensions. +/// +public static class SentryHttpServerUtilityExtensions +{ + /// + /// Captures the last error from the given HttpServerUtility and sends it to Sentry. + /// + /// The HttpServerUtility that contains the last error. + /// A SentryId. + public static SentryId CaptureLastError(this HttpServerUtility server) => server.CaptureLastError(HubAdapter.Instance); + + // for testing + internal static SentryId CaptureLastError(this HttpServerUtility server, IHub hub) + { + if (server.GetLastError() is { } exception) + { + exception.Data[Mechanism.HandledKey] = false; + exception.Data[Mechanism.MechanismKey] = "HttpApplication.Application_Error"; + return hub.CaptureException(exception); + } + return SentryId.Empty; + } +} diff --git a/test/Sentry.AspNet.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt b/test/Sentry.AspNet.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt index 8fdf157305..18dc43c940 100644 --- a/test/Sentry.AspNet.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt +++ b/test/Sentry.AspNet.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt @@ -10,4 +10,8 @@ namespace Sentry.AspNet { public static void AddAspNet(this Sentry.SentryOptions options, Sentry.Extensibility.RequestSize maxRequestBodySize = 0) { } } + public static class SentryHttpServerUtilityExtensions + { + public static Sentry.SentryId CaptureLastError(this System.Web.HttpServerUtility server) { } + } } \ No newline at end of file diff --git a/test/Sentry.AspNet.Tests/SentryHttpServerUtilityExtensionsTests.cs b/test/Sentry.AspNet.Tests/SentryHttpServerUtilityExtensionsTests.cs new file mode 100644 index 0000000000..1186031c23 --- /dev/null +++ b/test/Sentry.AspNet.Tests/SentryHttpServerUtilityExtensionsTests.cs @@ -0,0 +1,57 @@ +using System.Web; + +namespace Sentry.AspNet.Tests; + +public class SentryHttpServerUtilityExtensionsTests +{ + private class Fixture + { + public SentryId Id { get; set; } + + public IHub GetSut() + { + Id = SentryId.Create(); + var hub = Substitute.For(); + hub.IsEnabled.Returns(true); + hub.CaptureEvent(Arg.Any()).Returns(Id); + return hub; + } + } + + private readonly Fixture _fixture = new(); + + [Fact] + public void CaptureLastError_WithError_UnhandledErrorCaptured() + { + // Arrange + var hub = _fixture.GetSut(); + var exception = new Exception(); + + var context = new HttpContext(new HttpRequest("", "http://test", null), new HttpResponse(new StringWriter())); + context.AddError(exception); + + // Act + var receivedId = context.Server.CaptureLastError(hub); + + // Assert + hub.Received(1).CaptureEvent(Arg.Is(@event => @event.Exception == exception)); + Assert.False(exception.Data[Mechanism.HandledKey] as bool?); + Assert.Equal("HttpApplication.Application_Error", exception.Data[Mechanism.MechanismKey]); + Assert.Equal(_fixture.Id, receivedId); + } + + [Fact] + public void CaptureLastError_WithoutError_DoNothing() + { + // Arrange + var hub = _fixture.GetSut(); + var context = new HttpContext(new HttpRequest("", "http://test", null), new HttpResponse(new StringWriter())); + + // Act + var receivedId = context.Server.CaptureLastError(hub); + + // Assert + hub.Received(0).CaptureEvent(Arg.Any()); + Assert.Equal(SentryId.Empty, receivedId); + } +}