diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6a15ac40..7bfa2cc793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Add `SentrySdk.GetPersistedBreadcrumbs()` API to retrieve breadcrumbs from the previous Android app session ([#4977](https://github.com/getsentry/sentry-dotnet/pull/4977)) + ### Fixes - The SDK now logs a `Warning` instead of an `Error` when being ratelimited ([#4927](https://github.com/getsentry/sentry-dotnet/pull/4927)) diff --git a/src/Sentry/Platforms/Android/SentrySdk.cs b/src/Sentry/Platforms/Android/SentrySdk.cs index 919ec00357..314b04c1b9 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -1,5 +1,6 @@ using Android.Content.PM; using Android.OS; +using Android.Runtime; using Sentry.Android; using Sentry.Android.Callbacks; using Sentry.Android.Extensions; @@ -22,6 +23,7 @@ namespace Sentry; public static partial class SentrySdk { private static AndroidContext AppContext { get; set; } = Application.Context; + private static List? _persistedBreadcrumbs; private static void InitSentryAndroidSdk(SentryOptions options) { @@ -171,6 +173,11 @@ private static void InitSentryAndroidSdk(SentryOptions options) o.ConnectionStatusProvider = new AndroidConnectionStatusProvider(AppContext, o, buildInfoProvider, timeProvider, mainHandler).JavaCast(); o.AddIntegration(new SystemEventsBreadcrumbsIntegration(AppContext, mainHandler).JavaCast()); + + // Read persisted breadcrumbs now, inside the options callback, before the SDK starts. + // This avoids a race condition with breadcrumb-producing integrations that start writing + // to the QueueFile as soon as SentryAndroid.Init() completes. + _persistedBreadcrumbs = ReadPersistedBreadcrumbs(o); }); // Now initialize the Android SDK (with a logger only if we're debugging) @@ -286,4 +293,42 @@ private static void AndroidEnvironment_UnhandledExceptionRaiser(object? _, Raise #pragma warning restore CA1422 #pragma warning restore CS0618 } + + /// + /// Retrieves breadcrumbs that were persisted to disk by the Android SDK's scope observer. + /// These are breadcrumbs from the previous app session that were saved before the app terminated. + /// + /// A list of persisted breadcrumbs, or an empty list if unavailable. + public static IReadOnlyList GetPersistedBreadcrumbs() => + _persistedBreadcrumbs ?? []; + + private static List? ReadPersistedBreadcrumbs(JavaSdk.SentryOptions nativeOptions) + { + if (nativeOptions.CacheDirPath is null) + { + return null; + } + + // Create a temporary observer to read breadcrumbs from the previous session's QueueFile. + // This must be called before SentryAndroid.Init() completes, to avoid a race condition + // with breadcrumb-producing integrations that write to the same QueueFile concurrently. + using var observer = new JavaSdk.Cache.PersistingScopeObserver(nativeOptions); + var result = observer.Read(nativeOptions, "breadcrumbs.json", + Java.Lang.Class.FromType(typeof(Java.Util.IList))); + + if (result is not Java.Util.IList javaList) + { + return null; + } + + var breadcrumbs = new List(); + for (var i = 0; i < javaList.Size(); i++) + { + if (javaList.Get(i)?.JavaCast() is { } javaBreadcrumb) + { + breadcrumbs.Add(javaBreadcrumb.ToBreadcrumb()); + } + } + return breadcrumbs; + } }