From a3da89dfec6814fbfd7e551628bab18e32367891 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 18 Mar 2026 13:19:42 +0100 Subject: [PATCH 1/8] observe and sync file attachments --- .../sentry/android/ndk/NdkScopeObserver.java | 26 ++++++++++++++++ .../android/ndk/NdkScopeObserverTest.kt | 30 +++++++++++++++++++ .../main/java/io/sentry/IScopeObserver.java | 4 +++ sentry/src/main/java/io/sentry/Scope.java | 8 +++++ .../java/io/sentry/ScopeObserverAdapter.java | 6 ++++ 5 files changed, 74 insertions(+) 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..be5754a71b7 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,29 @@ 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) { + // Only file-path attachments are getting synced to the native layer right now. + options.getLogger().log(SentryLevel.DEBUG, "Scope sync addAttachment skips non-file attachment."); + return; + } + + try { + options.getExecutorService().submit(() -> nativeScope.addAttachment(pathname)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync addAttachment has an error."); + } + } + + @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..3ad803c32a3 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,33 @@ 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 does not sync to native scope`() { + val sut = fixture.getSut() + + val attachment = Attachment(byteArrayOf(1, 2, 3), "data.bin") + sut.addAttachment(attachment) + + verify(fixture.nativeScope, never()).addAttachment(any()) + } + + @Test + fun `clear attachments forwards call to native scope`() { + val sut = fixture.getSut() + + sut.clearAttachments() + + verify(fixture.nativeScope).clearAttachments() + } } diff --git a/sentry/src/main/java/io/sentry/IScopeObserver.java b/sentry/src/main/java/io/sentry/IScopeObserver.java index a43ccf6b695..7156fe287c6 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() {} } From af0502c8657cd20e70e5fd43ceac509f47fef800 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 18 Mar 2026 13:02:20 +0000 Subject: [PATCH 2/8] Format code --- .../src/main/java/io/sentry/android/ndk/NdkScopeObserver.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 be5754a71b7..f4298acbcda 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 @@ -152,7 +152,9 @@ public void addAttachment(final @NotNull Attachment attachment) { final String pathname = attachment.getPathname(); if (pathname == null) { // Only file-path attachments are getting synced to the native layer right now. - options.getLogger().log(SentryLevel.DEBUG, "Scope sync addAttachment skips non-file attachment."); + options + .getLogger() + .log(SentryLevel.DEBUG, "Scope sync addAttachment skips non-file attachment."); return; } From 80f8875bc1f36ff68c34e7851e3bb98a05b00fc1 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 18 Mar 2026 14:26:20 +0100 Subject: [PATCH 3/8] Updated CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d303a3c1e6..48baaf0a849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Android: Files attached to the scope will now be synced to native ([#5211](https://github.com/getsentry/sentry-java/pull/5211)) + ## 8.36.0 ### Features From 6278324e01bd9ae25c3b8c9c8e95b49a3e27151d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Mar 2026 11:51:40 +0100 Subject: [PATCH 4/8] also attach bytes --- .../sentry/android/ndk/NdkScopeObserver.java | 28 +++++++++++++------ .../android/ndk/NdkScopeObserverTest.kt | 7 +++-- .../main/java/io/sentry/IScopeObserver.java | 4 +-- 3 files changed, 25 insertions(+), 14 deletions(-) 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 f4298acbcda..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 @@ -150,19 +150,29 @@ public void setTrace(@Nullable SpanContext spanContext, @NotNull IScope scope) { @Override public void addAttachment(final @NotNull Attachment attachment) { final String pathname = attachment.getPathname(); - if (pathname == null) { - // Only file-path attachments are getting synced to the native layer right now. - options - .getLogger() - .log(SentryLevel.DEBUG, "Scope sync addAttachment skips non-file attachment."); + 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; } - try { - options.getExecutorService().submit(() -> nativeScope.addAttachment(pathname)); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, e, "Scope sync addAttachment has an error."); + 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 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 3ad803c32a3..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 @@ -166,13 +166,14 @@ class NdkScopeObserverTest { } @Test - fun `add byte attachment does not sync to native scope`() { + fun `add byte attachment syncs bytes to native scope`() { val sut = fixture.getSut() - val attachment = Attachment(byteArrayOf(1, 2, 3), "data.bin") + val bytes = byteArrayOf(1, 2, 3) + val attachment = Attachment(bytes, "data.bin") sut.addAttachment(attachment) - verify(fixture.nativeScope, never()).addAttachment(any()) + verify(fixture.nativeScope).addAttachmentBytes(bytes, "data.bin") } @Test diff --git a/sentry/src/main/java/io/sentry/IScopeObserver.java b/sentry/src/main/java/io/sentry/IScopeObserver.java index 7156fe287c6..e1b9a785043 100644 --- a/sentry/src/main/java/io/sentry/IScopeObserver.java +++ b/sentry/src/main/java/io/sentry/IScopeObserver.java @@ -46,7 +46,7 @@ public interface IScopeObserver { void setReplayId(@NotNull SentryId replayId); - void addAttachment(@NotNull Attachment attachment) {} + void addAttachment(@NotNull Attachment attachment); - void clearAttachments() {} + void clearAttachments(); } From 170e77bcde636a8f44cd45cce552669255228fe7 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 20 Mar 2026 09:21:39 +0100 Subject: [PATCH 5/8] bumped native ndk to 0.13.3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d659e43438c..7b53258a5a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -148,7 +148,7 @@ quartz = { module = "org.quartz-scheduler:quartz", version = "2.3.0" } reactor-core = { module = "io.projectreactor:reactor-core", version = "3.5.3" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } -sentry-native-ndk = { module = "io.sentry:sentry-native-ndk", version = "0.13.2" } +sentry-native-ndk = { module = "io.sentry:sentry-native-ndk", version = "0.13.3" } servlet-api = { module = "javax.servlet:javax.servlet-api", version = "3.1.0" } servlet-jakarta-api = { module = "jakarta.servlet:jakarta.servlet-api", version = "6.1.0" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } From 2e2d0ac9df4459dc12c66024b4c12e8561f3c44e Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 20 Mar 2026 09:32:31 +0100 Subject: [PATCH 6/8] updated api --- sentry/api/sentry.api | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 5b8f973e756..3a83befd011 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 From 3597462e26fad8f8dfec3d811d397ea725c93c38 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 20 Mar 2026 09:43:23 +0100 Subject: [PATCH 7/8] more api --- sentry-android-ndk/api/sentry-android-ndk.api | 2 ++ 1 file changed, 2 insertions(+) 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 From 273a9eafc6f914bca517b1ad0ca0862282b90cde Mon Sep 17 00:00:00 2001 From: Stefan Jandl Date: Fri, 20 Mar 2026 12:40:54 +0100 Subject: [PATCH 8/8] Apply suggestion from @bitsandfoxes --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48baaf0a849..f923ebb2eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Features -- Android: Files attached to the scope will now be synced to native ([#5211](https://github.com/getsentry/sentry-java/pull/5211)) +- Android: Attachments on the scope will now be synced to native ([#5211](https://github.com/getsentry/sentry-java/pull/5211)) ## 8.36.0