diff --git a/.gitignore b/.gitignore
index 40ab2754c7..1a40853714 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ docs/docfx/*
docs/docfx.zip
/samples/Sentry.Samples.Aws.Lambda.AspNetCoreServer/Properties/launchSettings.json
*.received.*
+mono_crash.*.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ffdaa0a45..c674467cfb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
- Added a new `Sentry.Maui` integration library for the [.NET MAUI](https://dotnet.microsoft.com/apps/maui) platform:
- Initial MAUI support ([#1663](https://github.com/getsentry/sentry-dotnet/pull/1663))
- Continue with adding MAUI support ([#1670](https://github.com/getsentry/sentry-dotnet/pull/1670))
+ - MAUI events become extra context in Sentry events ([#1706](https://github.com/getsentry/sentry-dotnet/pull/1706))
- Added a new `net6.0-android` target for the `Sentry` core library, which bundles the [Sentry Android SDK](https://docs.sentry.io/platforms/android/):
- Initial .NET 6 Android support ([#1288](https://github.com/getsentry/sentry-dotnet/pull/1288))
- Update Android Support ([#1669](https://github.com/getsentry/sentry-dotnet/pull/1669))
diff --git a/Sentry.sln b/Sentry.sln
index b63053bdc6..2eae3ad840 100644
--- a/Sentry.sln
+++ b/Sentry.sln
@@ -137,13 +137,14 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.DiagnosticSource.Tests", "test\Sentry.DiagnosticSource.Tests\Sentry.DiagnosticSource.Tests.csproj", "{D870B028-16ED-4551-8B0F-5529479D04C9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Samples.Android", "samples\Sentry.Samples.Android\Sentry.Samples.Android.csproj", "{5CB9167E-ED23-4A67-8D3A-B66B0C5196C8}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.DiagnosticSource.IntegrationTests", "test\Sentry.DiagnosticSource.IntegrationTests\Sentry.DiagnosticSource.IntegrationTests.csproj", "{F8120B9C-D4CA-43DA-B5E1-1CFBA7C36E3B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.Maui", "samples\Sentry.Samples.Maui\Sentry.Samples.Maui.csproj", "{EBCCABF9-F670-4C8D-AABC-4EB132961929}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Samples.Maui", "samples\Sentry.Samples.Maui\Sentry.Samples.Maui.csproj", "{EBCCABF9-F670-4C8D-AABC-4EB132961929}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui", "src\Sentry.Maui\Sentry.Maui.csproj", "{FFFC74C5-680B-43E3-9C42-A7A23B589CB6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Maui", "src\Sentry.Maui\Sentry.Maui.csproj", "{FFFC74C5-680B-43E3-9C42-A7A23B589CB6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui.Tests", "test\Sentry.Maui.Tests\Sentry.Maui.Tests.csproj", "{143076C0-8D6B-4054-9F45-06B21655F417}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Maui.Tests", "test\Sentry.Maui.Tests\Sentry.Maui.Tests.csproj", "{143076C0-8D6B-4054-9F45-06B21655F417}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -327,8 +328,10 @@ Global
{F8120B9C-D4CA-43DA-B5E1-1CFBA7C36E3B}.Release|Any CPU.Build.0 = Release|Any CPU
{EBCCABF9-F670-4C8D-AABC-4EB132961929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBCCABF9-F670-4C8D-AABC-4EB132961929}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EBCCABF9-F670-4C8D-AABC-4EB132961929}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{EBCCABF9-F670-4C8D-AABC-4EB132961929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBCCABF9-F670-4C8D-AABC-4EB132961929}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EBCCABF9-F670-4C8D-AABC-4EB132961929}.Release|Any CPU.Deploy.0 = Release|Any CPU
{FFFC74C5-680B-43E3-9C42-A7A23B589CB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFFC74C5-680B-43E3-9C42-A7A23B589CB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFFC74C5-680B-43E3-9C42-A7A23B589CB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/samples/Sentry.Samples.Maui/MainPage.xaml b/samples/Sentry.Samples.Maui/MainPage.xaml
index ee1f3e7b02..cd2d3b8b6e 100644
--- a/samples/Sentry.Samples.Maui/MainPage.xaml
+++ b/samples/Sentry.Samples.Maui/MainPage.xaml
@@ -36,10 +36,17 @@
HorizontalOptions="Center" />
+
+
diff --git a/samples/Sentry.Samples.Maui/MainPage.xaml.cs b/samples/Sentry.Samples.Maui/MainPage.xaml.cs
index 3126d64f3c..2de69370cf 100644
--- a/samples/Sentry.Samples.Maui/MainPage.xaml.cs
+++ b/samples/Sentry.Samples.Maui/MainPage.xaml.cs
@@ -21,9 +21,21 @@ private void OnCounterClicked(object sender, EventArgs e)
SemanticScreenReader.Announce(CounterBtn.Text);
}
- private void OnExceptionClicked(object sebnder, EventArgs e)
+ private void OnUnhandledExceptionClicked(object sender, EventArgs e)
{
- throw new Exception("This is a test exception, thrown from managed code in a MAUI app!");
+ throw new Exception("This is an unhanded test exception, thrown from managed code in a MAUI app!");
+ }
+
+ private void OnCapturedExceptionClicked(object sender, EventArgs e)
+ {
+ try
+ {
+ throw new Exception("This is a captured test exception, thrown from managed code in a MAUI app!");
+ }
+ catch (Exception ex)
+ {
+ SentrySdk.CaptureException(ex);
+ }
}
}
diff --git a/samples/Sentry.Samples.Maui/MauiProgram.cs b/samples/Sentry.Samples.Maui/MauiProgram.cs
index 5e063b934d..a885ec832c 100644
--- a/samples/Sentry.Samples.Maui/MauiProgram.cs
+++ b/samples/Sentry.Samples.Maui/MauiProgram.cs
@@ -8,6 +8,8 @@ public static MauiApp CreateMauiApp() =>
.UseSentry(options =>
{
options.Dsn = "https://eb18e953812b41c3aeb042e666fd3b5c@o447951.ingest.sentry.io/5428537";
+ options.Debug = true;
+ options.MaxBreadcrumbs = int.MaxValue; // TODO: reduce breadcrumbs, remove this
})
.ConfigureFonts(fonts =>
{
diff --git a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj
index 4ae723a232..d83bb06da0 100644
--- a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj
+++ b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj
@@ -63,7 +63,10 @@
-
+
diff --git a/src/Sentry.Maui/Constants.cs b/src/Sentry.Maui/Internal/Constants.cs
similarity index 91%
rename from src/Sentry.Maui/Constants.cs
rename to src/Sentry.Maui/Internal/Constants.cs
index cc59423a3f..566633fb34 100644
--- a/src/Sentry.Maui/Constants.cs
+++ b/src/Sentry.Maui/Internal/Constants.cs
@@ -1,6 +1,6 @@
using System.Reflection;
-namespace Sentry.Maui;
+namespace Sentry.Maui.Internal;
internal static class Constants
{
diff --git a/src/Sentry.Maui/Internal/Disposer.cs b/src/Sentry.Maui/Internal/Disposer.cs
new file mode 100644
index 0000000000..25e88862e5
--- /dev/null
+++ b/src/Sentry.Maui/Internal/Disposer.cs
@@ -0,0 +1,23 @@
+namespace Sentry.Maui.Internal;
+
+// This is a helper we register as a singleton on the service provider.
+// It allows us to register other items that should be disposed when the service provider disposes.
+// TODO: There might be something like this built-in to .NET already. Investigate and replace if so.
+
+internal class Disposer : IDisposable
+{
+ private readonly List _disposables = new();
+
+ public void Register(IDisposable disposable)
+ {
+ _disposables.Add(disposable);
+ }
+
+ public void Dispose()
+ {
+ foreach (var disposable in _disposables)
+ {
+ disposable.Dispose();
+ }
+ }
+}
diff --git a/src/Sentry.Maui/Internal/Extensions.cs b/src/Sentry.Maui/Internal/Extensions.cs
new file mode 100644
index 0000000000..f547c7c1dc
--- /dev/null
+++ b/src/Sentry.Maui/Internal/Extensions.cs
@@ -0,0 +1,70 @@
+namespace Sentry.Maui.Internal;
+
+internal static class Extensions
+{
+ public static void AddBreadcrumbForEvent(this IHub hub,
+ object? sender,
+ string eventName,
+ string? type,
+ string? category,
+ Action>? addExtraData)
+ => hub.AddBreadcrumbForEvent(sender, eventName, type, category, default, addExtraData);
+
+ public static void AddBreadcrumbForEvent(this IHub hub,
+ object? sender,
+ string eventName,
+ string? type = null,
+ string? category = null,
+ BreadcrumbLevel level = default,
+ Action>? addExtraData = null)
+ {
+ var data = new Dictionary();
+ if (sender is Element element)
+ {
+ data.AddElementInfo(element, null);
+ }
+
+ addExtraData?.Invoke(data);
+
+ var message = sender != null ? $"{sender.GetType().Name}.{eventName}" : eventName;
+ hub.AddBreadcrumb(message, category, type, data, level);
+ }
+
+ public static void AddElementInfo(this IDictionary data, Element? element, string? property)
+ {
+ if (element is null)
+ {
+ return;
+ }
+
+ var typeName = element.GetType().Name;
+ var prefix = (property ?? typeName) + ".";
+
+ if (property != null)
+ {
+ data.Add(property, typeName);
+ }
+
+ // The element ID seems to be mostly useless noise
+ //data.Add(prefix + nameof(element.Id), element.Id.ToString());
+
+ if (element.StyleId != null)
+ {
+ // The StyleId correlates to the element's name if one is set in XAML
+ // TODO: Is there a better way to get this?
+ data.Add(prefix + "Name", element.StyleId);
+ }
+
+ if (element is ITitledElement { Title: { } } titledElement)
+ {
+ // TODO: Scrub PII ?
+ data.Add(prefix + nameof(titledElement.Title), titledElement.Title);
+ }
+
+ if (element is IText { Text: { } } textElement)
+ {
+ // TODO: Scrub PII ?
+ data.Add(prefix + nameof(textElement.Text), textElement.Text);
+ }
+ }
+}
diff --git a/src/Sentry.Maui/Internal/IMauiEventsBinder.cs b/src/Sentry.Maui/Internal/IMauiEventsBinder.cs
new file mode 100644
index 0000000000..620744f414
--- /dev/null
+++ b/src/Sentry.Maui/Internal/IMauiEventsBinder.cs
@@ -0,0 +1,6 @@
+namespace Sentry.Maui.Internal;
+
+internal interface IMauiEventsBinder
+{
+ void BindMauiEvents();
+}
diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs
new file mode 100644
index 0000000000..97432cca1c
--- /dev/null
+++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs
@@ -0,0 +1,312 @@
+using System.Globalization;
+using System.Reflection;
+using Microsoft.Extensions.Options;
+using Sentry.Extensibility;
+
+namespace Sentry.Maui.Internal;
+
+internal class MauiEventsBinder : IMauiEventsBinder
+{
+ private readonly IApplication _application;
+ private readonly IHub _hub;
+ private readonly SentryMauiOptions _options;
+
+ // https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types
+ // https://github.com/getsentry/sentry/blob/master/static/app/types/breadcrumbs.tsx
+ private const string NavigationType = "navigation";
+ private const string SystemType = "system";
+ private const string UserType = "user";
+ private const string HandlersCategory = "ui.handlers";
+ private const string LifecycleCategory = "ui.lifecycle";
+ private const string NavigationCategory = "navigation";
+ private const string RenderingCategory = "ui.rendering";
+ private const string UserActionCategory = "ui.useraction";
+
+ // This list should contain all types that we have explicitly added handlers for their events.
+ // Any elements that are not in this list will have their events discovered by reflection.
+ private static readonly HashSet ExplicitlyHandledTypes = new()
+ {
+ typeof(Element),
+ typeof(BindableObject),
+ typeof(Application),
+ typeof(Window),
+ typeof(Shell),
+ typeof(Page),
+ typeof(Button),
+ };
+
+ public MauiEventsBinder(IApplication application, IHub hub, IOptions options)
+ {
+ _application = application;
+ _hub = hub;
+ _options = options.Value;
+ }
+
+ public void BindMauiEvents()
+ {
+ // Bind to the MAUI application events in a real application (not when testing)
+ if (_application is not Application application)
+ {
+ return;
+ }
+
+ BindApplicationEvents(application);
+ }
+
+ private void BindApplicationEvents(Application application)
+ {
+ // Attach element events to all descendents as they are added to the application.
+ application.DescendantAdded += (_, e) =>
+ {
+ // All elements have a set of common events we can hook
+ BindElementEvents(e.Element);
+
+ // We'll use reflection to attach to other events
+ // This allows us to attach to events from custom controls
+ BindReflectedEvents(e.Element);
+
+ // We can also attach to specific events on built-in controls
+ // Be sure to update ExplicitlyHandledTypes when adding to this list
+ switch (e.Element)
+ {
+ case Window window:
+ BindWindowEvents(window);
+ break;
+ case Shell shell:
+ BindShellEvents(shell);
+ break;
+ case Page page:
+ BindPageEvents(page);
+ break;
+ case Button button:
+ BindButtonEvents(button);
+ break;
+
+ // TODO: Attach to specific events on more control types
+ }
+ };
+
+ // The application is an element itself, so attach element events here also.
+ BindElementEvents(application);
+
+ // Navigation events
+ application.ModalPopping += (sender, e) =>
+ _hub.AddBreadcrumbForEvent(sender, nameof(Application.ModalPopping), NavigationType, NavigationCategory,
+ data => data.AddElementInfo(e.Modal, nameof(e.Modal)));
+ application.ModalPopped += (sender, e) =>
+ _hub.AddBreadcrumbForEvent(sender, nameof(Application.ModalPopped), NavigationType, NavigationCategory,
+ data => data.AddElementInfo(e.Modal, nameof(e.Modal)));
+ application.ModalPushing += (sender, e) =>
+ _hub.AddBreadcrumbForEvent(sender, nameof(Application.ModalPushing), NavigationType, NavigationCategory,
+ data => data.AddElementInfo(e.Modal, nameof(e.Modal)));
+ application.ModalPushed += (sender, e) =>
+ _hub.AddBreadcrumbForEvent(sender, nameof(Application.ModalPushed), NavigationType, NavigationCategory,
+ data => data.AddElementInfo(e.Modal, nameof(e.Modal)));
+ application.PageAppearing += (sender, page) =>
+ _hub.AddBreadcrumbForEvent(sender, nameof(Application.PageAppearing), NavigationType, NavigationCategory,
+ data => data.AddElementInfo(page, nameof(Page)));
+ application.PageDisappearing += (sender, page) =>
+ _hub.AddBreadcrumbForEvent(sender, nameof(Application.PageDisappearing), NavigationType, NavigationCategory,
+ data => data.AddElementInfo(page, nameof(Page)));
+
+ // Theme changed event
+ // https://docs.microsoft.com/dotnet/maui/user-interface/system-theme-changes#react-to-theme-changes
+ application.RequestedThemeChanged += (sender, e) =>
+ _hub.AddBreadcrumbForEvent(sender, nameof(Application.RequestedThemeChanged), SystemType, RenderingCategory,
+ data => data.Add(nameof(e.RequestedTheme), e.RequestedTheme.ToString()));
+ }
+
+ private void BindReflectedEvents(Element element)
+ {
+ // This reflects over the element's events, and attaches to any that
+ // are *NOT* declared by types in the ExplicitlyHandledTypes list.
+
+ var elementType = element.GetType();
+ var events = elementType.GetEvents(BindingFlags.Instance | BindingFlags.Public);
+ foreach (var eventInfo in events.Where(e => !ExplicitlyHandledTypes.Contains(e.DeclaringType!)))
+ {
+ Action