diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12ae447ec3..c9627550f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@
### Fixes
- The HTTP instrumentation uses the span created for the outgoing request in the sentry-trace header, fixing the parent-child relationship between client and server ([#4264](https://github.com/getsentry/sentry-dotnet/pull/4264))
+- ExtraData not captured for Breadcrumbs in MauiEventsBinder ([#4254](https://github.com/getsentry/sentry-dotnet/pull/4254))
+ - NOTE: Required breaking changes to the public API of `Sentry.Maui.BreadcrumbEvent`, while keeping an _Obsolete_ constructor for backward compatibility.
- InvalidOperationException sending attachments on Android with LLVM enabled ([#4276](https://github.com/getsentry/sentry-dotnet/pull/4276))
### Dependencies
diff --git a/src/Sentry.Maui/BreadcrumbEvent.cs b/src/Sentry.Maui/BreadcrumbEvent.cs
new file mode 100644
index 0000000000..48a9252901
--- /dev/null
+++ b/src/Sentry.Maui/BreadcrumbEvent.cs
@@ -0,0 +1,71 @@
+namespace Sentry.Maui;
+
+///
+/// Argument to the OnBreadcrumbCreateCallback
+///
+public sealed class BreadcrumbEvent
+{
+ ///
+ /// The sender of the event, usually the control that triggered it.
+ ///
+ public object? Sender { get; }
+
+ ///
+ /// The event name (e.g. "Tapped", "Swiped", etc.)
+ ///
+ public string EventName { get; }
+
+ ///
+ /// Any extra data to be included in the breadcrumb. This would typically be event specific information (for example
+ /// it could include the X, Y coordinates of a tap event).
+ ///
+ public IEnumerable> ExtraData { get; }
+
+ ///
+ /// Creates a new BreadcrumbEvent
+ ///
+ public BreadcrumbEvent(object? sender, string eventName)
+ : this(sender, eventName, Array.Empty>())
+ {
+ }
+
+ ///
+ /// Creates a new BreadcrumbEvent
+ ///
+ public BreadcrumbEvent(
+ object? sender,
+ string eventName,
+ params IEnumerable> extraData)
+ {
+ Sender = sender;
+ EventName = eventName;
+ ExtraData = extraData;
+ }
+
+ ///
+ /// Creates a new BreadcrumbEvent
+ ///
+ public BreadcrumbEvent(
+ object? sender,
+ string eventName,
+ params IEnumerable<(string key, string value)> extraData) : this(sender, eventName, extraData.Select(
+ e => new KeyValuePair(e.key, e.value)))
+ {
+ }
+
+ ///
+ /// This constructor remains for backward compatibility.
+ ///
+ ///
+ ///
+ ///
+ [Obsolete("Use one of the other simpler constructors instead.")]
+ public BreadcrumbEvent(
+ object? sender,
+ string eventName,
+ IEnumerable<(string Key, string Value)>[] extraData) : this(sender, eventName, extraData.SelectMany(
+ x => x.Select(pair => new KeyValuePair(pair.Key, pair.Value)))
+ )
+ {
+ }
+}
diff --git a/src/Sentry.Maui/IMauiElementEventBinder.cs b/src/Sentry.Maui/IMauiElementEventBinder.cs
index fcb9d1a1cc..d2a41cabd7 100644
--- a/src/Sentry.Maui/IMauiElementEventBinder.cs
+++ b/src/Sentry.Maui/IMauiElementEventBinder.cs
@@ -21,15 +21,3 @@ public interface IMauiElementEventBinder
///
public void UnBind(VisualElement element);
}
-
-///
-/// Breadcrumb arguments
-///
-///
-///
-///
-public record BreadcrumbEvent(
- object? Sender,
- string EventName,
- params IEnumerable<(string Key, string Value)>[] ExtraData
-);
diff --git a/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs b/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs
index b938fdcb19..a8fe7fe85f 100644
--- a/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs
+++ b/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs
@@ -3,12 +3,12 @@ namespace Sentry.Maui.Internal;
///
public class MauiButtonEventsBinder : IMauiElementEventBinder
{
- private Action? addBreadcrumbCallback;
+ private Action? _addBreadcrumbCallback;
///
public void Bind(VisualElement element, Action addBreadcrumb)
{
- addBreadcrumbCallback = addBreadcrumb;
+ _addBreadcrumbCallback = addBreadcrumb;
if (element is Button button)
{
@@ -30,11 +30,11 @@ public void UnBind(VisualElement element)
}
private void OnButtonOnClicked(object? sender, EventArgs _)
- => addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Clicked)));
+ => _addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Clicked)));
private void OnButtonOnPressed(object? sender, EventArgs _)
- => addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Pressed)));
+ => _addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Pressed)));
private void OnButtonOnReleased(object? sender, EventArgs _)
- => addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Released)));
+ => _addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Released)));
}
diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs
index dd35058015..737902041a 100644
--- a/src/Sentry.Maui/Internal/MauiEventsBinder.cs
+++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs
@@ -132,7 +132,14 @@ internal void OnBreadcrumbCreateCallback(BreadcrumbEvent breadcrumb)
breadcrumb.Sender,
breadcrumb.EventName,
UserType,
- UserActionCategory
+ UserActionCategory,
+ extra =>
+ {
+ foreach (var (key, value) in breadcrumb.ExtraData)
+ {
+ extra[key] = value;
+ }
+ }
);
}
diff --git a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs
index f15a4d303a..f9c8f96c30 100644
--- a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs
+++ b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs
@@ -139,7 +139,7 @@ private static void OnPointerEnteredGesture(object? sender, PointerEventArgs e)
ToPointerData(e)
));
- private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) =>
+ private static (string Key, string Value)[] ToPointerData(PointerEventArgs e) =>
[
#if ANDROID
("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? string.Empty)
diff --git a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
index f608570b94..05a2136d62 100644
--- a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
+++ b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt
@@ -11,17 +11,20 @@ namespace Microsoft.Maui.Hosting
}
namespace Sentry.Maui
{
- public class BreadcrumbEvent : System.IEquatable
+ public sealed class BreadcrumbEvent
{
- public BreadcrumbEvent(object? Sender, string EventName, [System.Runtime.CompilerServices.TupleElementNames(new string[] {
+ public BreadcrumbEvent(object? sender, string eventName) { }
+ public BreadcrumbEvent(object? sender, string eventName, System.Collections.Generic.IEnumerable> extraData) { }
+ public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.TupleElementNames(new string[] {
+ "key",
+ "value"})] System.Collections.Generic.IEnumerable> extraData) { }
+ [System.Obsolete("Use one of the other simpler constructors instead.")]
+ public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.TupleElementNames(new string[] {
"Key",
- "Value"})] params System.Collections.Generic.IEnumerable>[] ExtraData) { }
- public string EventName { get; init; }
- [System.Runtime.CompilerServices.TupleElementNames(new string[] {
- "Key",
- "Value"})]
- public System.Collections.Generic.IEnumerable>[] ExtraData { get; init; }
- public object? Sender { get; init; }
+ "Value"})] System.Collections.Generic.IEnumerable>[] extraData) { }
+ public string EventName { get; }
+ public System.Collections.Generic.IEnumerable> ExtraData { get; }
+ public object? Sender { get; }
}
public interface IMauiElementEventBinder
{
diff --git a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt
index f608570b94..12ae9dcc31 100644
--- a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt
+++ b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt
@@ -11,17 +11,20 @@ namespace Microsoft.Maui.Hosting
}
namespace Sentry.Maui
{
- public class BreadcrumbEvent : System.IEquatable
+ public sealed class BreadcrumbEvent
{
- public BreadcrumbEvent(object? Sender, string EventName, [System.Runtime.CompilerServices.TupleElementNames(new string[] {
+ public BreadcrumbEvent(object? sender, string eventName) { }
+ public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.ParamCollection] System.Collections.Generic.IEnumerable> extraData) { }
+ public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.ParamCollection] [System.Runtime.CompilerServices.TupleElementNames(new string[] {
+ "key",
+ "value"})] System.Collections.Generic.IEnumerable> extraData) { }
+ [System.Obsolete("Use one of the other simpler constructors instead.")]
+ public BreadcrumbEvent(object? sender, string eventName, [System.Runtime.CompilerServices.TupleElementNames(new string[] {
"Key",
- "Value"})] params System.Collections.Generic.IEnumerable>[] ExtraData) { }
- public string EventName { get; init; }
- [System.Runtime.CompilerServices.TupleElementNames(new string[] {
- "Key",
- "Value"})]
- public System.Collections.Generic.IEnumerable>[] ExtraData { get; init; }
- public object? Sender { get; init; }
+ "Value"})] System.Collections.Generic.IEnumerable>[] extraData) { }
+ public string EventName { get; }
+ public System.Collections.Generic.IEnumerable> ExtraData { get; }
+ public object? Sender { get; }
}
public interface IMauiElementEventBinder
{
diff --git a/test/Sentry.Maui.Tests/BreadcrumbEventTests.cs b/test/Sentry.Maui.Tests/BreadcrumbEventTests.cs
new file mode 100644
index 0000000000..c92938adde
--- /dev/null
+++ b/test/Sentry.Maui.Tests/BreadcrumbEventTests.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using FluentAssertions;
+using Xunit;
+
+namespace Sentry.Maui.Tests;
+
+public class BreadcrumbEventTests
+{
+ [Fact]
+ public void BreadcrumbEvent_OldConstructor_EquivalentToNewConstructor()
+ {
+ // Arrange
+ var sender = new object();
+ var eventName = "TestEvent";
+
+ // Act
+ IEnumerable<(string Key, string Value)>[] extraData = [[("key1", "value1")], [("key2", "value2")]];
+#pragma warning disable CS0618 // Type or member is obsolete
+ var oldEvent = new BreadcrumbEvent(sender, eventName, extraData);
+#pragma warning restore CS0618 // Type or member is obsolete
+ var newEvent = new BreadcrumbEvent(sender, eventName, ("key1", "value1"), ("key2", "value2"));
+
+ // Assert
+ oldEvent.Sender.Should().Be(newEvent.Sender);
+ oldEvent.EventName.Should().Be(newEvent.EventName);
+ oldEvent.ExtraData.Should().BeEquivalentTo(newEvent.ExtraData);
+ }
+}
diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.Button.cs b/test/Sentry.Maui.Tests/MauiButtonEventsBinderTests.cs
similarity index 92%
rename from test/Sentry.Maui.Tests/MauiEventsBinderTests.Button.cs
rename to test/Sentry.Maui.Tests/MauiButtonEventsBinderTests.cs
index 9fe7e2922e..aedaf9cd3a 100644
--- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.Button.cs
+++ b/test/Sentry.Maui.Tests/MauiButtonEventsBinderTests.cs
@@ -2,8 +2,10 @@
namespace Sentry.Maui.Tests;
-public partial class MauiEventsBinderTests
+public class MauiButtonEventsBinderTests
{
+ private readonly MauiEventsBinderFixture _fixture = new(new MauiButtonEventsBinder());
+
[Theory]
[InlineData(nameof(Button.Clicked))]
[InlineData(nameof(Button.Pressed))]
diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderFixture.cs b/test/Sentry.Maui.Tests/MauiEventsBinderFixture.cs
new file mode 100644
index 0000000000..38cb143a7a
--- /dev/null
+++ b/test/Sentry.Maui.Tests/MauiEventsBinderFixture.cs
@@ -0,0 +1,33 @@
+using Sentry.Maui.Internal;
+
+namespace Sentry.Maui.Tests;
+
+internal class MauiEventsBinderFixture
+{
+ public IHub Hub { get; }
+
+ public MauiEventsBinder Binder { get; }
+
+ public Scope Scope { get; } = new();
+
+ public SentryMauiOptions Options { get; } = new();
+
+ public MauiEventsBinderFixture(params IEnumerable elementEventBinders)
+ {
+ Hub = Substitute.For();
+ Hub.SubstituteConfigureScope(Scope);
+
+ Scope.Transaction = Substitute.For();
+
+ Options.Debug = true;
+ var logger = Substitute.For();
+ logger.IsEnabled(Arg.Any()).Returns(true);
+ Options.DiagnosticLogger = logger;
+ var options = Microsoft.Extensions.Options.Options.Create(Options);
+ Binder = new MauiEventsBinder(
+ Hub,
+ options,
+ elementEventBinders
+ );
+ }
+}
diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs
index 9b050b8bdb..0de536dcfd 100644
--- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs
+++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs
@@ -4,41 +4,35 @@ namespace Sentry.Maui.Tests;
public partial class MauiEventsBinderTests
{
- private class Fixture
- {
- public IHub Hub { get; }
+ private readonly MauiEventsBinderFixture _fixture = new();
- public MauiEventsBinder Binder { get; }
+ // Most of the tests for this class are in separate partial class files for better organisation
- public Scope Scope { get; } = new();
+ [Fact]
+ public void OnBreadcrumbCreateCallback_CreatesBreadcrumb()
+ {
+ // Arrange
+ var breadcrumbEvent = new BreadcrumbEvent(new object(), "TestName",
+ ("key1", "value1"), ("key2", "value2")
+ );
- public SentryMauiOptions Options { get; } = new();
+ // Act
+ _fixture.Binder.OnBreadcrumbCreateCallback(breadcrumbEvent);
- public Fixture()
+ // Assert
+ using (new AssertionScope())
{
- Hub = Substitute.For();
- Hub.SubstituteConfigureScope(Scope);
-
- Scope.Transaction = Substitute.For();
-
- Options.Debug = true;
- var logger = Substitute.For();
- logger.IsEnabled(Arg.Any()).Returns(true);
- Options.DiagnosticLogger = logger;
- var options = Microsoft.Extensions.Options.Options.Create(Options);
- Binder = new MauiEventsBinder(
- Hub,
- options,
- [
- new MauiButtonEventsBinder(),
- new MauiImageButtonEventsBinder(),
- new MauiGestureRecognizerEventsBinder()
- ]
- );
+ var crumb = Assert.Single(_fixture.Scope.Breadcrumbs);
+ Assert.Equal("Object.TestName", crumb.Message);
+ Assert.Equal(BreadcrumbLevel.Info, crumb.Level);
+ Assert.Equal(MauiEventsBinder.UserType, crumb.Type);
+ Assert.Equal(MauiEventsBinder.UserActionCategory, crumb.Category);
+ Assert.NotNull(crumb.Data);
+ Assert.Equal(breadcrumbEvent.ExtraData.Count(), crumb.Data.Count);
+ foreach (var (key, value) in breadcrumbEvent.ExtraData)
+ {
+ crumb.Data.Should().Contain(kvp => kvp.Key == key && kvp.Value == value);
+ }
}
}
-
- private readonly Fixture _fixture = new();
-
- // Tests are in partial class files for better organization
}
diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs b/test/Sentry.Maui.Tests/MauiGestureRecognizerEventsBinderTests.cs
similarity index 96%
rename from test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs
rename to test/Sentry.Maui.Tests/MauiGestureRecognizerEventsBinderTests.cs
index f9d8cefe45..9e81385ca2 100644
--- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs
+++ b/test/Sentry.Maui.Tests/MauiGestureRecognizerEventsBinderTests.cs
@@ -2,8 +2,10 @@
namespace Sentry.Maui.Tests;
-public partial class MauiEventsBinderTests
+public class MauiGestureRecognizerEventsBinderTests
{
+ private readonly MauiEventsBinderFixture _fixture = new(new MauiGestureRecognizerEventsBinder());
+
[SkippableFact]
public void TapGestureRecognizer_LifecycleEvents_AddsBreadcrumb()
{
diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.ImageButton.cs b/test/Sentry.Maui.Tests/MauiImageButtonEventsBinderTests.cs
similarity index 92%
rename from test/Sentry.Maui.Tests/MauiEventsBinderTests.ImageButton.cs
rename to test/Sentry.Maui.Tests/MauiImageButtonEventsBinderTests.cs
index 83f9d4aab5..f4b1360e9e 100644
--- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.ImageButton.cs
+++ b/test/Sentry.Maui.Tests/MauiImageButtonEventsBinderTests.cs
@@ -2,8 +2,10 @@
namespace Sentry.Maui.Tests;
-public partial class MauiEventsBinderTests
+public class MauiImageButtonEventsBinderTests
{
+ private readonly MauiEventsBinderFixture _fixture = new(new MauiImageButtonEventsBinder());
+
[Theory]
[InlineData(nameof(ImageButton.Clicked))]
[InlineData(nameof(ImageButton.Pressed))]