Skip to content

fix: Source context for class libraries when running on Android in Release mode #4294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 51 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d97b4cb
fix: Source context for class libraries when running on Android in Re…
jamescrosswell Jun 19, 2025
99ecda7
Update CHANGELOG.md
jamescrosswell Jun 19, 2025
eaf4382
Merge branch 'main' into multi-project-maui
jamescrosswell Jun 19, 2025
77a3836
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 8, 2025
0fae5db
Update CHANGELOG.md
jamescrosswell Jul 8, 2025
d6366f6
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 8, 2025
68674b6
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 8, 2025
be7ea6e
Update AndroidAssemblyStoreReaderV2.cs
jamescrosswell Jul 8, 2025
057489f
Update AndroidAssemblyStoreReaderV2.cs
jamescrosswell Jul 8, 2025
19f22ec
Update CachingTransportTests.cs
jamescrosswell Jul 9, 2025
50ab8f4
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 12, 2025
e322bef
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 13, 2025
544b354
Update Sentry.Maui.Device.TestApp.csproj
jamescrosswell Jul 13, 2025
6dd5218
Update device-tests-android.yml
jamescrosswell Jul 14, 2025
ce0a859
Exclude SentryMauiOptionsTests (testing)
jamescrosswell Jul 17, 2025
76ae028
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 17, 2025
a33c654
Update CHANGELOG.md
jamescrosswell Jul 17, 2025
2b15fda
Try FakeReliableNetworkStatusListener in SentryMauiOptionsTests
jamescrosswell Jul 17, 2025
982ffe4
Disable all but one SentryMauiOptionsTests
jamescrosswell Jul 17, 2025
d68676c
Update SentryMauiOptionsTests.cs
jamescrosswell Jul 18, 2025
3a36d99
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 18, 2025
849d704
Try skipping event binders
jamescrosswell Jul 18, 2025
7bd5069
Update CachingTransportTests.cs
jamescrosswell Jul 18, 2025
a527401
Try removing callbacks
jamescrosswell Jul 20, 2025
882e6cc
Merge branch 'main' into multi-project-maui
jamescrosswell Jul 20, 2025
2d2e1b2
Moved default event binders into SentryMauiAppBuilderExtensions
jamescrosswell Jul 21, 2025
c58d2d9
Uncomment the other device tests again
jamescrosswell Jul 21, 2025
6686e9e
Renamed InjectedEventBinders to IntegrationEventBinders
jamescrosswell Jul 21, 2025
11f09e4
Update SentryMauiAppBuilderExtensions.cs
jamescrosswell Jul 21, 2025
8689ce3
Try commenting out MauiCommunityToolkitMvvmEventsBinderTests
jamescrosswell Jul 21, 2025
bc87f3e
Update Startup.cs
jamescrosswell Jul 21, 2025
88daf10
Back to just the SentrySdkTests
jamescrosswell Jul 21, 2025
eed38d9
Added LogLevelExtensionsTests & AndroidAssemblyReaderTests
jamescrosswell Jul 21, 2025
053831f
Added SentryMauiOptionsTests back again
jamescrosswell Jul 21, 2025
ba8e335
No SentryMauiOptionsTests but MauiCommunityToolkitMvvmEventsBinderTests
jamescrosswell Jul 21, 2025
b3fea09
Include half the Sentry.Maui.Tests
jamescrosswell Jul 21, 2025
00ff1f2
Disable the other half
jamescrosswell Jul 21, 2025
ac86208
Just api and options tests
jamescrosswell Jul 21, 2025
e8375ab
Update Sentry.Maui.Tests.csproj
jamescrosswell Jul 21, 2025
1a080dd
Include logcat and screenshot tests
jamescrosswell Jul 22, 2025
2d954bc
Add SentryMauiAppBuilderExtensionsTests
jamescrosswell Jul 22, 2025
f0d9a3c
Added MauiNetworkStatusListenerTests and BreadcrumbEventTests
jamescrosswell Jul 22, 2025
1dde8f7
Added back MauiEventsBinderTests
jamescrosswell Jul 22, 2025
acc5518
Reintroduce button binders
jamescrosswell Jul 22, 2025
614b33e
Reintroduce MauiGestureRecognizerEventsBinderTests
jamescrosswell Jul 22, 2025
da061fc
Reintroduce MauiVisualElementEventsBinderTests
jamescrosswell Jul 22, 2025
527e09e
Reintroduce SentryMauiLogcatsTests
jamescrosswell Jul 23, 2025
ce94206
Reintroduce CaptureException_RemoveScreenshot_NotContainsScreenshotAt…
jamescrosswell Jul 23, 2025
9346258
Reintroduce CaptureException_BeforeCaptureScreenshot_DisableCaptureAsync
jamescrosswell Jul 23, 2025
cb19808
Update MauiImageButtonEventsBinder.cs
jamescrosswell Jul 23, 2025
4e12078
Reintroduce/tweak CaptureException_AttachScreenshot_Threadsafe
jamescrosswell Jul 23, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes

