Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions src/Sentry/Integrations/AppDomainProcessExitIntegration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Diagnostics;
using Sentry.Internal;

namespace Sentry.Integrations
{
internal class AppDomainProcessExitIntegration : IInternalSdkIntegration
{
private readonly IAppDomain _appDomain;
private IHub _hub;

public AppDomainProcessExitIntegration(IAppDomain appDomain = null)
{
_appDomain = appDomain ?? AppDomainAdapter.Instance;
}

public void Register(IHub hub, SentryOptions options)
{
Debug.Assert(hub != null);
_hub = hub;
_appDomain.ProcessExit += HandleProcessExit;
}

public void Unregister(IHub hub)
{
_appDomain.ProcessExit -= HandleProcessExit;
_hub = null;
}

internal void HandleProcessExit(object sender, EventArgs e)
{
(_hub as IDisposable)?.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Sentry.Internal;

namespace Sentry.Integrations
Expand Down
14 changes: 12 additions & 2 deletions src/Sentry/Internal/AppDomainAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@ namespace Sentry.Internal
internal interface IAppDomain
{
event UnhandledExceptionEventHandler UnhandledException;

event EventHandler ProcessExit;
}

internal sealed class AppDomainAdapter : IAppDomain
{
public static AppDomainAdapter Instance { get; } = new AppDomainAdapter();

private AppDomainAdapter() => AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;

private AppDomainAdapter()
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
}

public event UnhandledExceptionEventHandler UnhandledException;

public event EventHandler ProcessExit;

private void OnProcessExit(object sender, EventArgs e) => ProcessExit?.Invoke(sender, e);

private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
=> UnhandledException?.Invoke(this, e);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Sentry/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@

static Sentry.SentryOptionsExtensions.DisableAppDomainProcessExitFlush(this Sentry.SentryOptions options) -> void
3 changes: 2 additions & 1 deletion src/Sentry/SentryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,8 @@ public SentryOptions()

Integrations
= ImmutableList.Create<ISdkIntegration>(
new AppDomainUnhandledExceptionIntegration());
new AppDomainUnhandledExceptionIntegration(),
new AppDomainProcessExitIntegration());

InAppExclude
= ImmutableList.Create(
Expand Down
20 changes: 18 additions & 2 deletions src/Sentry/SentryOptionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using Sentry.Extensibility;
Expand Down Expand Up @@ -29,9 +30,14 @@ public static void DisableDuplicateEventDetection(this SentryOptions options)
/// Disables the capture of errors through <see cref="AppDomain.UnhandledException"/>
/// </summary>
/// <param name="options">The SentryOptions to remove the integration from.</param>
public static void DisableAppDomainUnhandledExceptionCapture(this SentryOptions options)
=> options.Integrations = options.Integrations.RemoveAll(p => p.GetType() == typeof(AppDomainUnhandledExceptionIntegration));
public static void DisableAppDomainUnhandledExceptionCapture(this SentryOptions options) => options.RemoveIntegration<AppDomainUnhandledExceptionIntegration>();

/// <summary>
/// Disables the capture of errors through <see cref="AppDomain.ProcessExit"/>
/// </summary>
/// <param name="options">The SentryOptions to remove the integration from.</param>
public static void DisableAppDomainProcessExitFlush(this SentryOptions options) => options.RemoveIntegration<AppDomainProcessExitIntegration>();

/// <summary>
/// Add an integration
/// </summary>
Expand All @@ -40,6 +46,15 @@ public static void DisableAppDomainUnhandledExceptionCapture(this SentryOptions
public static void AddIntegration(this SentryOptions options, ISdkIntegration integration)
=> options.Integrations = options.Integrations.Add(integration);

/// <summary>
/// Removes all integrations of type <typeparamref name="TIntegration"/>.
/// </summary>
/// <typeparam name="TIntegration">The type of the integration(s) to remove.</typeparam>
/// <param name="options">The SentryOptions to remove the integration(s) from.</param>
/// <returns></returns>
internal static void RemoveIntegration<TIntegration>(this SentryOptions options) where TIntegration : ISdkIntegration
=> options.Integrations = options.Integrations.RemoveAll(p => p.GetType() == typeof(TIntegration));

/// <summary>
/// Add prefix to exclude from 'InApp' stack trace list
/// </summary>
Expand Down Expand Up @@ -159,5 +174,6 @@ internal static void SetupLogging(this SentryOptions options)
options.DiagnosticLogger = null;
}
}

}
}
58 changes: 58 additions & 0 deletions test/Sentry.Tests/AppDomainProcessExitIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Threading.Tasks;
using NSubstitute;
using Sentry.Integrations;
using Sentry.Internal;
using Xunit;

namespace Sentry.Tests
{
public class AppDomainProcessExitIntegrationTests
{
private class Fixture
{
public IHub Hub { get; set; } = Substitute.For<IHub, IDisposable>();

public IAppDomain AppDomain { get; set; } = Substitute.For<IAppDomain>();

public Fixture() => Hub.IsEnabled.Returns(true);

public AppDomainProcessExitIntegration GetSut() => new AppDomainProcessExitIntegration(AppDomain);
}

private readonly Fixture _fixture = new Fixture();

public SentryOptions SentryOptions { get; set; } = new SentryOptions();

[Fact]
public void Handle_WithException_CaptureEvent()
{
var sut = _fixture.GetSut();
sut.Register(_fixture.Hub, SentryOptions);

sut.HandleProcessExit(this, EventArgs.Empty);

(_fixture.Hub as IDisposable).Received(1).Dispose();
}

[Fact]
public void Register_ProcessExit_Subscribes()
{
var sut = _fixture.GetSut();
sut.Register(_fixture.Hub, SentryOptions);

_fixture.AppDomain.Received().ProcessExit += sut.HandleProcessExit;
}

[Fact]
public void Unregister_ProcessExit_Unsubscribes()
{
var sut = _fixture.GetSut();

sut.Register(_fixture.Hub, SentryOptions);
sut.Unregister(_fixture.Hub);

_fixture.AppDomain.Received(1).ProcessExit -= sut.HandleProcessExit;
}
}
}