diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d640f385a..7e28ee777c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Android: Attachments on the scope will now be synced to native ([#5211](https://github.com/getsentry/sentry-java/pull/5211)) - Android: Add `beforeErrorSampling` callback to Session Replay ([#5214](https://github.com/getsentry/sentry-java/pull/5214)) - Allows filtering which errors trigger replay capture before the `onErrorSampleRate` is checked - Returning `false` skips replay capture entirely for that error; returning `true` proceeds with the normal sample rate check diff --git a/sentry-android-ndk/api/sentry-android-ndk.api b/sentry-android-ndk/api/sentry-android-ndk.api index 44c153a71fe..a7c5571d0bb 100644 --- a/sentry-android-ndk/api/sentry-android-ndk.api +++ b/sentry-android-ndk/api/sentry-android-ndk.api @@ -15,7 +15,9 @@ public final class io/sentry/android/ndk/DebugImagesLoader : io/sentry/android/c public final class io/sentry/android/ndk/NdkScopeObserver : io/sentry/ScopeObserverAdapter { public fun (Lio/sentry/SentryOptions;)V + public fun addAttachment (Lio/sentry/Attachment;)V public fun addBreadcrumb (Lio/sentry/Breadcrumb;)V + public fun clearAttachments ()V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java index 023ce965f51..a1474bb69c8 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java @@ -1,5 +1,6 @@ package io.sentry.android.ndk; +import io.sentry.Attachment; import io.sentry.Breadcrumb; import io.sentry.DateUtils; import io.sentry.IScope; @@ -145,4 +146,41 @@ public void setTrace(@Nullable SpanContext spanContext, @NotNull IScope scope) { options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setTrace failed."); } } + + @Override + public void addAttachment(final @NotNull Attachment attachment) { + final String pathname = attachment.getPathname(); + if (pathname != null) { + try { + options.getExecutorService().submit(() -> nativeScope.addAttachment(pathname)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync addAttachment has an error."); + } + return; + } + + final byte[] bytes = attachment.getBytes(); + if (bytes != null) { + final String filename = attachment.getFilename(); + try { + options.getExecutorService().submit(() -> nativeScope.addAttachmentBytes(bytes, filename)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync addAttachment has an error."); + } + return; + } + + options + .getLogger() + .log(SentryLevel.DEBUG, "Scope sync addAttachment skips attachment without path or bytes."); + } + + @Override + public void clearAttachments() { + try { + options.getExecutorService().submit(() -> nativeScope.clearAttachments()); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync clearAttachments has an error."); + } + } } diff --git a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt index 696bb69a8d5..a8b5318bfab 100644 --- a/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt +++ b/sentry-android-ndk/src/test/java/io/sentry/android/ndk/NdkScopeObserverTest.kt @@ -1,5 +1,6 @@ package io.sentry.android.ndk +import io.sentry.Attachment import io.sentry.Breadcrumb import io.sentry.DateUtils import io.sentry.JsonSerializer @@ -153,4 +154,34 @@ class NdkScopeObserverTest { verify(fixture.nativeScope) .addBreadcrumb(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } + + @Test + fun `add file-path attachment syncs to native scope`() { + val sut = fixture.getSut() + + val attachment = Attachment("/data/data/com.example/files/log.txt") + sut.addAttachment(attachment) + + verify(fixture.nativeScope).addAttachment("/data/data/com.example/files/log.txt") + } + + @Test + fun `add byte attachment syncs bytes to native scope`() { + val sut = fixture.getSut() + + val bytes = byteArrayOf(1, 2, 3) + val attachment = Attachment(bytes, "data.bin") + sut.addAttachment(attachment) + + verify(fixture.nativeScope).addAttachmentBytes(bytes, "data.bin") + } + + @Test + fun `clear attachments forwards call to native scope`() { + val sut = fixture.getSut() + + sut.clearAttachments() + + verify(fixture.nativeScope).clearAttachments() + } } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index cb9078ac07b..affbf5e2c99 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -943,7 +943,9 @@ public abstract interface class io/sentry/IScope { } public abstract interface class io/sentry/IScopeObserver { + public abstract fun addAttachment (Lio/sentry/Attachment;)V public abstract fun addBreadcrumb (Lio/sentry/Breadcrumb;)V + public abstract fun clearAttachments ()V public abstract fun removeExtra (Ljava/lang/String;)V public abstract fun removeTag (Ljava/lang/String;)V public abstract fun setBreadcrumbs (Ljava/util/Collection;)V @@ -2446,7 +2448,9 @@ public abstract interface class io/sentry/ScopeCallback { public abstract class io/sentry/ScopeObserverAdapter : io/sentry/IScopeObserver { public fun ()V + public fun addAttachment (Lio/sentry/Attachment;)V public fun addBreadcrumb (Lio/sentry/Breadcrumb;)V + public fun clearAttachments ()V public fun removeExtra (Ljava/lang/String;)V public fun removeTag (Ljava/lang/String;)V public fun setBreadcrumbs (Ljava/util/Collection;)V diff --git a/sentry/src/main/java/io/sentry/IScopeObserver.java b/sentry/src/main/java/io/sentry/IScopeObserver.java index a43ccf6b695..e1b9a785043 100644 --- a/sentry/src/main/java/io/sentry/IScopeObserver.java +++ b/sentry/src/main/java/io/sentry/IScopeObserver.java @@ -45,4 +45,8 @@ public interface IScopeObserver { void setTrace(@Nullable SpanContext spanContext, @NotNull IScope scope); void setReplayId(@NotNull SentryId replayId); + + void addAttachment(@NotNull Attachment attachment); + + void clearAttachments(); } diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index 1aab545b80c..fa44e90a194 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -924,12 +924,20 @@ public List getAttachments() { @Override public void addAttachment(final @NotNull Attachment attachment) { attachments.add(attachment); + + for (final IScopeObserver observer : options.getScopeObservers()) { + observer.addAttachment(attachment); + } } /** Clear all attachments. */ @Override public void clearAttachments() { attachments.clear(); + + for (final IScopeObserver observer : options.getScopeObservers()) { + observer.clearAttachments(); + } } /** diff --git a/sentry/src/main/java/io/sentry/ScopeObserverAdapter.java b/sentry/src/main/java/io/sentry/ScopeObserverAdapter.java index f0ec6448e03..4f6a5ac842c 100644 --- a/sentry/src/main/java/io/sentry/ScopeObserverAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopeObserverAdapter.java @@ -57,4 +57,10 @@ public void setTrace(@Nullable SpanContext spanContext, @NotNull IScope scope) { @Override public void setReplayId(@NotNull SentryId replayId) {} + + @Override + public void addAttachment(@NotNull Attachment attachment) {} + + @Override + public void clearAttachments() {} }