- Source context for class libraries when running on Android in Release mode ([#4294](https://github.com/getsentry/sentry-dotnet/pull/4294))
- Native AOT: don't load SentryNative on unsupported platforms ([#4347](https://github.com/getsentry/sentry-dotnet/pull/4347))

### Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,19 @@ public static bool TryReadStore(string inputFile, IList<string> supportedAbis, D
return assembly;
}

// If the assembly name ends with .dll or .exe, try to find it without the extension.
if ((IsFileType(".dll") || IsFileType(".exe")) && FindBestAssembly(name[..^4], out assembly))
{
return assembly;
}

// Conversely, if there is no extension, try with the dll extension (sometimes required for class libraries).
// See: https://github.com/getsentry/sentry-dotnet/issues/4278#issuecomment-2986009125
if (!IsFileType(".dll") && !IsFileType(".exe") && FindBestAssembly(name + ".dll", out assembly))
{
return assembly;
}

return null;

bool IsFileType(string extension)
Expand All @@ -119,10 +127,12 @@ private bool FindBestAssembly(string name, out ExplorerStoreItem? explorerAssemb
{
if (explorer.AssembliesByName?.TryGetValue(name, out var assembly) is true)
{
_logger?.Invoke("Found best assembly {0} in APK AssemblyStore for target arch {1}", name, explorer.TargetArch);
explorerAssembly = new(explorer, assembly);
return true;
}
}
_logger?.Invoke("No best assembly for {0} in APK AssemblyStore", name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we have one logger but shouldn't this be a level Error log?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd have to refactor the AssemblyReader classes that we vendored in fomr MS in order to be able to do that (they use a different pattern for logging).

Probably the easiest would be to replace DebugLogger with our own IDiagnosticLogger... would involve quite a few changes, but then we've made so many changes to these already that the only way to merge in changes from new code is by hand. So a few more changes wouldn't hurt.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bruno-garcia one remaining question on this PR is whether we want to refactor all the vendored in code to use our IDiagnosticLogger instead of the DebugLogger delegate that is used currently:

public delegate void DebugLogger(string message, params object?[] args);

The existing delegate doesn't have any notion of "log levels" (so doesn't support what you suggested above).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't add that as a requirement to unblock this. But it's not unreasonable to add if it'll help us debug this better going forward

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k, I'll track that in a separate issue as I think we should get this fix out ASAP:

explorerAssembly = null;
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ private AssemblyStoreExplorer(Stream storeStream, string path, DebugLogger? logg
{
foreach (var item in Assemblies)
{
logger?.Invoke("Assembly {0} indexed from AssemblyStore {1}", item.Name, path);
dict.Add(item.Name, item);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class SentryOptionsExtensions
/// </summary>
public static SentryMauiOptions AddCommunityToolkitIntegration(this SentryMauiOptions options)
{
options.AddDefaultEventBinder<MauiCommunityToolkitMvvmEventsBinder>();
options.AddIntegrationEventBinder<MauiCommunityToolkitMvvmEventsBinder>();
return options;
}
}
1 change: 1 addition & 0 deletions src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public void Bind(VisualElement element, Action<BreadcrumbEvent> addBreadcrumb)
/// <inheritdoc />
public void UnBind(VisualElement element)
{
_addBreadcrumbCallback = null;
if (element is Button button)
{
button.Clicked -= OnButtonOnClicked;
Expand Down
11 changes: 6 additions & 5 deletions src/Sentry.Maui/Internal/MauiImageButtonEventsBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ namespace Sentry.Maui.Internal;
/// <inheritdoc />
public class MauiImageButtonEventsBinder : IMauiElementEventBinder
{
private Action<BreadcrumbEvent>? addBreadcrumbCallback;
private Action<BreadcrumbEvent>? _addBreadcrumbCallback;

/// <inheritdoc />
public void Bind(VisualElement element, Action<BreadcrumbEvent> addBreadcrumb)
{
addBreadcrumbCallback = addBreadcrumb;
_addBreadcrumbCallback = addBreadcrumb;

if (element is ImageButton image)
{
Expand All @@ -21,6 +21,7 @@ public void Bind(VisualElement element, Action<BreadcrumbEvent> addBreadcrumb)
/// <inheritdoc />
public void UnBind(VisualElement element)
{
_addBreadcrumbCallback = null;
if (element is ImageButton image)
{
image.Clicked -= OnButtonOnClicked;
Expand All @@ -31,11 +32,11 @@ public void UnBind(VisualElement element)


private void OnButtonOnClicked(object? sender, EventArgs _)
=> addBreadcrumbCallback?.Invoke(new(sender, nameof(ImageButton.Clicked)));
=> _addBreadcrumbCallback?.Invoke(new(sender, nameof(ImageButton.Clicked)));

private void OnButtonOnPressed(object? sender, EventArgs _)
=> addBreadcrumbCallback?.Invoke(new(sender, nameof(ImageButton.Pressed)));
=> _addBreadcrumbCallback?.Invoke(new(sender, nameof(ImageButton.Pressed)));

private void OnButtonOnReleased(object? sender, EventArgs _)
=> addBreadcrumbCallback?.Invoke(new(sender, nameof(ImageButton.Released)));
=> _addBreadcrumbCallback?.Invoke(new(sender, nameof(ImageButton.Released)));
}
10 changes: 8 additions & 2 deletions src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder,
services.AddSingleton<IConfigureOptions<SentryMauiOptions>, SentryMauiOptionsSetup>();
services.AddSingleton<Disposer>();

// Resolve the configured options and register any element event binders from these
// Add default event binders
services.AddSingleton<IMauiElementEventBinder, MauiButtonEventsBinder>();
services.AddSingleton<IMauiElementEventBinder, MauiImageButtonEventsBinder>();
services.AddSingleton<IMauiElementEventBinder, MauiGestureRecognizerEventsBinder>();
services.AddSingleton<IMauiElementEventBinder, MauiVisualElementEventsBinder>();

// Resolve the configured options and register any event binders that have been injected by integrations
var options = new SentryMauiOptions();
configureOptions?.Invoke(options);
foreach (var eventBinder in options.DefaultEventBinders)
foreach (var eventBinder in options.IntegrationEventBinders)
{
eventBinder.Register(services);
}
Expand Down
10 changes: 3 additions & 7 deletions src/Sentry.Maui/SentryMauiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,14 @@ public SentryMauiOptions()
#if !PLATFORM_NEUTRAL
CacheDirectoryPath = Microsoft.Maui.Storage.FileSystem.CacheDirectory;
#endif
AddDefaultEventBinder<MauiButtonEventsBinder>();
AddDefaultEventBinder<MauiImageButtonEventsBinder>();
AddDefaultEventBinder<MauiGestureRecognizerEventsBinder>();
AddDefaultEventBinder<MauiVisualElementEventsBinder>();
Copy link
Collaborator Author

@jamescrosswell jamescrosswell Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

internal List<IMauiElementEventBinderRegistration> DefaultEventBinders { get; } = [];
internal List<IMauiElementEventBinderRegistration> IntegrationEventBinders { get; } = [];

internal void AddDefaultEventBinder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TEventBinder>()
internal void AddIntegrationEventBinder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TEventBinder>()
where TEventBinder : class, IMauiElementEventBinder
{
DefaultEventBinders.Add(new MauiElementEventBinderRegistration<TEventBinder>());
IntegrationEventBinders.Add(new MauiElementEventBinderRegistration<TEventBinder>());
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
<ProjectReference Include="..\Sentry.Extensions.Logging.Tests\Sentry.Extensions.Logging.Tests.csproj"/>
<ProjectReference Include="..\Sentry.Maui.Tests\Sentry.Maui.Tests.csproj"/>
<ProjectReference Include="..\Sentry.Maui.CommunityToolkit.Mvvm.Tests\Sentry.Maui.CommunityToolkit.Mvvm.Tests.csproj" Condition="'$(_SentryIsNet9OrGreater)' == 'true'"/>
<ProjectReference Include="..\..\src\Sentry.SourceGenerators\Sentry.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion test/Sentry.Maui.Device.TestApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static MauiApp CreateMauiApp()
typeof(Sentry.Maui.CommunityToolkit.Mvvm.Tests.MauiCommunityToolkitMvvmEventsBinderTests).Assembly,
#endif
#if ANDROID
typeof(Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests).Assembly,
typeof(Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests).Assembly,
#endif
]);
var appBuilder = MauiApp.CreateBuilder()
Expand Down
27 changes: 15 additions & 12 deletions test/Sentry.Maui.Tests/SentryMauiOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,50 @@ namespace Sentry.Maui.Tests;

public class SentryMauiOptionsTests
{
private static SentryMauiOptions GetSut() => new()
{
NetworkStatusListener = FakeReliableNetworkStatusListener.Instance
};

[Fact]
public void IncludeTextInBreadcrumbs_Default()
{
var options = new SentryMauiOptions();
var options = GetSut();
Assert.False(options.IncludeTextInBreadcrumbs);
}

[Fact]
public void IncludeTitleInBreadcrumbs_Default()
{
var options = new SentryMauiOptions();
var options = GetSut();
Assert.False(options.IncludeTitleInBreadcrumbs);
}

[Fact]
public void IncludeBackgroundingStateInBreadcrumbs_Default()
{
var options = new SentryMauiOptions();
var options = GetSut();
Assert.False(options.IncludeBackgroundingStateInBreadcrumbs);
}

[Fact]
public void AutoSessionTracking_Default()
{
var options = new SentryMauiOptions();
var options = GetSut();
Assert.True(options.AutoSessionTracking);
}

[Fact]
public void DetectStartupTime_Default()
{
var options = new SentryMauiOptions();
var options = GetSut();
Assert.Equal(StartupTimeDetectionMode.Fast, options.DetectStartupTime);
}

[Fact]
public void CacheDirectoryPath_Default()
{
var options = new SentryMauiOptions();
var options = GetSut();

#if PLATFORM_NEUTRAL
Assert.Null(options.CacheDirectoryPath);
Expand All @@ -58,7 +63,7 @@ public void HandlerStrategy_Default()
{
// Arrange
var expected = Android.LogCatIntegrationType.None;
var options = new SentryMauiOptions();
var options = GetSut();

// Assert
Assert.Equal(expected, options.Android.LogCatIntegration);
Expand All @@ -69,7 +74,7 @@ public void HandlerStrategy_Set()
{
// Arrange
var expected = Android.LogCatIntegrationType.None;
var options = new SentryMauiOptions();
var options = GetSut();

// Act
options.Android.LogCatIntegration = Android.LogCatIntegrationType.All;
Expand All @@ -83,7 +88,7 @@ public void HandlerStrategy_Set()
public void BeforeCaptureScreenshot_Set()
{
// Arrange
var options = new SentryMauiOptions();
var options = GetSut();
options.AttachScreenshot = true;

// Act
Expand All @@ -94,18 +99,16 @@ public void BeforeCaptureScreenshot_Set()

// Assert
Assert.NotNull(options.BeforeCaptureInternal);

}

[Fact]
public void BeforeCaptureScreenshot_NotSet()
{
// Arrange
var options = new SentryMauiOptions();
var options = GetSut();
options.AttachScreenshot = true;

// Assert
Assert.Null(options.BeforeCaptureInternal);

}
}
51 changes: 22 additions & 29 deletions test/Sentry.Maui.Tests/SentryMauiScreenshotTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task CaptureException_WhenAttachScreenshots_ContainsScreenshotAttac
var builder = _fixture.Builder.UseSentry();

// Act
using var app = builder.Build();
await using var app = builder.Build();
var client = app.Services.GetRequiredService<ISentryClient>();
var sentryId = client.CaptureException(new Exception());
await client.FlushAsync();
Expand Down Expand Up @@ -94,13 +94,11 @@ public async Task CaptureException_RemoveScreenshot_NotContainsScreenshotAttachm
));

// Act
using var app = builder.Build();
await using var app = builder.Build();
var client = app.Services.GetRequiredService<ISentryClient>();
var sentryId = client.CaptureException(new Exception());
await client.FlushAsync();

var options = app.Services.GetRequiredService<IOptions<SentryMauiOptions>>().Value;

var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
envelope.Should().NotBeNull();

Expand All @@ -118,20 +116,16 @@ public async Task CaptureException_BeforeCaptureScreenshot_DisableCaptureAsync()
#endif

// Arrange
var builder = _fixture.Builder.UseSentry(options => options.SetBeforeScreenshotCapture((e, hint) =>
{
return false;
}
var builder = _fixture.Builder.UseSentry(options => options.SetBeforeScreenshotCapture(
(_, _) => false
));

// Act
using var app = builder.Build();
await using var app = builder.Build();
var client = app.Services.GetRequiredService<ISentryClient>();
var sentryId = client.CaptureException(new Exception());
await client.FlushAsync();

var options = app.Services.GetRequiredService<IOptions<SentryMauiOptions>>().Value;

var envelope = _fixture.Transport.GetSentEnvelopes().FirstOrDefault(e => e.TryGetEventId() == sentryId);
envelope.Should().NotBeNull();

Expand All @@ -157,10 +151,8 @@ public async Task CaptureException_BeforeCaptureScreenshot_DefaultAsync()
#endif

// Arrange
var builder = _fixture.Builder.UseSentry(options => options.SetBeforeScreenshotCapture((e, hint) =>
{
return true;
}
var builder = _fixture.Builder.UseSentry(options => options.SetBeforeScreenshotCapture(
(_, _) => true
));

// Act
Expand All @@ -184,43 +176,44 @@ public async Task CaptureException_BeforeCaptureScreenshot_DefaultAsync()
}
else
{
envelopeItem.Should().NotBeNull();
envelopeItem.Should().NotBeNull();
envelopeItem!.TryGetFileName().Should().Be("screenshot.jpg");
}
}

[Fact]
[SkippableFact]
public async Task CaptureException_AttachScreenshot_Threadsafe()
{
// Arrange
var builder = _fixture.Builder.UseSentry(options =>
{
options.AttachScreenshot = true;
});
var builder = _fixture.Builder.UseSentry(options => options.AttachScreenshot = true);
await using var app = builder.Build();
var client = app.Services.GetRequiredService<ISentryClient>();
var startSignal = new ManualResetEventSlim(false);

// Act
var tasks = new List<Task<SentryId>>();
for (var i = 0; i < 20; i++)
{
var j = i;
tasks.Add(Task.Run(() =>
tasks.Add(Task.Run(async () =>
{
startSignal.Wait(); // Make sure all the tasks start at the same time
var exSample = new NotImplementedException("Sample Exception " + j);
var sentryId = client.CaptureException(exSample);
client.FlushAsync();
await client.FlushAsync(TimeSpan.FromSeconds(5));
return sentryId;
}));
}

// Act
await Task.Delay(50); // Wait for all of the tasks to be ready
startSignal.Set();
await Task.WhenAll(tasks);

// Assert
while (tasks.Any())
foreach (var task in tasks)
{
var finishedTask = await Task.WhenAny(tasks);

finishedTask.Exception.Should().BeNull();
tasks.Remove(finishedTask);
task.Status.Should().Be(TaskStatus.RanToCompletion, $"Task should complete successfully. Status: {task.Status}");
task.Exception.Should().BeNull("No unhandled exceptions should occur during concurrent capture.");
}
}
#endif
Expand Down
Loading