Skip to content
Merged
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();
}

public 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
}
}
37 changes: 2 additions & 35 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 @@ -15,35 +11,6 @@ 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