Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8d40272
feat: Auto-create traces for MAUI navigation events
jamescrosswell Apr 2, 2026
4041119
Format code
getsentry-bot Apr 2, 2026
09760b3
Discard UI event transactions without child spans
jamescrosswell Apr 7, 2026
3a8c8f0
missing verify files
jamescrosswell Apr 7, 2026
86a36db
Clean up duplicate tests
jamescrosswell Apr 7, 2026
e02ac47
Fix scope issue for detecting manual transactions
jamescrosswell Apr 7, 2026
e0145f4
Fix race conditions
jamescrosswell Apr 7, 2026
67c36c3
Fix no transactions being created when user has a transaction on the …
jamescrosswell Apr 7, 2026
2b71e0c
Address test gaps
jamescrosswell Apr 7, 2026
430ec80
Matched tests with Android implementation
jamescrosswell Apr 7, 2026
54b3d45
Review feedback
jamescrosswell Apr 7, 2026
5f73295
Merge remote-tracking branch 'origin/main' into maui-transactions-5109
jamescrosswell Apr 8, 2026
a3b5e22
Format code
getsentry-bot Apr 8, 2026
0d5a5f4
Windows verify tests
jamescrosswell Apr 8, 2026
0418c99
Add runtime guard to ensure HubAdaptes is never set to SentrySdk.Curr…
jamescrosswell Apr 8, 2026
2995289
Fix https://github.com/getsentry/sentry-dotnet/pull/5111#discussion_r…
jamescrosswell Apr 8, 2026
b75839b
Change locking mechanism on _hasFinished to prevent adding spans to f…
jamescrosswell Apr 9, 2026
b9e4ff1
Renamed file to match the class name
jamescrosswell Apr 9, 2026
7a0395a
Fix threading issues around idleTimer
jamescrosswell Apr 9, 2026
a0778cc
Merge remote-tracking branch 'origin/main' into maui-transactions-5109
jamescrosswell Apr 16, 2026
00c1466
feat: Add traces for UI interractions on Android
jamescrosswell Apr 17, 2026
fa04539
Initial investigation
jamescrosswell Apr 20, 2026
06b9c0e
Use pushing/pushed instead of pushed/popped
jamescrosswell Apr 20, 2026
8c61617
Implement popping/popped
jamescrosswell Apr 20, 2026
ac1b0b8
Implement pop.cancelled for shell navigation
jamescrosswell Apr 20, 2026
d89400b
Removed modal event handlers (duplicates shell navigating stuff)
jamescrosswell Apr 20, 2026
bc1c8ab
Generalised and added for ImageButton as well
jamescrosswell Apr 20, 2026
a18ae26
Added demo for ImageButton
jamescrosswell Apr 21, 2026
58cdab1
Tidied up the AI mess
jamescrosswell Apr 21, 2026
d53cd9f
Fixed shell tests
jamescrosswell Apr 22, 2026
5e5f2f5
Fix Window tests
jamescrosswell Apr 22, 2026
ae5af48
Revert unintentional changes to Window_UnbindPopCanceled tests
jamescrosswell Apr 22, 2026
cd8b52b
Revert unintentional changes to Shell breadcrumb tests
jamescrosswell Apr 22, 2026
e88f45c
Typo
jamescrosswell Apr 22, 2026
19db7e2
.
jamescrosswell Apr 22, 2026
7591ce5
Review feedback from Warden
jamescrosswell Apr 22, 2026
e530669
Format code
getsentry-bot Apr 22, 2026
a27d4d0
Avoid childless transaction capture on window stop
jamescrosswell Apr 29, 2026
20badda
Clear ui transaction from scope when it's replaced
jamescrosswell Apr 30, 2026
1256a19
Fixed visual file hierarchy
jamescrosswell Apr 30, 2026
cf6ff42
Avoid unecessary lock if _idleTimeout is null
jamescrosswell Apr 30, 2026
b9d2bf5
Merge remote-tracking branch 'origin/main' into maui-trace-ui-5116
jamescrosswell Apr 30, 2026
a529783
Fix race condition
jamescrosswell Apr 30, 2026
47730ea
Prevent unintentional reset of the PropagationContext
jamescrosswell Apr 30, 2026
58982fa
Add unit test to prevent regression
jamescrosswell Apr 30, 2026
a5e40d9
Format code
getsentry-bot Apr 30, 2026
4a1d0ec
fix: prevent race condition when span count approaches the 1000-span …
jamescrosswell May 13, 2026
d315120
Fixed IsSampled when transaction already finished
jamescrosswell May 14, 2026
5b29f9d
fix(maui): add missing GetSpan mock in OnShellOnNavigated_FinishesNav…
jamescrosswell May 15, 2026
f84cd2d
Refactored ResetIdleTimeout to internal interface to prevent breaking…
jamescrosswell May 17, 2026
af8a3c5
Merge remote-tracking branch 'origin/maui-trace-ui-5116' into maui-tr…
jamescrosswell May 17, 2026
3185873
Fix mock in unit test
jamescrosswell May 18, 2026
b74a6c3
Fix https://github.com/getsentry/sentry-dotnet/pull/5138#discussion_r…
jamescrosswell May 18, 2026
4b482a7
test: update API approval snapshot for Net4_8
jamescrosswell May 18, 2026
e13985b
fix: hide ResetIdleTimeout from public API via explicit interface impl
jamescrosswell May 18, 2026
f5d222e
fix: hide DisabledHub.StartTransaction(idleTimeout) via explicit inte…
jamescrosswell May 18, 2026
f5d5bb4
Fix TransactionTracer concurrency for Maui Transactions (#5248)
jamescrosswell May 21, 2026
8dcb541
Merge remote-tracking branch 'origin/maui-trace-ui-5116' into spike/m…
jamescrosswell Jul 13, 2026
389600f
spike: MAUI idle transactions over the Activity shim (Q1 - battle test)
jamescrosswell Jul 13, 2026
e2432fb
spike: MAUI idle transactions directly over Activities (Q2 - remove t…
jamescrosswell Jul 13, 2026
ece5dad
Format code
getsentry-bot Jul 13, 2026
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
3 changes: 2 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"WebFetch(domain:docs.sentry.io)",
"WebFetch(domain:develop.sentry.dev)",
"Bash(grep:*)",
"Bash(mv:*)"
"Bash(mv:*)",
"Bash(pwsh ./scripts/accept-verifier-changes.ps1)"
],
"deny": []
},
Expand Down
4 changes: 4 additions & 0 deletions samples/Sentry.Samples.Maui/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public static MauiApp CreateMauiApp()
// capture a certain percentage)
options.TracesSampleRate = 1.0F;

