Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
### Fixes

- The `Serilog` integration captures _Structured Logs_ (when enabled) independently of captured Events and added Breadcrumbs ([#4691](https://github.com/getsentry/sentry-dotnet/pull/4691))
- Deliver system breadcrumbs in the main thread on Android ([#4671](https://github.com/getsentry/sentry-dotnet/pull/4671))

## 6.0.0-preview.2

Expand Down
24 changes: 24 additions & 0 deletions integration-test/android.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,28 @@ Describe 'MAUI app (<tfm>, <configuration>)' -ForEach @(
$result.Envelopes() | Should -HaveCount 1
}
}

It 'Delivers battery breadcrumbs in main thread (<configuration>)' {
$result = Invoke-SentryServer {
param([string]$url)
RunAndroidApp -Dsn $url -TestArg "BATTERY_CHANGED"
}

Dump-ServerErrors -Result $result
$result.HasErrors() | Should -BeFalse
$result.Envelopes() | Should -AnyElementMatch "`"type`":`"system`",`"thread_id`":`"1`",`"category`":`"device.event`",`"action`":`"BATTERY_CHANGED`""
$result.Envelopes() | Should -HaveCount 1
}

It 'Delivers network breadcrumbs in main thread (<configuration>)' {
$result = Invoke-SentryServer {
param([string]$url)
RunAndroidApp -Dsn $url -TestArg "NETWORK_CAPABILITIES_CHANGED"
}

Dump-ServerErrors -Result $result
$result.HasErrors() | Should -BeFalse
$result.Envelopes() | Should -AnyElementMatch "`"type`":`"system`",`"thread_id`":`"1`",`"category`":`"network.event`",`"action`":`"NETWORK_CAPABILITIES_CHANGED`""
$result.Envelopes() | Should -HaveCount 1
}
}
100 changes: 99 additions & 1 deletion integration-test/net9-maui/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,112 @@
namespace Sentry.Maui.Device.IntegrationTestApp;
#if ANDROID
using Android.OS;
#endif
using System.Collections.Concurrent;

namespace Sentry.Maui.Device.IntegrationTestApp;

public partial class App : Application
{
private static readonly ConcurrentDictionary<string, Dictionary<string, string>> systemBreadcrumbs = new();
private static string? testArg;

public App()
{
InitializeComponent();
}

private static bool HasTestArg(string arg)
{
return string.Equals(testArg, arg, StringComparison.OrdinalIgnoreCase);
}

public static void ReceiveSystemBreadcrumb(Breadcrumb breadcrumb)
{
if (breadcrumb.Type != "system" ||
breadcrumb.Data?.TryGetValue("action", out var action) != true ||
string.IsNullOrEmpty(action))
{
return;
}

systemBreadcrumbs[action] = new Dictionary<string, string>()
{
["action"] = action,
["category"] = breadcrumb.Category ?? string.Empty,
["thread_id"] = Thread.CurrentThread.ManagedThreadId.ToString(),
["type"] = breadcrumb.Type ?? string.Empty,
};

if (HasTestArg(action))
{
// received after OnAppearing
CaptureSystemBreadcrumb(action, systemBreadcrumbs[action]!);
Kill();
}
}

public static void CaptureSystemBreadcrumb(string action, Dictionary<string, string> data)
{
SentrySdk.CaptureMessage(action, scope =>
{
foreach (var kvp in data)
{
scope.SetExtra(kvp.Key, kvp.Value);
}
});
}

protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}

public static void OnAppearing()
{
testArg = System.Environment.GetEnvironmentVariable("SENTRY_TEST_ARG");

#pragma warning disable CS0618
if (Enum.TryParse<CrashType>(testArg, ignoreCase: true, out var crashType))
{
SentrySdk.CauseCrash(crashType);
}
#pragma warning restore CS0618

if (HasTestArg("NullReferenceException"))
{
try
{
object? obj = null;
_ = obj!.ToString();
}
catch (NullReferenceException ex)
{
SentrySdk.CaptureException(ex);
}
Kill();
}
else if (!string.IsNullOrEmpty(testArg) && systemBreadcrumbs.TryGetValue(testArg, out var breadcrumb))
{
// received before OnAppearing
CaptureSystemBreadcrumb(testArg, breadcrumb);
Kill();
}
else if (HasTestArg("None"))
{
Kill();
}
}

public static void Kill()
{
SentrySdk.Close();

#if ANDROID
// prevent auto-restart
Platform.CurrentActivity?.FinishAffinity();
Process.KillProcess(Process.MyPid());
#elif IOS
System.Environment.Exit(0);
#endif
}
}
38 changes: 2 additions & 36 deletions integration-test/net9-maui/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
#if ANDROID
using Android.OS;
#endif

namespace Sentry.Maui.Device.IntegrationTestApp;
namespace Sentry.Maui.Device.IntegrationTestApp;

public partial class MainPage : ContentPage
{
Expand All @@ -14,36 +10,6 @@ public MainPage()
protected override void OnAppearing()
{
base.OnAppearing();

var testArg = System.Environment.GetEnvironmentVariable("SENTRY_TEST_ARG");

#pragma warning disable CS0618
if (Enum.TryParse<CrashType>(testArg, ignoreCase: true, out var crashType))
{
SentrySdk.CauseCrash(crashType);
}
#pragma warning restore CS0618

if (testArg?.Equals("NullReferenceException", StringComparison.OrdinalIgnoreCase) == true)
{
try
{
object? obj = null;
_ = obj!.ToString();
}
catch (NullReferenceException ex)
{
SentrySdk.CaptureException(ex);
}
}

SentrySdk.Close();
#if ANDROID
// prevent auto-restart
Platform.CurrentActivity?.FinishAffinity();
Process.KillProcess(Process.MyPid());
#elif IOS
System.Environment.Exit(0);
#endif
App.OnAppearing();
}
}
6 changes: 6 additions & 0 deletions integration-test/net9-maui/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public static MauiApp CreateMauiApp()
// predictable crash envelopes only
options.SendClientReports = false;
options.AutoSessionTracking = false;

options.SetBeforeBreadcrumb((breadcrumb, hint) =>
{
App.ReceiveSystemBreadcrumb(breadcrumb);
return breadcrumb;
});
})
.ConfigureFonts(fonts =>
{
Expand Down
3 changes: 3 additions & 0 deletions src/Sentry.Bindings.Android/Transforms/Metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@
</method>
</add-node>

<!-- Remove problematic deprecated methods in the IScopes interface -->
<remove-node path="/api/package[@name='io.sentry']/interface[@name='IScopes']/method[@deprecated]" />

<!--
TODO: If we need this, figure out how to multi-target or late bind.
This API uses FrameMetrics, which requires Android >= 24.0. We currently target Android >= 21.0 which is the minimum supported by MAUI.
Expand Down
12 changes: 6 additions & 6 deletions src/Sentry/Platforms/Android/AndroidDiagnosticLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ namespace Sentry.Android;

internal class AndroidDiagnosticLogger : JavaObject, JavaSdk.ILogger
{
private readonly IDiagnosticLogger _logger;
private readonly IDiagnosticLogger? _logger;

public AndroidDiagnosticLogger(IDiagnosticLogger logger) => _logger = logger;
public AndroidDiagnosticLogger(IDiagnosticLogger? logger) => _logger = logger;

public void Log(JavaSdk.SentryLevel level, string message, JavaObject[]? args) =>
_logger.Log(level.ToSentryLevel(), "Android: " + FormatJavaString(message, args));
_logger?.Log(level.ToSentryLevel(), "Android: " + FormatJavaString(message, args));

public void Log(JavaSdk.SentryLevel level, string message, Throwable? throwable) =>
_logger.Log(level.ToSentryLevel(), "Android: " + message, throwable);
_logger?.Log(level.ToSentryLevel(), "Android: " + message, throwable);

public void Log(JavaSdk.SentryLevel level, Throwable? throwable, string message, params JavaObject[]? args) =>
_logger.Log(level.ToSentryLevel(), "Android: " + FormatJavaString(message, args), throwable);
_logger?.Log(level.ToSentryLevel(), "Android: " + FormatJavaString(message, args), throwable);

public bool IsEnabled(JavaSdk.SentryLevel? level) =>
level != null && _logger.IsEnabled(level.ToSentryLevel());
level != null && _logger != null && _logger.IsEnabled(level.ToSentryLevel());

private static string FormatJavaString(string s, JavaObject[]? args) =>
args is null ? s : JavaString.Format(s, args);
Expand Down
2 changes: 2 additions & 0 deletions src/Sentry/Platforms/Android/Sentry.Android.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<Using Include="Android.Runtime" />
<Using Include="Android.Content.Context" Alias="AndroidContext" />
<Using Include="Android.OS.Build" Alias="AndroidBuild" />
<Using Include="Android.OS.Looper" Alias="AndroidLooper" />
<Using Include="Android.OS.Handler" Alias="AndroidHandler" />
<Using Include="Java.Lang.Boolean" Alias="JavaBoolean" />
<Using Include="Java.Lang.Class" Alias="JavaClass" />
<Using Include="Java.Lang.Double" Alias="JavaDouble" />
Expand Down
12 changes: 12 additions & 0 deletions src/Sentry/Platforms/Android/SentrySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using Sentry.Android.Callbacks;
using Sentry.Android.Extensions;
using Sentry.Extensibility;
using Sentry.JavaSdk;
using Sentry.JavaSdk.Android.Core;
using Sentry.JavaSdk.Android.Core.Internal.Util;

// Don't let the Sentry Android SDK auto-init, as we do that manually in SentrySdk.Init
// See https://docs.sentry.io/platforms/android/configuration/manual-init/
Expand Down Expand Up @@ -155,6 +157,16 @@ private static void InitSentryAndroidSdk(SentryOptions options)

// Don't capture managed exceptions in the native SDK, since we already capture them in the managed SDK
o.AddIgnoredExceptionForType(JavaClass.ForName("android.runtime.JavaProxyThrowable"));

// Deliver network and system event breadcrumbs in the main thread
// See https://github.com/getsentry/sentry-dotnet/issues/3828
var networkLogger = new AndroidDiagnosticLogger(options.DiagnosticLogger);
var buildInfoProvider = new BuildInfoProvider(networkLogger);
var timeProvider = AndroidCurrentDateProvider.Instance!;
var mainHandler = new AndroidHandler(AndroidLooper.MainLooper!);
o.ConnectionStatusProvider =
new AndroidConnectionStatusProvider(AppContext, o, buildInfoProvider, timeProvider, mainHandler).JavaCast<IConnectionStatusProvider>();
o.AddIntegration(new SystemEventsBreadcrumbsIntegration(AppContext, mainHandler).JavaCast<JavaSdk.IIntegration>());
});

// Now initialize the Android SDK (with a logger only if we're debugging)
Expand Down
Loading