From 6a2153cf8f32e88b6634fb75f270226537c924c8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 27 Mar 2026 14:12:01 +0100 Subject: [PATCH 1/3] scope observer syncs attachments --- global.json | 4 +- .../AndroidJavaScopeObserver.cs | 9 ++++ src/Sentry.Unity.Android/SentryJava.cs | 41 +++++++++++++++++++ src/Sentry.Unity.Native/CFunctions.cs | 9 ++++ .../NativeScopeObserver.cs | 9 ++++ src/Sentry.Unity.iOS/NativeScopeObserver.cs | 15 +++++++ src/Sentry.Unity/ScopeObserver.cs | 30 ++++++++++++++ .../ScriptableSentryUnityOptions.cs | 2 +- src/Sentry.Unity/SentrySdk.Dotnet.cs | 31 +++----------- src/sentry-dotnet | 2 +- .../TestSentryJava.cs | 6 +++ 11 files changed, 128 insertions(+), 30 deletions(-) diff --git a/global.json b/global.json index df06b7379..edcd540fe 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "10.0.102", - "workloadVersion": "10.0.102", + "version": "10.0.201", + "workloadVersion": "10.0.201", "rollForward": "disable", "allowPrerelease": false } diff --git a/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs b/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs index 55e51705f..65936ac79 100644 --- a/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs +++ b/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs @@ -36,4 +36,13 @@ public override void UnsetUserImpl() => public override void SetTraceImpl(SentryId traceId, SpanId spanId) => _sentryJava.SetTrace(traceId, spanId); + + public override void AddFileAttachmentImpl(string filePath, string fileName, string? contentType) => + _sentryJava.AddAttachment(filePath, fileName, contentType); + + public override void AddByteAttachmentImpl(byte[] data, string fileName, string? contentType) => + _sentryJava.AddAttachmentBytes(data, fileName, contentType); + + public override void ClearAttachmentsImpl() => + _sentryJava.ClearAttachments(); } diff --git a/src/Sentry.Unity.Android/SentryJava.cs b/src/Sentry.Unity.Android/SentryJava.cs index 9a68442aa..918acc746 100644 --- a/src/Sentry.Unity.Android/SentryJava.cs +++ b/src/Sentry.Unity.Android/SentryJava.cs @@ -42,6 +42,9 @@ public void WriteScope( public void SetUser(SentryUser user); public void UnsetUser(); public void SetTrace(SentryId traceId, SpanId spanId); + void AddAttachment(string path, string fileName, string? contentType); + void AddAttachmentBytes(byte[] data, string fileName, string? contentType); + void ClearAttachments(); } /// @@ -361,6 +364,44 @@ public void SetTrace(SentryId traceId, SpanId spanId) }); } + public void AddAttachment(string path, string fileName, string? contentType) + { + RunJniSafe(() => + { + using var attachment = contentType is not null + ? new AndroidJavaObject("io.sentry.Attachment", path, fileName, contentType) + : new AndroidJavaObject("io.sentry.Attachment", path, fileName); + + using var sentry = GetSentryJava(); + sentry.CallStatic("configureScope", new ScopeCallback(scope => + scope.Call("addAttachment", attachment))); + }); + } + + public void AddAttachmentBytes(byte[] data, string fileName, string? contentType) + { + RunJniSafe(() => + { + using var attachment = contentType is not null + ? new AndroidJavaObject("io.sentry.Attachment", data, fileName, contentType) + : new AndroidJavaObject("io.sentry.Attachment", data, fileName); + + using var sentry = GetSentryJava(); + sentry.CallStatic("configureScope", new ScopeCallback(scope => + scope.Call("addAttachment", attachment))); + }); + } + + public void ClearAttachments() + { + RunJniSafe(() => + { + using var sentry = GetSentryJava(); + sentry.CallStatic("configureScope", new ScopeCallback(scope => + scope.Call("clearAttachments"))); + }); + } + // https://github.com/getsentry/sentry-java/blob/db4dfc92f202b1cefc48d019fdabe24d487db923/sentry/src/main/java/io/sentry/SentryLevel.java#L4-L9 internal static string GetLevelString(SentryLevel level) => level switch { diff --git a/src/Sentry.Unity.Native/CFunctions.cs b/src/Sentry.Unity.Native/CFunctions.cs index 4a644e70c..212fc0ed7 100644 --- a/src/Sentry.Unity.Native/CFunctions.cs +++ b/src/Sentry.Unity.Native/CFunctions.cs @@ -165,6 +165,15 @@ internal static void SetValueIfNotNull(sentry_value_t obj, string key, long? val [DllImport(SentryLib)] internal static extern void sentry_set_trace(string traceId, string parentSpanId); + [DllImport(SentryLib)] + internal static extern IntPtr sentry_attach_file(string path); + + [DllImport(SentryLib)] + internal static extern IntPtr sentry_attach_bytes(byte[] buf, UIntPtr buf_len, string filename); + + [DllImport(SentryLib)] + internal static extern void sentry_clear_attachments(); + internal static readonly Lazy> DebugImages = new(LoadDebugImages); private static IEnumerable LoadDebugImages() diff --git a/src/Sentry.Unity.Native/NativeScopeObserver.cs b/src/Sentry.Unity.Native/NativeScopeObserver.cs index bebeae55c..c99e11641 100644 --- a/src/Sentry.Unity.Native/NativeScopeObserver.cs +++ b/src/Sentry.Unity.Native/NativeScopeObserver.cs @@ -43,6 +43,15 @@ public override void SetUserImpl(SentryUser user) public override void SetTraceImpl(SentryId traceId, SpanId spanId) => C.sentry_set_trace(traceId.ToString(), spanId.ToString()); + public override void AddFileAttachmentImpl(string filePath, string fileName, string? contentType) => + C.sentry_attach_file(filePath); + + public override void AddByteAttachmentImpl(byte[] data, string fileName, string? contentType) => + C.sentry_attach_bytes(data, (UIntPtr)data.Length, fileName); + + public override void ClearAttachmentsImpl() => + C.sentry_clear_attachments(); + private static string GetTimestamp(DateTimeOffset timestamp) => // "o": Using ISO 8601 to make sure the timestamp makes it to the bridge correctly. // https://docs.microsoft.com/en-gb/dotnet/standard/base-types/standard-date-and-time-format-strings#Roundtrip diff --git a/src/Sentry.Unity.iOS/NativeScopeObserver.cs b/src/Sentry.Unity.iOS/NativeScopeObserver.cs index 8c8637eb4..0358d6086 100644 --- a/src/Sentry.Unity.iOS/NativeScopeObserver.cs +++ b/src/Sentry.Unity.iOS/NativeScopeObserver.cs @@ -29,6 +29,21 @@ public override void SetUserImpl(SentryUser user) => public override void SetTraceImpl(SentryId traceId, SpanId spanId) => SentryCocoaBridgeProxy.SetTrace(traceId.ToString(), spanId.ToString()); + public override void AddFileAttachmentImpl(string filePath, string fileName, string? contentType) + { + // iOS/macOS attachment sync to sentry-cocoa is not yet supported. + } + + public override void AddByteAttachmentImpl(byte[] data, string fileName, string? contentType) + { + // iOS/macOS attachment sync to sentry-cocoa is not yet supported. + } + + public override void ClearAttachmentsImpl() + { + // iOS/macOS attachment sync to sentry-cocoa is not yet supported. + } + internal static string GetTimestamp(DateTimeOffset timestamp) => // "o": Using ISO 8601 to make sure the timestamp makes it to the bridge correctly. // https://docs.microsoft.com/en-gb/dotnet/standard/base-types/standard-date-and-time-format-strings#Roundtrip diff --git a/src/Sentry.Unity/ScopeObserver.cs b/src/Sentry.Unity/ScopeObserver.cs index 556a83da4..3ae361163 100644 --- a/src/Sentry.Unity/ScopeObserver.cs +++ b/src/Sentry.Unity/ScopeObserver.cs @@ -86,4 +86,34 @@ public void SetTrace(SentryId traceId, SpanId spanId) } public abstract void SetTraceImpl(SentryId traceId, SpanId spanId); + + public void AddAttachment(SentryAttachment attachment) + { + if (attachment.Content is FileAttachmentContent fileContent) + { + _options.LogDebug("{0} Scope Sync - Adding file attachment \"{1}\"", _name, fileContent.FilePath); + AddFileAttachmentImpl(fileContent.FilePath, attachment.FileName, attachment.ContentType); + } + else if (attachment.Content is ByteAttachmentContent byteContent) + { + _options.LogDebug("{0} Scope Sync - Adding byte attachment \"{1}\" ({2} bytes)", _name, attachment.FileName, byteContent.Bytes.Length); + AddByteAttachmentImpl(byteContent.Bytes, attachment.FileName, attachment.ContentType); + } + else + { + _options.LogDebug("{0} Scope Sync - Skipping attachment \"{1}\" (unsupported content type for native sync)", _name, attachment.FileName); + } + } + + public abstract void AddFileAttachmentImpl(string filePath, string fileName, string? contentType); + + public abstract void AddByteAttachmentImpl(byte[] data, string fileName, string? contentType); + + public void ClearAttachments() + { + _options.LogDebug("{0} Scope Sync - Clearing attachments", _name); + ClearAttachmentsImpl(); + } + + public abstract void ClearAttachmentsImpl(); } diff --git a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs index 55f4ad6a1..87e93fbb4 100644 --- a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs +++ b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs @@ -228,7 +228,7 @@ internal SentryUnityOptions ToSentryUnityOptions( AddBreadcrumbsWithStructuredLogs = AddBreadcrumbsWithStructuredLogs }; - options.Experimental.EnableMetrics = EnableMetrics; + options.EnableMetrics = EnableMetrics; // By default, the cacheDirectoryPath gets set on known platforms. We're overwriting this behaviour here. if (!EnableOfflineCaching) diff --git a/src/Sentry.Unity/SentrySdk.Dotnet.cs b/src/Sentry.Unity/SentrySdk.Dotnet.cs index 2539aa3b2..6e750d553 100644 --- a/src/Sentry.Unity/SentrySdk.Dotnet.cs +++ b/src/Sentry.Unity/SentrySdk.Dotnet.cs @@ -56,7 +56,7 @@ public static partial class SentrySdk /// /// /// Use this property to access structured logging functionality. Logs are only sent when - /// 's + /// /// is set to true. /// /// @@ -548,32 +548,11 @@ public static void ResumeSession() public static void CauseCrash(CrashType crashType) => Sentry.SentrySdk.CauseCrash(crashType); /// - /// Sentry features that are currently in an experimental state. + /// Gets the metric emitter for emitting counters, gauges, and distributions connected to traces. /// - /// - /// Experimental features are subject to binary, source and behavioral breaking changes in future updates. - /// - public static ExperimentalSentrySdk Experimental { get; } = new(); - - /// - /// Sentry features that are currently in an experimental state. - /// - /// - /// Experimental features are subject to binary, source and behavioral breaking changes in future updates. - /// - public sealed class ExperimentalSentrySdk + public static SentryMetricEmitter Metrics { - internal ExperimentalSentrySdk() - { - } - - /// - /// Gets the metric emitter for emitting counters, gauges, and distributions connected to traces. - /// - public SentryMetricEmitter Metrics - { - [DebuggerStepThrough] - get => Sentry.SentrySdk.Experimental.Metrics; - } + [DebuggerStepThrough] + get => Sentry.SentrySdk.Metrics; } } diff --git a/src/sentry-dotnet b/src/sentry-dotnet index 248f45541..3e2eae923 160000 --- a/src/sentry-dotnet +++ b/src/sentry-dotnet @@ -1 +1 @@ -Subproject commit 248f45541c5e23e7fe425cdeb98c187389de0aa8 +Subproject commit 3e2eae9230101d229a65a1d667cf5d2203410d94 diff --git a/test/Sentry.Unity.Android.Tests/TestSentryJava.cs b/test/Sentry.Unity.Android.Tests/TestSentryJava.cs index 08dbe299c..195d94221 100644 --- a/test/Sentry.Unity.Android.Tests/TestSentryJava.cs +++ b/test/Sentry.Unity.Android.Tests/TestSentryJava.cs @@ -54,4 +54,10 @@ public void SetUser(SentryUser user) { } public void UnsetUser() { } public void SetTrace(SentryId traceId, SpanId spanId) { } + + public void AddAttachment(string path, string fileName, string? contentType) { } + + public void AddAttachmentBytes(byte[] data, string fileName, string? contentType) { } + + public void ClearAttachments() { } } From ad1e0422e1e8d8084c20923995ef0e5731ce7571 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 31 Mar 2026 16:50:31 +0200 Subject: [PATCH 2/3] rev: global.json --- global.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/global.json b/global.json index edcd540fe..df06b7379 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "10.0.201", - "workloadVersion": "10.0.201", + "version": "10.0.102", + "workloadVersion": "10.0.102", "rollForward": "disable", "allowPrerelease": false } From 83cf73b6f90e611365594d79bad7dd349fcddb0d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 1 Apr 2026 10:47:34 +0200 Subject: [PATCH 3/3] updated changelog.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac85dcaf..a24d68b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## Unreleased -## Features +### Features - The _Metrics_ APIs are now stable: removed `Experimental` from `SentrySdk` and `SentryOptions` ([#2615](https://github.com/getsentry/sentry-unity/pull/2615)) +- Attachments added to the scope are now included in native crash reports on Android, Windows, and Linux ([#2609](https://github.com/getsentry/sentry-unity/pull/2609)) ### Dependencies