// Automatically create traces for navigation and user-interaction events
options.EnableAutoTransactions = true;

// Automatically create traces for async relay commands in the MVVM Community Toolkit
options.AddCommunityToolkitIntegration();

Expand Down Expand Up @@ -77,6 +80,7 @@ public static MauiApp CreateMauiApp()
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
fonts.AddFont("fa-solid-900.ttf", "FontAwesomeSolid");
});

// Configure Logging, including Structured Logs sent to Sentry (see 'EnableLogs = true')
Expand Down
Binary file not shown.
12 changes: 10 additions & 2 deletions samples/Sentry.Samples.Maui/SubmitFeedback.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@
<Button Text="Attach Screenshot" Clicked="OnAttachScreenshotClicked" Margin="0,20,0,0" />

<StackLayout Orientation="Horizontal" HorizontalOptions="EndAndExpand" Margin="0,20,0,0" Spacing="10">
<Button Text="Cancel" Clicked="OnCancelClicked" BackgroundColor="Red" TextColor="White" />
<Button Text="Submit" Clicked="OnSubmitClicked" BackgroundColor="Green" TextColor="White" />
<ImageButton x:Name="CancelBtn" Clicked="OnCancelClicked" BackgroundColor="Red" HeightRequest="44" WidthRequest="44" Padding="10" SemanticProperties.Description="Cancel">
<ImageButton.Source>
<FontImageSource FontFamily="FontAwesomeSolid" Glyph="&#xF00D;" Color="White" Size="18" />
</ImageButton.Source>
</ImageButton>
<ImageButton x:Name="SubmitBtn" Clicked="OnSubmitClicked" BackgroundColor="Green" HeightRequest="44" WidthRequest="44" Padding="10" SemanticProperties.Description="Submit">
<ImageButton.Source>
<FontImageSource FontFamily="FontAwesomeSolid" Glyph="&#xF00C;" Color="White" Size="18" />
</ImageButton.Source>
</ImageButton>
</StackLayout>
</StackLayout>
</ContentPage.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ internal static class ShimKeys
{
public const string SpanStatus = "sentry.shim.status";
public const string CustomSamplingContext = "sentry.shim.custom_sampling_context";
public const string IdleTimeout = "sentry.shim.idle_timeout";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Alias required because Android TFMs of the core Sentry package otherwise see an ambiguous reference
// between System.Diagnostics.Activity (global using) and Android.App.Activity (Android implicit usings).
using Activity = System.Diagnostics.Activity;

namespace Sentry.Internal.Tracing;

/// <summary>
/// The API surface Sentry's own integrations use when instrumenting directly with Activities (i.e. without
/// the <see cref="ActivityTransactionShim"/>). These helpers cover the Sentry-specific semantics that raw
/// <see cref="ActivitySource"/> calls don't express: starting an independent root (transaction), idle
/// timeouts, rich span statuses.
/// </summary>
internal static class ActivityTracingExtensions
{
/// <summary>
/// Starts an Activity that is an independent trace root - the Sentry-transaction equivalent.
/// Detaches from any ambient Activity first (Sentry semantics: a transaction begins a new trace),
/// and optionally carries an idle timeout, which the <see cref="SentryActivityProcessor"/> hands to
/// the shadow tracer so the transaction auto-finishes (or is discarded when trivial) after inactivity.
/// </summary>
public static Activity? StartRootActivity(
this ActivitySource source, string operation, string name, TimeSpan? idleTimeout = null)
{
Activity.Current = null;

var activity = source.CreateActivity(operation, ActivityKind.Internal);
if (activity is null)
{
// No listener is subscribed to this source.
return null;
}

activity.DisplayName = name;
if (idleTimeout is not null)
{
activity.SetFused(ShimKeys.IdleTimeout, idleTimeout);
}

activity.Start();
return activity;
}

/// <summary>
/// Resets the idle timeout of a root Activity started with an idle timeout (see
/// <see cref="StartRootActivity"/>), e.g. on user interaction.
/// </summary>
public static void ResetIdleTimeout(this Activity activity) =>
(activity.GetFused<ISpan>() as IAutoTimeoutTracer)?.ResetIdleTimeout();

/// <summary>
/// Stops the Activity with a rich Sentry <see cref="SpanStatus"/> - statuses like Cancelled have no
/// <see cref="ActivityStatusCode"/> equivalent and travel via the fused side-channel instead.
/// </summary>
public static void Stop(this Activity activity, SpanStatus status)
{
activity.SetFused(ShimKeys.SpanStatus, status);
activity.SetStatus(status == SpanStatus.Ok ? ActivityStatusCode.Ok : ActivityStatusCode.Error);
activity.Stop();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Sentry.Internal.Tracing;
/// <c>Hub.StartTransaction</c> invokes (via <c>SentryOptions.ActivityShimFactory</c>) for Sentry-API
/// transactions when Activity-based tracing is enabled.
/// </summary>
internal sealed class ActivityTransactionShim : ActivitySpanShim, ITransactionTracer
internal sealed class ActivityTransactionShim : ActivitySpanShim, ITransactionTracer, IAutoTimeoutTracer
{
private readonly ITransactionTracer _inner;

Expand All @@ -33,7 +33,8 @@ private ActivityTransactionShim(Activity activity, ITransactionTracer inner, IHu
IHub hub,
ITransactionContext context,
IReadOnlyDictionary<string, object?> customSamplingContext,
DynamicSamplingContext? dynamicSamplingContext)
DynamicSamplingContext? dynamicSamplingContext,
TimeSpan? idleTimeout)
{
// Continue an inbound (remote) trace, propagating the upstream sampling decision.
ActivityContext parentContext = default;
Expand All @@ -44,16 +45,27 @@ private ActivityTransactionShim(Activity activity, ITransactionTracer inner, IHu
context.TraceId.AsActivityTraceId(), parentSpanId.AsActivitySpanId(), flags, isRemote: true);
}

// Sentry semantics: StartTransaction begins a new trace (unless explicitly continuing a remote one).
// Detach from any ambient Activity so the new activity does not become an implicit child of it -
// e.g. a second UI click must be an independent transaction, not a child of the (still idling) first.
var ambientActivity = Activity.Current;
Activity.Current = null;

var activity = SentryActivitySources.Shim.CreateActivity(
context.Operation, ActivityKind.Internal, parentContext);
if (activity is null)
{
// No listener is subscribed to the Sentry ActivitySource.
Activity.Current = ambientActivity;
return null;
}

activity.DisplayName = context.Name;
activity.SetFused(ShimKeys.CustomSamplingContext, customSamplingContext);
if (idleTimeout is not null)
{
activity.SetFused(ShimKeys.IdleTimeout, idleTimeout);
}
if (dynamicSamplingContext is not null)
{
activity.SetFused(dynamicSamplingContext);
Expand All @@ -65,10 +77,15 @@ private ActivityTransactionShim(Activity activity, ITransactionTracer inner, IHu
{
// The listener saw the activity but did not map it (e.g. the hub was disabled mid-flight).
activity.Dispose();
Activity.Current = ambientActivity;
return null;
}

var shim = new ActivityTransactionShim(activity, inner, hub);

// Note: out-of-band tracer finishes (idle timer) stop the backing Activity via the OnFinished
// hook the processor installs on every activity-derived transaction.

// The processor put the shadow tracer on the scope; replace it with the shim so that
// integrations calling hub.GetSpan()/scope.Transaction route child spans through Activities.
hub.ConfigureScope(static (scope, s) => scope.Transaction = s, shim);
Expand Down Expand Up @@ -167,6 +184,8 @@ public string? TransactionName

public SdkVersion Sdk => _inner.Sdk;

void IAutoTimeoutTracer.ResetIdleTimeout() => (_inner as IAutoTimeoutTracer)?.ResetIdleTimeout();

public string? Platform
{
get => _inner.Platform;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// Alias required because Android TFMs of the core Sentry package otherwise see an ambiguous reference
// between System.Diagnostics.Activity (global using) and Android.App.Activity (Android implicit usings).
using Activity = System.Diagnostics.Activity;

using Sentry.Extensibility;
using Sentry.Internal.Extensions;
using Sentry.Internal.OpenTelemetry;
using Activity = System.Diagnostics.Activity;

namespace Sentry.Internal.Tracing;

Expand Down Expand Up @@ -171,13 +170,25 @@ private void CreateRootSpan(Activity data)
var baggageHeader = data.Baggage.AsBaggageHeader();
var dynamicSamplingContext = data.GetFused<DynamicSamplingContext>()
?? baggageHeader.CreateDynamicSamplingContext(_replaySession);
var idleTimeout = data.GetFused<TimeSpan?>(ShimKeys.IdleTimeout);
var transaction = _hub.StartTransaction(
transactionContext, customSamplingContext, dynamicSamplingContext
transactionContext, customSamplingContext, dynamicSamplingContext, idleTimeout
);
if (transaction is TransactionTracer tracer)
{
tracer.Contexts.Trace.Origin = OpenTelemetryOrigin;
tracer.StartTimestamp = data.StartTimeUtc;

// If the tracer finishes out-of-band (e.g. its idle timer fires, capturing or discarding
// without the Activity lifecycle being involved), the backing Activity must be stopped too -
// otherwise it leaks and stays Activity.Current, silently re-parenting later spans.
tracer.OnFinished = () =>
{
if (!data.IsStopped)
{
data.Stop();
}
};
}
_hub.ConfigureScope(static (scope, transaction) => scope.Transaction = transaction, transaction);
transaction.SetFused(data);
Expand Down
6 changes: 6 additions & 0 deletions src/Sentry.Maui/BindableSentryMauiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ internal class BindableSentryMauiOptions : BindableSentryLoggingOptions
public bool? IncludeBackgroundingStateInBreadcrumbs { get; set; }
public bool? CreateElementEventsBreadcrumbs { get; set; } = false;
public bool? AttachScreenshot { get; set; }
public bool? EnableAutoTransactions { get; set; }
public TimeSpan? AutoTransactionIdleTimeout { get; set; }
public bool? EnableUserInteractionTracing { get; set; }

public void ApplyTo(SentryMauiOptions options)
{
Expand All @@ -19,5 +22,8 @@ public void ApplyTo(SentryMauiOptions options)
options.IncludeBackgroundingStateInBreadcrumbs = IncludeBackgroundingStateInBreadcrumbs ?? options.IncludeBackgroundingStateInBreadcrumbs;
options.CreateElementEventsBreadcrumbs = CreateElementEventsBreadcrumbs ?? options.CreateElementEventsBreadcrumbs;
options.AttachScreenshot = AttachScreenshot ?? options.AttachScreenshot;
options.EnableAutoTransactions = EnableAutoTransactions ?? options.EnableAutoTransactions;
options.AutoTransactionIdleTimeout = AutoTransactionIdleTimeout ?? options.AutoTransactionIdleTimeout;
options.EnableUserInteractionTracing = EnableUserInteractionTracing ?? options.EnableUserInteractionTracing;
}
}
16 changes: 16 additions & 0 deletions src/Sentry.Maui/Internal/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,20 @@ public static string ToStringOrTypeName(this object o)
var s = o.ToString();
return s == null || s == t.FullName ? t.Name : s;
}

internal static Page? FindContainingPage(this Element? element)
{
const int hopLimit = 32;
var hops = 0;
var current = element;
while (current is not null && hops++ < hopLimit)
{
if (current is Page page)
{
return page;
}
current = current.Parent;
}
return null;
}
}
Loading
Loading