From 14427b677a32039ec41d9b77132c0a0117413e24 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 3 Jul 2025 17:39:42 +0200 Subject: [PATCH 01/42] initial working implementation --- package-dev/Runtime/SentryInitialization.cs | 51 +- package-dev/Runtime/SentryIntegrations.cs | 3 +- package-dev/Runtime/SentryUserFeedback.cs | 4 +- .../Tests/Runtime/SentryIntegrationsTests.cs | 4 +- .../SentrySceneTracingIntegrationTests.cs | 2 +- .../Assets/Scripts/AdditionalButtons.cs | 2 +- .../Assets/Scripts/BugFarmButtons.cs | 6 +- .../Scripts/NativeSupport/NativeButtons.cs | 4 +- .../Assets/Scripts/ThreadingSamples.cs | 2 +- .../NativeUtils/SentryPlatformServices.cs | 9 + .../ScriptableSentryUnityOptions.cs | 5 +- src/Sentry.Unity/SentryMonoBehaviour.cs | 4 +- src/Sentry.Unity/SentrySdk.Dotnet.cs | 546 ++++++++++++++++++ .../{SentryUnity.cs => SentrySdk.cs} | 24 +- .../{SentryUnitySDK.cs => SentryUnitySdk.cs} | 15 +- src/Sentry.Unity/WebGL/SentryWebGL.cs | 2 +- src/sentry-dotnet | 2 +- test/Sentry.Unity.Tests/ContextWriterTests.cs | 2 +- test/Sentry.Unity.Tests/IntegrationTests.cs | 8 +- .../ScreenshotEventProcessorTests.cs | 4 +- test/Sentry.Unity.Tests/SentryTests.cs | 4 +- test/Sentry.Unity.Tests/SentryUnityTests.cs | 40 +- .../SessionIntegrationTests.cs | 4 +- .../TraceGenerationIntegrationTests.cs | 2 +- .../UnityBadGatewayExceptionFilterTests.cs | 4 +- .../UnityEventScopeTests.cs | 18 +- .../ViewHierarchyEventProcessorTests.cs | 4 +- 27 files changed, 673 insertions(+), 102 deletions(-) create mode 100644 src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs create mode 100644 src/Sentry.Unity/SentrySdk.Dotnet.cs rename src/Sentry.Unity/{SentryUnity.cs => SentrySdk.cs} (74%) rename src/Sentry.Unity/{SentryUnitySDK.cs => SentryUnitySdk.cs} (91%) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 918cc2165..63e56d08f 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -15,7 +15,9 @@ #endif using System; +using Sentry.Unity; using Sentry.Extensibility; +using Sentry.Unity.NativeUtils; #if UNITY_2020_3_OR_NEWER using System.Buffers; using System.Runtime.InteropServices; @@ -37,9 +39,9 @@ [assembly: AlwaysLinkAssembly] -namespace Sentry.Unity +namespace Sentry.Internal.Unity { - public static class SentryInitialization + internal static class SentryInitialization { public const string StartupTransactionOperation = "app.start"; public static ISpan InitSpan; @@ -53,20 +55,23 @@ public static class SentryInitialization #else [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] #endif - public static void Init() + internal static void Init() { - var unityInfo = new SentryUnityInfo(); + // We're setting up `UnityInfo` and the platform specific configure callbacks as the very first thing to be + // available during initialization. + SetupPlatformConfiguration(); + // Loading the options invokes the ScriptableOption`Configure` callback. Users can disable the SDK via code. - var options = ScriptableSentryUnityOptions.LoadSentryUnityOptions(unityInfo); + var options = ScriptableSentryUnityOptions.LoadSentryUnityOptions(); if (options != null && options.ShouldInitializeSdk()) { // Certain integrations require access to preprocessor directives so we provide them as `.cs` and - // compile them with the game instead of precompiling them with the rest of the SDK. + // compile them with the game instead of pre-compiling them with the rest of the SDK. // i.e. SceneManagerAPI requires UNITY_2020_3_OR_NEWER + // TODO: Find a way to move this into `SentrySdk.Init` too SentryIntegrations.Configure(options); - // Configures scope sync and (by default) initializes the native SDK. - SetupNativeSdk(options, unityInfo); - SentryUnity.Init(options); + + SentrySdk.Init(options); SetupStartupTracing(options); } else @@ -74,37 +79,25 @@ public static void Init() // If the SDK is not `enabled` we're closing down the native layer as well. This is especially relevant // in a `built-time-initialization` scenario where the native SDKs self-initialize. #if SENTRY_NATIVE_COCOA - SentryNativeCocoa.Close(options, unityInfo); + SentryNativeCocoa.Close(options, SentryPlatformServices.UnityInfo); #elif SENTRY_NATIVE_ANDROID - SentryNativeAndroid.Close(options, unityInfo); + SentryNativeAndroid.Close(options, SentryPlatformServices.UnityInfo); #endif } } - private static void SetupNativeSdk(SentryUnityOptions options, SentryUnityInfo unityInfo) + private static void SetupPlatformConfiguration() { - try - { + SentryPlatformServices.UnityInfo = new SentryUnityInfo(); #if SENTRY_NATIVE_COCOA - SentryNativeCocoa.Configure(options, unityInfo); + SentryPlatformServices.PlatformConfiguration = SentryNativeCocoa.Configure; #elif SENTRY_NATIVE_ANDROID - SentryNativeAndroid.Configure(options, unityInfo); + SentryPlatformServices.PlatformConfiguration = SentryNativeAndroid.Configure; #elif SENTRY_NATIVE - SentryNative.Configure(options, unityInfo); + SentryPlatformServices.PlatformConfiguration = SentryNative.Configure; #elif SENTRY_WEBGL - SentryWebGL.Configure(options); + SentryPlatformServices.PlatformConfiguration = SentryWebGL.Configure; #endif - } - catch (DllNotFoundException e) - { - options.DiagnosticLogger?.LogError(e, - "Sentry native-error capture configuration failed to load a native library. This usually " + - "means the library is missing from the application bundle or the installation directory."); - } - catch (Exception e) - { - options.DiagnosticLogger?.LogError(e, "Sentry native error capture configuration failed."); - } } private static void SetupStartupTracing(SentryUnityOptions options) diff --git a/package-dev/Runtime/SentryIntegrations.cs b/package-dev/Runtime/SentryIntegrations.cs index e39cbb974..e1480d17b 100644 --- a/package-dev/Runtime/SentryIntegrations.cs +++ b/package-dev/Runtime/SentryIntegrations.cs @@ -10,10 +10,11 @@ using Sentry.Extensibility; using Sentry.Integrations; +using Sentry.Unity; using UnityEngine; using UnityEngine.SceneManagement; -namespace Sentry.Unity +namespace Sentry.Internal.Unity { public static class SentryIntegrations { diff --git a/package-dev/Runtime/SentryUserFeedback.cs b/package-dev/Runtime/SentryUserFeedback.cs index 4e01d3a06..b6475b802 100644 --- a/package-dev/Runtime/SentryUserFeedback.cs +++ b/package-dev/Runtime/SentryUserFeedback.cs @@ -86,7 +86,7 @@ public void SendFeedback() else { // Since there is no screenshot added we can capture the feedback right away - SentryUnity.CaptureFeedback(_description.text, _email.text, _name.text, addScreenshot: false); + SentrySdk.CaptureFeedback(_description.text, _email.text, _name.text, addScreenshot: false); } } @@ -99,7 +99,7 @@ private IEnumerator HideFormAndCaptureFeedback() // We're waiting for the EndOfFrame so the FeedbackForm gets updated before capturing the screenshot yield return new WaitForEndOfFrame(); - SentryUnity.CaptureFeedback(_description.text, _email.text, _name.text, addScreenshot: true); + SentrySdk.CaptureFeedback(_description.text, _email.text, _name.text, addScreenshot: true); ResetUserFeedback(); } diff --git a/package-dev/Tests/Runtime/SentryIntegrationsTests.cs b/package-dev/Tests/Runtime/SentryIntegrationsTests.cs index 38bcfd66f..e025a7fdc 100644 --- a/package-dev/Tests/Runtime/SentryIntegrationsTests.cs +++ b/package-dev/Tests/Runtime/SentryIntegrationsTests.cs @@ -5,7 +5,7 @@ using System; using System.Collections; using NUnit.Framework; -using Sentry.Unity.Tests; +using Sentry.Internal.Unity; using UnityEngine.SceneManagement; using UnityEngine.TestTools; @@ -52,7 +52,7 @@ public IEnumerator Configure_TranceSampleRateOne_AddsSceneManagerTracingIntegrat public static IDisposable InitSentrySdk(SentryUnityOptions options) { - SentryUnity.Init(options); + SentrySdk.Init(options); return new SentryDisposable(); } diff --git a/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs b/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs index 541da7092..edc0036bf 100644 --- a/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs +++ b/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs @@ -5,8 +5,8 @@ using System; using System.Collections; using NUnit.Framework; +using Sentry.Internal.Unity; using Sentry.Unity.Tests; -using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; diff --git a/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs b/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs index 7d8afd0a7..9ab032cac 100644 --- a/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs +++ b/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Threading; -using System.Threading.Tasks; using Sentry; +using Sentry.Unity; using UnityEngine; public class AdditionalButtons : MonoBehaviour diff --git a/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs b/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs index 674fc54c3..61dc61067 100644 --- a/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs +++ b/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs @@ -1,15 +1,17 @@ using System; -using System.Globalization; using System.Runtime.CompilerServices; -using Sentry; +using Sentry.Unity; using UnityEngine; using UnityEngine.Assertions; + public class BugFarmButtons : MonoBehaviour { private void Awake() { Debug.Log("Sample 🐛"); + + SentrySdk.Init(options => {}); } private void Start() diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeButtons.cs b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeButtons.cs index c43ea33da..28ce26844 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeButtons.cs +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeButtons.cs @@ -1,7 +1,7 @@ -using Sentry; -using System; +using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using Sentry.Unity; using UnityEngine; using UnityEngine.Diagnostics; using UnityEngine.UI; diff --git a/samples/unity-of-bugs/Assets/Scripts/ThreadingSamples.cs b/samples/unity-of-bugs/Assets/Scripts/ThreadingSamples.cs index 7aac93994..b8cf7e3be 100644 --- a/samples/unity-of-bugs/Assets/Scripts/ThreadingSamples.cs +++ b/samples/unity-of-bugs/Assets/Scripts/ThreadingSamples.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Sentry; +using Sentry.Unity; using UnityEngine; using UnityEngine.Assertions; diff --git a/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs new file mode 100644 index 000000000..d5d06c472 --- /dev/null +++ b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs @@ -0,0 +1,9 @@ +using System; + +namespace Sentry.Unity.NativeUtils; + +public static class SentryPlatformServices +{ + public static ISentryUnityInfo? UnityInfo { get; set; } + public static Action? PlatformConfiguration { get; set; } +} diff --git a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs index 3684172fb..2b401242d 100644 --- a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs +++ b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Sentry.Extensibility; using Sentry.Unity.Integrations; +using Sentry.Unity.NativeUtils; using UnityEngine; namespace Sentry.Unity; @@ -120,12 +121,12 @@ public static string GetConfigPath(string? notDefaultConfigName = null) /// /// Used for loading the SentryUnityOptions from the ScriptableSentryUnityOptions during runtime. /// - public static SentryUnityOptions? LoadSentryUnityOptions(ISentryUnityInfo unityInfo) + public static SentryUnityOptions? LoadSentryUnityOptions() { var scriptableOptions = Resources.Load($"{ConfigRootFolder}/{ConfigName}"); if (scriptableOptions is not null) { - return scriptableOptions.ToSentryUnityOptions(false, unityInfo); + return scriptableOptions.ToSentryUnityOptions(false, SentryPlatformServices.UnityInfo); } return null; diff --git a/src/Sentry.Unity/SentryMonoBehaviour.cs b/src/Sentry.Unity/SentryMonoBehaviour.cs index 0945d3e72..1bac791d3 100644 --- a/src/Sentry.Unity/SentryMonoBehaviour.cs +++ b/src/Sentry.Unity/SentryMonoBehaviour.cs @@ -39,9 +39,9 @@ public static SentryMonoBehaviour Instance public partial class SentryMonoBehaviour { public void StartAwakeSpan(MonoBehaviour monoBehaviour) => - SentrySdk.GetSpan()?.StartChild("awake", $"{monoBehaviour.gameObject.name}.{monoBehaviour.GetType().Name}"); + Sentry.SentrySdk.GetSpan()?.StartChild("awake", $"{monoBehaviour.gameObject.name}.{monoBehaviour.GetType().Name}"); - public void FinishAwakeSpan() => SentrySdk.GetSpan()?.Finish(SpanStatus.Ok); + public void FinishAwakeSpan() => Sentry.SentrySdk.GetSpan()?.Finish(SpanStatus.Ok); } /// diff --git a/src/Sentry.Unity/SentrySdk.Dotnet.cs b/src/Sentry.Unity/SentrySdk.Dotnet.cs new file mode 100644 index 000000000..00341dc3d --- /dev/null +++ b/src/Sentry.Unity/SentrySdk.Dotnet.cs @@ -0,0 +1,546 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Sentry.Extensibility; +using Sentry.Infrastructure; +using Sentry.Internal; +using Sentry.Protocol.Envelopes; + +namespace Sentry.Unity; + +public static partial class SentrySdk +{ + /// + /// Flushes the queue of captured events until the timeout set in + /// is reached. + /// + /// + /// Blocks synchronously. Prefer in async code. + /// + [DebuggerStepThrough] + public static void Flush() => Sentry.SentrySdk.Flush(); + + /// + /// Flushes the queue of captured events until the timeout is reached. + /// + /// The amount of time allowed for flushing. + /// + /// Blocks synchronously. Prefer in async code. + /// + [DebuggerStepThrough] + public static void Flush(TimeSpan timeout) => Sentry.SentrySdk.Flush(timeout); + + /// + /// Flushes the queue of captured events until the timeout set in + /// is reached. + /// + /// A task to await for the flush operation. + [DebuggerStepThrough] + public static Task FlushAsync() => Sentry.SentrySdk.FlushAsync(); + + /// + /// Flushes the queue of captured events until the timeout is reached. + /// + /// The amount of time allowed for flushing. + /// A task to await for the flush operation. + [DebuggerStepThrough] + public static Task FlushAsync(TimeSpan timeout) => Sentry.SentrySdk.FlushAsync(timeout); + + /// + /// Whether the SDK is enabled or not. + /// + public static bool IsEnabled { [DebuggerStepThrough] get => Sentry.SentrySdk.IsEnabled; } + + /// + /// Creates a new scope that will terminate when disposed. + /// + /// + /// Pushes a new scope while inheriting the current scope's data. + /// + /// A state object to be added to the scope. + /// A disposable that when disposed, ends the created scope. + [DebuggerStepThrough] + public static IDisposable PushScope(TState state) => Sentry.SentrySdk.PushScope(state); + + /// + /// Creates a new scope that will terminate when disposed. + /// + /// A disposable that when disposed, ends the created scope. + [DebuggerStepThrough] + public static IDisposable PushScope() => Sentry.SentrySdk.PushScope(); + + /// + /// Binds the client to the current scope. + /// + /// The client. + [DebuggerStepThrough] + public static void BindClient(ISentryClient client) => Sentry.SentrySdk.BindClient(client); + + /// + /// Adds a breadcrumb to the current Scope. + /// + /// + /// If a message is provided it’s rendered as text and the whitespace is preserved. + /// Very long text might be abbreviated in the UI. + /// + /// Categories are dotted strings that indicate what the crumb is or where it comes from. + /// Typically it’s a module name or a descriptive string. + /// For instance ui.click could be used to indicate that a click happened in the UI or flask could be used to indicate that the event originated in the Flask framework. + /// + /// + /// The type of breadcrumb. + /// The default type is default which indicates no specific handling. + /// Other types are currently http for HTTP requests and navigation for navigation events. + /// + /// + /// + /// Data associated with this breadcrumb. + /// Contains a sub-object whose contents depend on the breadcrumb type. + /// Additional parameters that are unsupported by the type are rendered as a key/value table. + /// + /// Breadcrumb level. + /// + [DebuggerStepThrough] + public static void AddBreadcrumb( + string message, + string? category = null, + string? type = null, + IDictionary? data = null, + BreadcrumbLevel level = default) + => Sentry.SentrySdk.AddBreadcrumb(message, category, type, data, level); + + /// + /// Adds a breadcrumb to the current scope. + /// + /// + /// This overload is intended to be used by integrations only. + /// The objective is to allow better testability by allowing control of the timestamp set to the breadcrumb. + /// + /// An optional . + /// The message. + /// The category. + /// The type. + /// The data. + /// The level. + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static void AddBreadcrumb( + ISystemClock? clock, + string message, + string? category = null, + string? type = null, + IDictionary? data = null, + BreadcrumbLevel level = default) + => Sentry.SentrySdk.AddBreadcrumb(clock, message, category, type, data, level); + + /// + /// Adds a breadcrumb to the current Scope. + /// + /// The breadcrumb to be added + /// A hint providing additional context that can be used in the BeforeBreadcrumb callback + /// + [DebuggerStepThrough] + public static void AddBreadcrumb(Breadcrumb breadcrumb, SentryHint? hint = null) + => Sentry.SentrySdk.AddBreadcrumb(breadcrumb, hint); + + /// + /// Configures the scope through the callback. + /// + /// The configure scope callback. + [DebuggerStepThrough] + public static void ConfigureScope(Action configureScope) + => Sentry.SentrySdk.ConfigureScope(configureScope); + + /// + /// Configures the scope through the callback. + /// + /// + /// object someValue = ...; + /// SentrySdk.ConfigureScope(static (scope, arg) => scope.SetExtra("key", arg), someValue); + /// + /// + /// + /// The configure scope callback. + /// The argument to pass to the configure scope callback. + public static void ConfigureScope(Action configureScope, TArg arg) + => Sentry.SentrySdk.ConfigureScope(configureScope, arg); + + /// + /// Configures the scope through the callback asynchronously. + /// + /// The configure scope callback. + /// A task that completes when the callback is done or a completed task if the SDK is disabled. + [DebuggerStepThrough] + public static Task ConfigureScopeAsync(Func configureScope) + => Sentry.SentrySdk.ConfigureScopeAsync(configureScope); + + /// + /// Configures the scope through the callback asynchronously. + /// + /// + /// object someValue = ...; + /// SentrySdk.ConfigureScopeAsync(static async (scope, arg) => + /// { + /// scope.SetExtra("key", arg); + /// }, someValue); + /// + /// + /// + /// The configure scope callback. + /// The argument to pass to the configure scope callback. + /// A task that completes when the callback is done or a completed task if the SDK is disabled. + [DebuggerStepThrough] + public static Task ConfigureScopeAsync(Func configureScope, TArg arg) + => Sentry.SentrySdk.ConfigureScopeAsync(configureScope, arg); + + /// + /// Sets a tag on the current scope. + /// + [DebuggerStepThrough] + public static void SetTag(string key, string value) + => Sentry.SentrySdk.SetTag(key, value); + + /// + /// Removes a tag from the current scope. + /// + [DebuggerStepThrough] + public static void UnsetTag(string key) + => Sentry.SentrySdk.UnsetTag(key); + + /// + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool CaptureEnvelope(Envelope envelope) + => Sentry.SentrySdk.CaptureEnvelope(envelope); + + /// + /// Captures the event, passing a hint, using the specified scope. + /// + /// The event. + /// The scope. + /// a hint for the event. + /// The Id of the event. + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static SentryId CaptureEvent(SentryEvent evt, Scope? scope = null, SentryHint? hint = null) + => Sentry.SentrySdk.CaptureEvent(evt, scope, hint); + + /// + /// Captures an event with a configurable scope. + /// + /// + /// This allows modifying a scope without affecting other events. + /// + /// The event. + /// The callback to configure the scope. + /// The Id of the event. + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static SentryId CaptureEvent(SentryEvent evt, Action configureScope) + => Sentry.SentrySdk.CaptureEvent(evt, null, configureScope); + + /// + /// Captures an event with a configurable scope. + /// + /// + /// This allows modifying a scope without affecting other events. + /// + /// The event. + /// An optional hint to be provided with the event + /// The callback to configure the scope. + /// The Id of the event. + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static SentryId CaptureEvent(SentryEvent evt, SentryHint? hint, Action configureScope) + => Sentry.SentrySdk.CaptureEvent(evt, hint, configureScope); + + /// + /// Captures the exception. + /// + /// The exception. + /// The Id of the event. + [DebuggerStepThrough] + public static SentryId CaptureException(Exception exception) + => Sentry.SentrySdk.CaptureException(exception); + + /// + /// Captures the exception with a configurable scope. + /// + /// + /// This allows modifying a scope without affecting other events. + /// + /// The exception. + /// The callback to configure the scope. + /// The Id of the event. + [DebuggerStepThrough] + public static SentryId CaptureException(Exception exception, Action configureScope) + => Sentry.SentrySdk.CaptureException(exception, configureScope); + + /// + /// Captures the message. + /// + /// The message to send. + /// The message level. + /// The Id of the event. + [DebuggerStepThrough] + public static SentryId CaptureMessage(string message, SentryLevel level = SentryLevel.Info) + => Sentry.SentrySdk.CaptureMessage(message, level); + + /// + /// Captures the message with a configurable scope. + /// + /// + /// This allows modifying a scope without affecting other events. + /// + /// The message to send. + /// The callback to configure the scope. + /// The message level. + /// The Id of the event. + [DebuggerStepThrough] + public static SentryId CaptureMessage(string message, Action configureScope, SentryLevel level = SentryLevel.Info) + => Sentry.SentrySdk.CaptureMessage(message, configureScope, level); + + /// + /// Captures feedback from the user. + /// + [DebuggerStepThrough] + public static void CaptureFeedback(SentryFeedback feedback, Action configureScope, SentryHint? hint = null) + => Sentry.SentrySdk.CaptureFeedback(feedback, configureScope, hint); + + /// + /// Captures feedback from the user. + /// + [DebuggerStepThrough] + public static void CaptureFeedback(SentryFeedback feedback, Scope? scope = null, SentryHint? hint = null) + => Sentry.SentrySdk.CaptureFeedback(feedback, scope, hint); + + /// + /// Captures feedback from the user. + /// + [DebuggerStepThrough] + public static void CaptureFeedback(string message, string? contactEmail = null, string? name = null, + string? replayId = null, string? url = null, SentryId? associatedEventId = null, Scope? scope = null, + SentryHint? hint = null) + => Sentry.SentrySdk.CaptureFeedback(new SentryFeedback(message, contactEmail, name, replayId, url, associatedEventId), + scope, hint); + + /// + /// Captures a user feedback. + /// + /// The user feedback to send to Sentry. + [DebuggerStepThrough] + [Obsolete("Use CaptureFeedback instead.")] + public static void CaptureUserFeedback(UserFeedback userFeedback) + => Sentry.SentrySdk.CaptureUserFeedback(userFeedback); + + /// + /// Captures a user feedback. + /// + /// The event Id. + /// The user email. + /// The user comments. + /// The optional username. + [DebuggerStepThrough] + [Obsolete("Use CaptureFeedback instead.")] + public static void CaptureUserFeedback(SentryId eventId, string email, string comments, string? name = null) + => Sentry.SentrySdk.CaptureUserFeedback(new UserFeedback(eventId, name, email, comments)); + + /// + /// Captures a transaction. + /// + /// + /// Note: this method is NOT meant to be called from user code! + /// Instead, call on the transaction. + /// + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static void CaptureTransaction(SentryTransaction transaction) + => Sentry.SentrySdk.CaptureTransaction(transaction); + + /// + /// Captures a transaction. + /// + /// + /// Note: this method is NOT meant to be called from user code! + /// Instead, call on the transaction. + /// + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static void CaptureTransaction(SentryTransaction transaction, Scope? scope, SentryHint? hint) + => Sentry.SentrySdk.CaptureTransaction(transaction, scope, hint); + + /// + /// Captures a session update. + /// + [DebuggerStepThrough] + public static void CaptureSession(SessionUpdate sessionUpdate) + => Sentry.SentrySdk.CaptureSession(sessionUpdate); + + /// + /// Captures a check-in. + /// + /// + /// Capturing a check-in returns an ID. The ID can be used to update the status. I.e. to update a check-in you + /// captured from `CheckInStatus.InProgress` to `CheckInStatus.Ok`. + /// + /// The monitor slug of the check-in. + /// The status of the check-in. + /// The associated with the check-in. + /// The duration of the check-in. + /// The scope of the check-in. + /// The optional monitor config used to create a Check-In programmatically. + /// The ID of the check-in. + [DebuggerStepThrough] + public static SentryId CaptureCheckIn(string monitorSlug, + CheckInStatus status, + SentryId? sentryId = null, + TimeSpan? duration = null, + Scope? scope = null, + Action? configureMonitorOptions = null) + => Sentry.SentrySdk.CaptureCheckIn( + monitorSlug, + status, + sentryId, + duration, + scope, + configureMonitorOptions); + + /// + /// Starts a transaction. + /// + [DebuggerStepThrough] + public static ITransactionTracer StartTransaction( + ITransactionContext context, + IReadOnlyDictionary customSamplingContext) + => Sentry.SentrySdk.StartTransaction(context, customSamplingContext); + + /// + /// Starts a transaction. + /// + [DebuggerStepThrough] + internal static ITransactionTracer StartTransaction( + ITransactionContext context, + IReadOnlyDictionary customSamplingContext, + DynamicSamplingContext? dynamicSamplingContext) + => Sentry.SentrySdk.StartTransaction(context, customSamplingContext, dynamicSamplingContext); + + /// + /// Starts a transaction. + /// + [DebuggerStepThrough] + public static ITransactionTracer StartTransaction(ITransactionContext context) + => Sentry.SentrySdk.StartTransaction(context); + + /// + /// Starts a transaction. + /// + [DebuggerStepThrough] + public static ITransactionTracer StartTransaction(string name, string operation) + => Sentry.SentrySdk.StartTransaction(name, operation); + + /// + /// Starts a transaction. + /// + [DebuggerStepThrough] + public static ITransactionTracer StartTransaction(string name, string operation, string? description) + => Sentry.SentrySdk.StartTransaction(name, operation, description); + + /// + /// Starts a transaction. + /// + [DebuggerStepThrough] + public static ITransactionTracer StartTransaction(string name, string operation, SentryTraceHeader traceHeader) + => Sentry.SentrySdk.StartTransaction(name, operation, traceHeader); + + /// + /// Binds specified exception the specified span. + /// + /// + /// This method is used internally and is not meant for public use. + /// + [DebuggerStepThrough] + public static void BindException(Exception exception, ISpan span) + => Sentry.SentrySdk.BindException(exception, span); + + /// + /// Gets the last active span. + /// + [DebuggerStepThrough] + public static ISpan? GetSpan() + => Sentry.SentrySdk.GetSpan(); + + /// + /// Gets the Sentry trace header of the parent that allows tracing across services + /// + [DebuggerStepThrough] + public static SentryTraceHeader? GetTraceHeader() + => Sentry.SentrySdk.GetTraceHeader(); + + /// + /// Gets the Sentry "baggage" header that allows tracing across services + /// + [DebuggerStepThrough] + public static BaggageHeader? GetBaggage() + => Sentry.SentrySdk.GetBaggage(); + + /// + /// Continues a trace based on HTTP header values provided as strings. + /// + /// + /// If no "sentry-trace" header is provided a random trace ID and span ID is created. + /// + [DebuggerStepThrough] + public static TransactionContext ContinueTrace( + string? traceHeader, + string? baggageHeader, + string? name = null, + string? operation = null) + => Sentry.SentrySdk.ContinueTrace(traceHeader, baggageHeader, name, operation); + + /// + /// Continues a trace based on HTTP header values. + /// + /// + /// If no "sentry-trace" header is provided a random trace ID and span ID is created. + /// + [DebuggerStepThrough] + public static TransactionContext ContinueTrace( + SentryTraceHeader? traceHeader, + BaggageHeader? baggageHeader, + string? name = null, + string? operation = null) + => Sentry.SentrySdk.ContinueTrace(traceHeader, baggageHeader, name, operation); + + /// + [DebuggerStepThrough] + public static void StartSession() + => Sentry.SentrySdk.StartSession(); + + /// + [DebuggerStepThrough] + public static void EndSession(SessionEndStatus status = SessionEndStatus.Exited) + => Sentry.SentrySdk.EndSession(status); + + /// + [DebuggerStepThrough] + public static void PauseSession() + => Sentry.SentrySdk.PauseSession(); + + /// + [DebuggerStepThrough] + public static void ResumeSession() + => Sentry.SentrySdk.ResumeSession(); + + /// + /// Deliberately crashes an application, which is useful for testing and demonstration purposes. + /// + /// + /// The method is marked obsolete only to discourage accidental misuse. + /// We do not intend to remove it. + /// + [Obsolete("WARNING: This method deliberately causes a crash, and should not be used in a real application.")] + public static void CauseCrash(CrashType crashType) => Sentry.SentrySdk.CauseCrash(crashType); +} diff --git a/src/Sentry.Unity/SentryUnity.cs b/src/Sentry.Unity/SentrySdk.cs similarity index 74% rename from src/Sentry.Unity/SentryUnity.cs rename to src/Sentry.Unity/SentrySdk.cs index 4306d8dce..2cfe448d4 100644 --- a/src/Sentry.Unity/SentryUnity.cs +++ b/src/Sentry.Unity/SentrySdk.cs @@ -1,13 +1,14 @@ using System; using System.ComponentModel; using Sentry.Extensibility; +using Sentry.Unity.NativeUtils; namespace Sentry.Unity; /// /// Sentry Unity initialization class. /// -public static class SentryUnity +public static partial class SentrySdk { private static SentryUnitySdk? UnitySdk; @@ -32,7 +33,25 @@ public static void Init(SentryUnityOptions options) { if (UnitySdk is not null) { - options.DiagnosticLogger?.LogWarning("The SDK has already been initialized."); + options.LogWarning("The SDK has already been initialized."); + } + + if (SentryPlatformServices.UnityInfo is not null) + { + try + { + SentryPlatformServices.PlatformConfiguration?.Invoke(options, SentryPlatformServices.UnityInfo); + } + catch (DllNotFoundException e) + { + options.DiagnosticLogger?.LogError(e, + "Sentry native-error capture configuration failed to load a native library. This usually " + + "means the library is missing from the application bundle or the installation directory."); + } + catch (Exception e) + { + options.DiagnosticLogger?.LogError(e, "Sentry native error capture configuration failed."); + } } UnitySdk = SentryUnitySdk.Init(options); @@ -91,4 +110,5 @@ public static CrashedLastRun GetLastRunState() /// public static void CaptureFeedback(string message, string? email, string? name, bool addScreenshot) => UnitySdk?.CaptureFeedback(message, email, name, addScreenshot); + } diff --git a/src/Sentry.Unity/SentryUnitySDK.cs b/src/Sentry.Unity/SentryUnitySdk.cs similarity index 91% rename from src/Sentry.Unity/SentryUnitySDK.cs rename to src/Sentry.Unity/SentryUnitySdk.cs index 4119c312f..d4a7fa4be 100644 --- a/src/Sentry.Unity/SentryUnitySDK.cs +++ b/src/Sentry.Unity/SentryUnitySdk.cs @@ -49,11 +49,11 @@ private SentryUnitySdk(SentryUnityOptions options) } } - unitySdk._dotnetSdk = SentrySdk.Init(options); + unitySdk._dotnetSdk = Sentry.SentrySdk.Init(options); if (options.NativeContextWriter is { } contextWriter) { - SentrySdk.ConfigureScope((scope) => + Sentry.SentrySdk.CurrentHub.ConfigureScope((scope) => { var task = Task.Run(() => contextWriter.Write(scope)).ContinueWith(t => { @@ -100,18 +100,18 @@ public void Close() } } - public SentryUnity.CrashedLastRun CrashedLastRun() + public SentrySdk.CrashedLastRun CrashedLastRun() { if (_options.CrashedLastRun is null) { _options.DiagnosticLogger?.LogDebug("The SDK does not have a 'CrashedLastRun' set. " + "This might be due to a missing or disabled native integration."); - return SentryUnity.CrashedLastRun.Unknown; + return SentrySdk.CrashedLastRun.Unknown; } return _options.CrashedLastRun.Invoke() - ? SentryUnity.CrashedLastRun.Crashed - : SentryUnity.CrashedLastRun.DidNotCrash; + ? SentrySdk.CrashedLastRun.Crashed + : SentrySdk.CrashedLastRun.DidNotCrash; } public void CaptureFeedback(string message, string? email, string? name, bool addScreenshot) @@ -131,7 +131,6 @@ public void CaptureFeedback(string message, string? email, string? name, bool ad "image/jpeg")) : null; - - SentrySdk.CaptureFeedback(message, email, name, hint: hint); + Sentry.SentrySdk.CurrentHub.CaptureFeedback(message, email, name, hint: hint); } } diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index c46f68888..c35a68f3f 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -12,7 +12,7 @@ public static class SentryWebGL /// Configures the WebGL support. /// /// The Sentry Unity options to use. - public static void Configure(SentryUnityOptions options) + public static void Configure(SentryUnityOptions options, ISentryUnityInfo unityInfo) { options.DiagnosticLogger?.LogDebug("Updating configuration for Unity WebGL."); diff --git a/src/sentry-dotnet b/src/sentry-dotnet index 997dcae2f..b708072f2 160000 --- a/src/sentry-dotnet +++ b/src/sentry-dotnet @@ -1 +1 @@ -Subproject commit 997dcae2fc7c52d0c49c1f412cee329aeb1c8d29 +Subproject commit b708072f280a4f87cce9c0bad1256a05c8623086 diff --git a/test/Sentry.Unity.Tests/ContextWriterTests.cs b/test/Sentry.Unity.Tests/ContextWriterTests.cs index ca9d40c11..b432c5ea0 100644 --- a/test/Sentry.Unity.Tests/ContextWriterTests.cs +++ b/test/Sentry.Unity.Tests/ContextWriterTests.cs @@ -79,7 +79,7 @@ public void Arguments() // act MainThreadData.SentrySystemInfo = sysInfo; - SentryUnity.Init(options); + SentrySdk.Init(options); Assert.IsTrue(context.SyncFinished.WaitOne(TimeSpan.FromSeconds(10))); // assert diff --git a/test/Sentry.Unity.Tests/IntegrationTests.cs b/test/Sentry.Unity.Tests/IntegrationTests.cs index 10e7e4924..f1cebc1b4 100644 --- a/test/Sentry.Unity.Tests/IntegrationTests.cs +++ b/test/Sentry.Unity.Tests/IntegrationTests.cs @@ -32,9 +32,9 @@ public void SetUp() [TearDown] public void TearDown() { - if (SentrySdk.IsEnabled) + if (Sentry.SentrySdk.IsEnabled) { - SentryUnity.Close(); + SentrySdk.Close(); } } @@ -338,7 +338,7 @@ internal static IEnumerator SetupSceneCoroutine(string sceneName, [CallerMemberN internal IDisposable InitSentrySdk(Action? configure = null) { - SentryUnity.Init(options => + SentrySdk.Init(options => { options.Dsn = "https://e9ee299dbf554dfd930bc5f3c90d5d4b@o447951.ingest.sentry.io/4504604988538880"; options.CreateHttpMessageHandler = () => _testHttpClientHandler; @@ -351,6 +351,6 @@ internal IDisposable InitSentrySdk(Action? configure = null) private sealed class SentryDisposable : IDisposable { - public void Dispose() => SentrySdk.Close(); + public void Dispose() => Sentry.SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs index fcad4a82a..0dbc0671c 100644 --- a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs +++ b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs @@ -22,9 +22,9 @@ private class Fixture [TearDown] public void TearDown() { - if (SentrySdk.IsEnabled) + if (Sentry.SentrySdk.IsEnabled) { - SentryUnity.Close(); + SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/SentryTests.cs b/test/Sentry.Unity.Tests/SentryTests.cs index d0d8494c1..21827e295 100644 --- a/test/Sentry.Unity.Tests/SentryTests.cs +++ b/test/Sentry.Unity.Tests/SentryTests.cs @@ -6,7 +6,7 @@ public static class SentryTests { internal static IDisposable InitSentrySdk(Action? configure = null, TestHttpClientHandler? testHttpClientHandler = null) { - SentryUnity.Init(options => + SentrySdk.Init(options => { options.Dsn = "https://e9ee299dbf554dfd930bc5f3c90d5d4b@o447951.ingest.sentry.io/4504604988538880"; if (testHttpClientHandler is not null) @@ -22,6 +22,6 @@ internal static IDisposable InitSentrySdk(Action? configure private sealed class SentryDisposable : IDisposable { - public void Dispose() => SentrySdk.Close(); + public void Dispose() => Sentry.SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/SentryUnityTests.cs b/test/Sentry.Unity.Tests/SentryUnityTests.cs index 0c1c2f9e9..165459c56 100644 --- a/test/Sentry.Unity.Tests/SentryUnityTests.cs +++ b/test/Sentry.Unity.Tests/SentryUnityTests.cs @@ -16,9 +16,9 @@ public class SentryUnitySelfInitializationTests [TearDown] public void TearDown() { - if (SentrySdk.IsEnabled) + if (Sentry.SentrySdk.IsEnabled) { - SentryUnity.Close(); + SentrySdk.Close(); } } @@ -72,9 +72,9 @@ public void SentryUnity_OptionsValid_Initializes() Dsn = TestDsn }; - SentryUnity.Init(options); + SentrySdk.Init(options); - Assert.IsTrue(SentrySdk.IsEnabled); + Assert.IsTrue(Sentry.SentrySdk.IsEnabled); } [Test] @@ -83,9 +83,9 @@ public void SentryUnity_OptionsInvalid_DoesNotInitialize() var options = new SentryUnityOptions(); // Even tho the defaults are set the DSN is missing making the options invalid for initialization - SentryUnity.Init(options); + SentrySdk.Init(options); - Assert.IsFalse(SentrySdk.IsEnabled); + Assert.IsFalse(Sentry.SentrySdk.IsEnabled); } [Test] @@ -99,8 +99,8 @@ public void Init_MultipleTimes_LogsWarning() DiagnosticLogger = testLogger, }; - SentryUnity.Init(options); - SentryUnity.Init(options); + SentrySdk.Init(options); + SentrySdk.Init(options); Assert.IsTrue(testLogger.Logs.Any(log => log.logLevel == SentryLevel.Warning && @@ -111,13 +111,13 @@ public void Init_MultipleTimes_LogsWarning() public void GetLastRunState_WithoutInit_ReturnsUnknown() { // Make sure SDK is closed - SentryUnity.Close(); + SentrySdk.Close(); // Act - var result = SentryUnity.GetLastRunState(); + var result = SentrySdk.GetLastRunState(); // Assert - Assert.AreEqual(SentryUnity.CrashedLastRun.Unknown, result); + Assert.AreEqual(SentrySdk.CrashedLastRun.Unknown, result); } [Test] @@ -131,11 +131,11 @@ public void GetLastRunState_WhenCrashed_ReturnsCrashed() }; // Act - SentryUnity.Init(options); - var result = SentryUnity.GetLastRunState(); + SentrySdk.Init(options); + var result = SentrySdk.GetLastRunState(); // Assert - Assert.AreEqual(SentryUnity.CrashedLastRun.Crashed, result); + Assert.AreEqual(SentrySdk.CrashedLastRun.Crashed, result); } [Test] @@ -149,11 +149,11 @@ public void GetLastRunState_WhenNotCrashed_ReturnsDidNotCrash() }; // Act - SentryUnity.Init(options); - var result = SentryUnity.GetLastRunState(); + SentrySdk.Init(options); + var result = SentrySdk.GetLastRunState(); // Assert - Assert.AreEqual(SentryUnity.CrashedLastRun.DidNotCrash, result); + Assert.AreEqual(SentrySdk.CrashedLastRun.DidNotCrash, result); } [Test] @@ -167,10 +167,10 @@ public void GetLastRunState_WithNullDelegate_ReturnsUnknown() }; // Act - SentryUnity.Init(options); - var result = SentryUnity.GetLastRunState(); + SentrySdk.Init(options); + var result = SentrySdk.GetLastRunState(); // Assert - Assert.AreEqual(SentryUnity.CrashedLastRun.Unknown, result); + Assert.AreEqual(SentrySdk.CrashedLastRun.Unknown, result); } } diff --git a/test/Sentry.Unity.Tests/SessionIntegrationTests.cs b/test/Sentry.Unity.Tests/SessionIntegrationTests.cs index 6fb7aee3f..00ee545cd 100644 --- a/test/Sentry.Unity.Tests/SessionIntegrationTests.cs +++ b/test/Sentry.Unity.Tests/SessionIntegrationTests.cs @@ -27,7 +27,7 @@ public IEnumerator SessionIntegration_Init_SentryMonoBehaviourCreated() internal IDisposable InitSentrySdk(Action? configure = null) { - SentryUnity.Init(options => + SentrySdk.Init(options => { options.Dsn = "https://e9ee299dbf554dfd930bc5f3c90d5d4b@o447951.ingest.sentry.io/4504604988538880"; configure?.Invoke(options); @@ -38,6 +38,6 @@ internal IDisposable InitSentrySdk(Action? configure = null) private sealed class SentryDisposable : IDisposable { - public void Dispose() => SentrySdk.Close(); + public void Dispose() => Sentry.SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/TraceGenerationIntegrationTests.cs b/test/Sentry.Unity.Tests/TraceGenerationIntegrationTests.cs index 751c35d5a..91189d44e 100644 --- a/test/Sentry.Unity.Tests/TraceGenerationIntegrationTests.cs +++ b/test/Sentry.Unity.Tests/TraceGenerationIntegrationTests.cs @@ -29,7 +29,7 @@ private class Fixture public void SetUp() { _fixture.TestHub = new TestHub(); - SentrySdk.UseHub(_fixture.TestHub); + Sentry.SentrySdk.UseHub(_fixture.TestHub); } [Test] diff --git a/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs b/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs index bfab5d235..c1817a4ee 100644 --- a/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs +++ b/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs @@ -26,7 +26,7 @@ public void Filter_FiltersBadGatewayExceptionsOfTypeException() => internal IDisposable InitSentrySdk(Action? configure = null) { - SentryUnity.Init(options => + SentrySdk.Init(options => { options.Dsn = "https://e9ee299dbf554dfd930bc5f3c90d5d4b@o447951.ingest.sentry.io/4504604988538880"; options.CreateHttpMessageHandler = () => _testHttpClientHandler; @@ -39,6 +39,6 @@ internal IDisposable InitSentrySdk(Action? configure = null) private sealed class SentryDisposable : IDisposable { - public void Dispose() => SentrySdk.Close(); + public void Dispose() => Sentry.SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/UnityEventScopeTests.cs b/test/Sentry.Unity.Tests/UnityEventScopeTests.cs index 303bcace5..30253cc00 100644 --- a/test/Sentry.Unity.Tests/UnityEventScopeTests.cs +++ b/test/Sentry.Unity.Tests/UnityEventScopeTests.cs @@ -35,9 +35,9 @@ public void TearDown() { Object.Destroy(_gameObject); - if (SentrySdk.IsEnabled) + if (Sentry.SentrySdk.IsEnabled) { - SentryUnity.Close(); + SentrySdk.Close(); } } @@ -66,7 +66,7 @@ public void SentrySdkCaptureEvent_OnNotUIThread_Succeeds() Debug = true, DiagnosticLogger = _testLogger }; - SentryUnity.Init(options); + SentrySdk.Init(options); var sentryEvent = new SentryEvent { @@ -77,9 +77,9 @@ public void SentrySdkCaptureEvent_OnNotUIThread_Succeeds() }; // act - Task.Run(() => SentrySdk.CaptureEvent(sentryEvent)).Wait(); + Task.Run(() => Sentry.SentrySdk.CaptureEvent(sentryEvent)).Wait(); - SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); + Sentry.SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); // assert var logsFound = _testLogger.Logs.Where(log => log.logLevel >= SentryLevel.Warning && log.message != "Cache directory is empty.").ToList(); @@ -123,7 +123,7 @@ public void SentrySdkCaptureEvent(bool captureOnUiThread) MainThreadData.SentrySystemInfo = systemInfo; MainThreadData.CollectData(); - SentryUnity.Init(options); + SentrySdk.Init(options); // Act var @event = new SentryEvent @@ -134,16 +134,16 @@ public void SentrySdkCaptureEvent(bool captureOnUiThread) // Events should have the same context, regardless of the thread they were issued on. if (captureOnUiThread) { - SentrySdk.CaptureEvent(@event); + Sentry.SentrySdk.CaptureEvent(@event); } else { - var task = Task.Run(() => SentrySdk.CaptureEvent(@event)); + var task = Task.Run(() => Sentry.SentrySdk.CaptureEvent(@event)); Thread.Sleep(10); task.Wait(); } - SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); + Sentry.SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); // Assert Assert.AreEqual(systemInfo.GraphicsDeviceVendorId!.Value, @event.Contexts.Gpu.VendorId); diff --git a/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs b/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs index 85adba0b8..cf3e20bbc 100644 --- a/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs +++ b/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs @@ -24,9 +24,9 @@ private class Fixture [TearDown] public void TearDown() { - if (SentrySdk.IsEnabled) + if (Sentry.SentrySdk.IsEnabled) { - SentryUnity.Close(); + SentrySdk.Close(); } } From bb4dbc0c7c0cbf6656971f9be9281033961ab55c Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 4 Jul 2025 13:10:38 +0200 Subject: [PATCH 02/42] fixed using in smoketester --- test/Scripts.Integration.Test/Scripts/SmokeTester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs index edd0a4430..3e62045a5 100644 --- a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs +++ b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Sentry; +using Sentry.Unity; using UnityEngine; using Debug = UnityEngine.Debug; From 0f97f0b2017534189c1dfe30822bab7fd2727157 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 4 Jul 2025 14:09:19 +0200 Subject: [PATCH 03/42] we actually need both. idk if that's great. doesn't look great --- test/Scripts.Integration.Test/Scripts/SmokeTester.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs index 3e62045a5..16becdfdf 100644 --- a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs +++ b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Sentry; using Sentry.Unity; using UnityEngine; using Debug = UnityEngine.Debug; From a76e957fe4419f6241dfe8f266b8ccc41fa4ec7e Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 13:58:26 +0200 Subject: [PATCH 04/42] bumped .NET to have the comment --- src/sentry-dotnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry-dotnet b/src/sentry-dotnet index b708072f2..9e38832d5 160000 --- a/src/sentry-dotnet +++ b/src/sentry-dotnet @@ -1 +1 @@ -Subproject commit b708072f280a4f87cce9c0bad1256a05c8623086 +Subproject commit 9e38832d5c56c7b874511b82bfeba7ead17436c9 From e494279deb7238b44b3859be38135bd2c5bf380e Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 13:58:31 +0200 Subject: [PATCH 05/42] Updated CHANGELOG.md --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9e5f07a..ed8e25a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ ## Unreleased +### Breaking Changes + +- **Breaking Change**: The Unity SDK's static API has been simplified moved from `Sentry.Unity.SentryUnity` and `Sentry.SentrySdk` + to `Sentry.Unity.SentrySdk`. + This change enables manual SDK initialization with full functionality, previously only available through auto-initialization. + The underlying .NET SDK's `SentrySdk` class is now internal, and several previously public classes like `SentryInitialization` + and `SentryIntegrations` are now internal. + + **Migration**: Update your using statements from `using Sentry;` to `using Sentry.Unity;`. IDEs like Rider can automatically + import the missing references. In some cases, you may need both `using Sentry.Unity;` (for the static API) and `using Sentry;` + (for types like `SentryId`). No changes are required to your actual SDK method calls (e.g., `SentrySdk.CaptureException()` + remains the same). ([#2227](https://github.com/getsentry/sentry-unity/pull/2227)) + ### Features - The SDK now comes with a `SentryUserFeedback` prefab ready to be used. You can drag and drop it into your scene or From df211a3750396fabed87d7776387614f2bda2579 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 15:12:27 +0200 Subject: [PATCH 06/42] bumped to cleaned up version of .NET --- src/sentry-dotnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry-dotnet b/src/sentry-dotnet index 9e38832d5..2aa2bb216 160000 --- a/src/sentry-dotnet +++ b/src/sentry-dotnet @@ -1 +1 @@ -Subproject commit 9e38832d5c56c7b874511b82bfeba7ead17436c9 +Subproject commit 2aa2bb21648ef7645c13fc263449de2c326fdd1d From 19c4e78970bd5ea2db8c2e12cced8e7611dfce3d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 16:31:06 +0200 Subject: [PATCH 07/42] added services logging to init --- src/Sentry.Unity/SentrySdk.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Sentry.Unity/SentrySdk.cs b/src/Sentry.Unity/SentrySdk.cs index 2cfe448d4..a12c89ee1 100644 --- a/src/Sentry.Unity/SentrySdk.cs +++ b/src/Sentry.Unity/SentrySdk.cs @@ -33,26 +33,30 @@ public static void Init(SentryUnityOptions options) { if (UnitySdk is not null) { - options.LogWarning("The SDK has already been initialized."); + options.LogWarning("The SDK has already been initialized. Skipping initialization."); } - if (SentryPlatformServices.UnityInfo is not null) + if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is not null) { try { - SentryPlatformServices.PlatformConfiguration?.Invoke(options, SentryPlatformServices.UnityInfo); + SentryPlatformServices.PlatformConfiguration.Invoke(options, SentryPlatformServices.UnityInfo); } catch (DllNotFoundException e) { - options.DiagnosticLogger?.LogError(e, + options.LogError(e, "Sentry native-error capture configuration failed to load a native library. This usually " + "means the library is missing from the application bundle or the installation directory."); } catch (Exception e) { - options.DiagnosticLogger?.LogError(e, "Sentry native error capture configuration failed."); + options.LogError(e, "Sentry native error capture configuration failed."); } } + else + { + options.LogWarning("The SDK's Platform Services have not been set up. Native support will be limited."); + } UnitySdk = SentryUnitySdk.Init(options); } @@ -68,7 +72,7 @@ public static void Close() } /// - /// Represents the crash state of the games's previous run. + /// Represents the crash state of the game's previous run. /// Used to determine if the last execution terminated normally or crashed. /// public enum CrashedLastRun @@ -110,5 +114,4 @@ public static CrashedLastRun GetLastRunState() /// public static void CaptureFeedback(string message, string? email, string? name, bool addScreenshot) => UnitySdk?.CaptureFeedback(message, email, name, addScreenshot); - } From 00e788aaf8026d25ea0a48c284cf92e6383d03a0 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 16:39:35 +0200 Subject: [PATCH 08/42] added summary to platformservices --- src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs index d5d06c472..c3df5fefc 100644 --- a/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs +++ b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs @@ -2,6 +2,12 @@ namespace Sentry.Unity.NativeUtils; +/// +/// These are SDK's services that are only available at runtime and cannot be baked into the SDK. The +/// SentryInitialization.cs is provided as .cs and gets compiled with the game. It sets IUnityInfo +/// and the PlatformConfiguration callback during the game's startup so that they are available during initializtion. +/// +/// Consider this internal. public static class SentryPlatformServices { public static ISentryUnityInfo? UnityInfo { get; set; } From bfcd0f1534ea013371ff305745a042417f8ab570 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 16:43:22 +0200 Subject: [PATCH 09/42] fixed namespaces --- package-dev/Runtime/SentryInitialization.cs | 2 +- package-dev/Runtime/SentryIntegrations.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 63e56d08f..f8d1b96d4 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -39,7 +39,7 @@ [assembly: AlwaysLinkAssembly] -namespace Sentry.Internal.Unity +namespace Sentry.Unity { internal static class SentryInitialization { diff --git a/package-dev/Runtime/SentryIntegrations.cs b/package-dev/Runtime/SentryIntegrations.cs index e1480d17b..2ce136673 100644 --- a/package-dev/Runtime/SentryIntegrations.cs +++ b/package-dev/Runtime/SentryIntegrations.cs @@ -14,7 +14,7 @@ using UnityEngine; using UnityEngine.SceneManagement; -namespace Sentry.Internal.Unity +namespace Sentry.Unity { public static class SentryIntegrations { From 44483340476360898f2bdf455765695e61626d87 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 16:43:36 +0200 Subject: [PATCH 10/42] cleaned up bugfarmbuttons --- samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs b/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs index 61dc61067..0126bdd6e 100644 --- a/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs +++ b/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using Sentry; using Sentry.Unity; using UnityEngine; using UnityEngine.Assertions; @@ -10,8 +11,6 @@ public class BugFarmButtons : MonoBehaviour private void Awake() { Debug.Log("Sample 🐛"); - - SentrySdk.Init(options => {}); } private void Start() From 232833cc819c1b356c7f4c30f638f657edaab26a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 16:43:57 +0200 Subject: [PATCH 11/42] namespace clean followup --- package-dev/Tests/Runtime/SentryIntegrationsTests.cs | 1 - package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/package-dev/Tests/Runtime/SentryIntegrationsTests.cs b/package-dev/Tests/Runtime/SentryIntegrationsTests.cs index e025a7fdc..9910258b7 100644 --- a/package-dev/Tests/Runtime/SentryIntegrationsTests.cs +++ b/package-dev/Tests/Runtime/SentryIntegrationsTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections; using NUnit.Framework; -using Sentry.Internal.Unity; using UnityEngine.SceneManagement; using UnityEngine.TestTools; diff --git a/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs b/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs index edc0036bf..ac66d7793 100644 --- a/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs +++ b/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections; using NUnit.Framework; -using Sentry.Internal.Unity; using Sentry.Unity.Tests; using UnityEngine.SceneManagement; using UnityEngine.TestTools; From bcbb5511557b9ad9c0c8865df53b31ca6e377515 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 7 Jul 2025 17:08:58 +0200 Subject: [PATCH 12/42] fixed qualification --- src/Sentry.Unity/SentryMonoBehaviour.cs | 4 ++-- test/Sentry.Unity.Tests/IntegrationTests.cs | 4 ++-- .../ScreenshotEventProcessorTests.cs | 2 +- test/Sentry.Unity.Tests/SentryTests.cs | 2 +- test/Sentry.Unity.Tests/SentryUnityTests.cs | 6 +++--- test/Sentry.Unity.Tests/SessionIntegrationTests.cs | 2 +- .../UnityBadGatewayExceptionFilterTests.cs | 2 +- test/Sentry.Unity.Tests/UnityEventScopeTests.cs | 12 ++++++------ .../ViewHierarchyEventProcessorTests.cs | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Sentry.Unity/SentryMonoBehaviour.cs b/src/Sentry.Unity/SentryMonoBehaviour.cs index 1bac791d3..0945d3e72 100644 --- a/src/Sentry.Unity/SentryMonoBehaviour.cs +++ b/src/Sentry.Unity/SentryMonoBehaviour.cs @@ -39,9 +39,9 @@ public static SentryMonoBehaviour Instance public partial class SentryMonoBehaviour { public void StartAwakeSpan(MonoBehaviour monoBehaviour) => - Sentry.SentrySdk.GetSpan()?.StartChild("awake", $"{monoBehaviour.gameObject.name}.{monoBehaviour.GetType().Name}"); + SentrySdk.GetSpan()?.StartChild("awake", $"{monoBehaviour.gameObject.name}.{monoBehaviour.GetType().Name}"); - public void FinishAwakeSpan() => Sentry.SentrySdk.GetSpan()?.Finish(SpanStatus.Ok); + public void FinishAwakeSpan() => SentrySdk.GetSpan()?.Finish(SpanStatus.Ok); } /// diff --git a/test/Sentry.Unity.Tests/IntegrationTests.cs b/test/Sentry.Unity.Tests/IntegrationTests.cs index f1cebc1b4..23f9def4f 100644 --- a/test/Sentry.Unity.Tests/IntegrationTests.cs +++ b/test/Sentry.Unity.Tests/IntegrationTests.cs @@ -32,7 +32,7 @@ public void SetUp() [TearDown] public void TearDown() { - if (Sentry.SentrySdk.IsEnabled) + if (SentrySdk.IsEnabled) { SentrySdk.Close(); } @@ -351,6 +351,6 @@ internal IDisposable InitSentrySdk(Action? configure = null) private sealed class SentryDisposable : IDisposable { - public void Dispose() => Sentry.SentrySdk.Close(); + public void Dispose() => SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs index 0dbc0671c..36252b96e 100644 --- a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs +++ b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs @@ -22,7 +22,7 @@ private class Fixture [TearDown] public void TearDown() { - if (Sentry.SentrySdk.IsEnabled) + if (SentrySdk.IsEnabled) { SentrySdk.Close(); } diff --git a/test/Sentry.Unity.Tests/SentryTests.cs b/test/Sentry.Unity.Tests/SentryTests.cs index 21827e295..4761751a3 100644 --- a/test/Sentry.Unity.Tests/SentryTests.cs +++ b/test/Sentry.Unity.Tests/SentryTests.cs @@ -22,6 +22,6 @@ internal static IDisposable InitSentrySdk(Action? configure private sealed class SentryDisposable : IDisposable { - public void Dispose() => Sentry.SentrySdk.Close(); + public void Dispose() => SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/SentryUnityTests.cs b/test/Sentry.Unity.Tests/SentryUnityTests.cs index 165459c56..e3326cad6 100644 --- a/test/Sentry.Unity.Tests/SentryUnityTests.cs +++ b/test/Sentry.Unity.Tests/SentryUnityTests.cs @@ -16,7 +16,7 @@ public class SentryUnitySelfInitializationTests [TearDown] public void TearDown() { - if (Sentry.SentrySdk.IsEnabled) + if (SentrySdk.IsEnabled) { SentrySdk.Close(); } @@ -74,7 +74,7 @@ public void SentryUnity_OptionsValid_Initializes() SentrySdk.Init(options); - Assert.IsTrue(Sentry.SentrySdk.IsEnabled); + Assert.IsTrue(SentrySdk.IsEnabled); } [Test] @@ -85,7 +85,7 @@ public void SentryUnity_OptionsInvalid_DoesNotInitialize() // Even tho the defaults are set the DSN is missing making the options invalid for initialization SentrySdk.Init(options); - Assert.IsFalse(Sentry.SentrySdk.IsEnabled); + Assert.IsFalse(SentrySdk.IsEnabled); } [Test] diff --git a/test/Sentry.Unity.Tests/SessionIntegrationTests.cs b/test/Sentry.Unity.Tests/SessionIntegrationTests.cs index 00ee545cd..d53f81698 100644 --- a/test/Sentry.Unity.Tests/SessionIntegrationTests.cs +++ b/test/Sentry.Unity.Tests/SessionIntegrationTests.cs @@ -38,6 +38,6 @@ internal IDisposable InitSentrySdk(Action? configure = null) private sealed class SentryDisposable : IDisposable { - public void Dispose() => Sentry.SentrySdk.Close(); + public void Dispose() => SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs b/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs index c1817a4ee..8ec055f79 100644 --- a/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs +++ b/test/Sentry.Unity.Tests/UnityBadGatewayExceptionFilterTests.cs @@ -39,6 +39,6 @@ internal IDisposable InitSentrySdk(Action? configure = null) private sealed class SentryDisposable : IDisposable { - public void Dispose() => Sentry.SentrySdk.Close(); + public void Dispose() => SentrySdk.Close(); } } diff --git a/test/Sentry.Unity.Tests/UnityEventScopeTests.cs b/test/Sentry.Unity.Tests/UnityEventScopeTests.cs index 30253cc00..ed9c7d795 100644 --- a/test/Sentry.Unity.Tests/UnityEventScopeTests.cs +++ b/test/Sentry.Unity.Tests/UnityEventScopeTests.cs @@ -35,7 +35,7 @@ public void TearDown() { Object.Destroy(_gameObject); - if (Sentry.SentrySdk.IsEnabled) + if (SentrySdk.IsEnabled) { SentrySdk.Close(); } @@ -77,9 +77,9 @@ public void SentrySdkCaptureEvent_OnNotUIThread_Succeeds() }; // act - Task.Run(() => Sentry.SentrySdk.CaptureEvent(sentryEvent)).Wait(); + Task.Run(() => SentrySdk.CaptureEvent(sentryEvent)).Wait(); - Sentry.SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); + SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); // assert var logsFound = _testLogger.Logs.Where(log => log.logLevel >= SentryLevel.Warning && log.message != "Cache directory is empty.").ToList(); @@ -134,16 +134,16 @@ public void SentrySdkCaptureEvent(bool captureOnUiThread) // Events should have the same context, regardless of the thread they were issued on. if (captureOnUiThread) { - Sentry.SentrySdk.CaptureEvent(@event); + SentrySdk.CaptureEvent(@event); } else { - var task = Task.Run(() => Sentry.SentrySdk.CaptureEvent(@event)); + var task = Task.Run(() => SentrySdk.CaptureEvent(@event)); Thread.Sleep(10); task.Wait(); } - Sentry.SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); + SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); // Assert Assert.AreEqual(systemInfo.GraphicsDeviceVendorId!.Value, @event.Contexts.Gpu.VendorId); diff --git a/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs b/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs index cf3e20bbc..93ec9dfc4 100644 --- a/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs +++ b/test/Sentry.Unity.Tests/ViewHierarchyEventProcessorTests.cs @@ -24,7 +24,7 @@ private class Fixture [TearDown] public void TearDown() { - if (Sentry.SentrySdk.IsEnabled) + if (SentrySdk.IsEnabled) { SentrySdk.Close(); } From deb6aa78e19f65b690001f72caa76f9d8d608be6 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 14:26:00 +0200 Subject: [PATCH 13/42] moved scenetracing integration into the SDK --- package-dev/Runtime/SentryIntegrations.cs | 76 ------------------- .../SceneManagerTracingIntegration.cs | 63 +++++++++++++++ src/Sentry.Unity/SentryUnityOptions.cs | 1 + .../SceneManagerTracingIntegrationTests.cs | 34 ++++++--- 4 files changed, 87 insertions(+), 87 deletions(-) create mode 100644 src/Sentry.Unity/Integrations/SceneManagerTracingIntegration.cs rename package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs => test/Sentry.Unity.Tests/SceneManagerTracingIntegrationTests.cs (66%) diff --git a/package-dev/Runtime/SentryIntegrations.cs b/package-dev/Runtime/SentryIntegrations.cs index e39cbb974..1c1e64c9b 100644 --- a/package-dev/Runtime/SentryIntegrations.cs +++ b/package-dev/Runtime/SentryIntegrations.cs @@ -1,7 +1,3 @@ -#if UNITY_2020_3_OR_NEWER -#define SENTRY_SCENE_MANAGER_TRACING_INTEGRATION -#endif - #if !UNITY_EDITOR #if UNITY_WEBGL #define SENTRY_WEBGL @@ -27,21 +23,8 @@ public static void Configure(SentryUnityOptions options) { options.AddIntegration(new StartupTracingIntegration()); } -#endif -#if SENTRY_SCENE_MANAGER_TRACING_INTEGRATION - if (options.AutoSceneLoadTraces) - { - options.AddIntegration(new SceneManagerTracingIntegration()); - } #endif } - else - { -#if SENTRY_SCENE_MANAGER_TRACING_INTEGRATION - options.DiagnosticLogger?.LogDebug("Skipping SceneManagerTracing integration because performance tracing is disabled."); -#endif - } - } } @@ -136,63 +119,4 @@ public static void AfterSceneLoad() } } #endif - -#if SENTRY_SCENE_MANAGER_TRACING_INTEGRATION - public class SceneManagerTracingIntegration : ISdkIntegration - { - private static IDiagnosticLogger Logger; - - public void Register(IHub hub, SentryOptions options) - { - Logger = options.DiagnosticLogger; - - if (SceneManagerAPI.overrideAPI != null) - { - // TODO: Add a place to put a custom 'SceneManagerAPI' on the editor window so we can "decorate" it. - Logger?.LogWarning("Registering SceneManagerTracing integration - overwriting the previous SceneManagerAPI.overrideAPI."); - } - - SceneManagerAPI.overrideAPI = new SceneManagerTracingAPI(Logger); - } - } - - public class SceneManagerTracingAPI : SceneManagerAPI - { - public const string TransactionOperation = "scene.load"; - private const string SpanOperation = "scene.load"; - private readonly IDiagnosticLogger _logger; - - public SceneManagerTracingAPI(IDiagnosticLogger logger) - { - _logger = logger; - } - - protected override AsyncOperation LoadSceneAsyncByNameOrIndex(string sceneName, int sceneBuildIndex, LoadSceneParameters parameters, bool mustCompleteNextFrame) - { - _logger?.LogInfo("Creating '{0}' transaction for '{1}'.", TransactionOperation, sceneName); - - var transaction = SentrySdk.StartTransaction("scene.loading", TransactionOperation); - SentrySdk.ConfigureScope(scope => scope.Transaction = transaction); - - _logger?.LogDebug("Creating '{0}' span.", SpanOperation); - var span = SentrySdk.GetSpan()?.StartChild(SpanOperation, sceneName ?? $"buildIndex:{sceneBuildIndex}"); - - var asyncOp = base.LoadSceneAsyncByNameOrIndex(sceneName, sceneBuildIndex, parameters, mustCompleteNextFrame); - - // TODO: setExtra()? e.g. from the LoadSceneParameters: - // https://github.com/Unity-Technologies/UnityCsReference/blob/02d565cf3dd0f6b15069ba976064c75dc2705b08/Runtime/Export/SceneManager/SceneManager.cs#L30 - // Note: asyncOp.completed triggers in the next frame after finishing (so the time isn't precise). - // https://docs.unity3d.com/2020.3/Documentation/ScriptReference/AsyncOperation-completed.html - asyncOp.completed += _ => - { - _logger?.LogInfo("Finishing '{0}' transaction for '{1}'.", TransactionOperation, sceneName); - - span?.Finish(SpanStatus.Ok); - transaction.Finish(SpanStatus.Ok); - }; - - return asyncOp; - } - } -#endif } diff --git a/src/Sentry.Unity/Integrations/SceneManagerTracingIntegration.cs b/src/Sentry.Unity/Integrations/SceneManagerTracingIntegration.cs new file mode 100644 index 000000000..e01a0d390 --- /dev/null +++ b/src/Sentry.Unity/Integrations/SceneManagerTracingIntegration.cs @@ -0,0 +1,63 @@ +using Sentry.Extensibility; +using Sentry.Integrations; +using UnityEngine.SceneManagement; + +namespace Sentry.Unity; + +internal class SceneManagerTracingIntegration : ISdkIntegration +{ + public void Register(IHub hub, SentryOptions options) + { + if (options.TracesSampleRate > 0.0f) + { + if (SceneManagerAPI.overrideAPI != null) + { + // TODO: Add a place to put a custom 'SceneManagerAPI' on the editor window so we can "decorate" it. + options.LogWarning("Registering {0} integration - overwriting the previous SceneManagerAPI.overrideAPI.", nameof(SceneManagerTracingIntegration)); + } + + SceneManagerAPI.overrideAPI = new SceneManagerTracingAPI(options.DiagnosticLogger); + } + else + { + options.LogDebug("Sample Rate set to {0}. Skipping registering {1}.", options.TracesSampleRate, nameof(SceneManagerTracingIntegration)); + } + } +} + +public class SceneManagerTracingAPI : SceneManagerAPI +{ + public const string TransactionOperation = "scene.load"; + private const string SpanOperation = "scene.load"; + private readonly IDiagnosticLogger? _logger; + + public SceneManagerTracingAPI(IDiagnosticLogger? logger) => + _logger = logger; + + protected override UnityEngine.AsyncOperation LoadSceneAsyncByNameOrIndex(string sceneName, int sceneBuildIndex, LoadSceneParameters parameters, bool mustCompleteNextFrame) + { + _logger?.LogInfo("Creating '{0}' transaction for '{1}'.", TransactionOperation, sceneName); + + var transaction = SentrySdk.StartTransaction("scene.loading", TransactionOperation); + SentrySdk.ConfigureScope(scope => scope.Transaction = transaction); + + _logger?.LogDebug("Creating '{0}' span.", SpanOperation); + var span = SentrySdk.GetSpan()?.StartChild(SpanOperation, sceneName ?? $"buildIndex:{sceneBuildIndex}"); + + var asyncOp = base.LoadSceneAsyncByNameOrIndex(sceneName, sceneBuildIndex, parameters, mustCompleteNextFrame); + + // TODO: setExtra()? e.g. from the LoadSceneParameters: + // https://github.com/Unity-Technologies/UnityCsReference/blob/02d565cf3dd0f6b15069ba976064c75dc2705b08/Runtime/Export/SceneManager/SceneManager.cs#L30 + // Note: asyncOp.completed triggers in the next frame after finishing (so the time isn't precise). + // https://docs.unity3d.com/2020.3/Documentation/ScriptReference/AsyncOperation-completed.html + asyncOp.completed += _ => + { + _logger?.LogInfo("Finishing '{0}' transaction for '{1}'.", TransactionOperation, sceneName); + + span?.Finish(SpanStatus.Ok); + transaction.Finish(SpanStatus.Ok); + }; + + return asyncOp; + } +} diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 9ca204cd9..548ea1a2b 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -319,6 +319,7 @@ internal SentryUnityOptions(SentryMonoBehaviour behaviour, IApplication applicat this.AddIntegration(new UnityScopeIntegration(application, unityInfo)); this.AddIntegration(new UnityBeforeSceneLoadIntegration()); this.AddIntegration(new SceneManagerIntegration()); + this.AddIntegration(new SceneManagerTracingIntegration()); this.AddIntegration(new SessionIntegration(behaviour)); this.AddIntegration(new TraceGenerationIntegration(behaviour)); diff --git a/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs b/test/Sentry.Unity.Tests/SceneManagerTracingIntegrationTests.cs similarity index 66% rename from package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs rename to test/Sentry.Unity.Tests/SceneManagerTracingIntegrationTests.cs index 541da7092..fbf01b0ce 100644 --- a/package-dev/Tests/Runtime/SentrySceneTracingIntegrationTests.cs +++ b/test/Sentry.Unity.Tests/SceneManagerTracingIntegrationTests.cs @@ -1,12 +1,8 @@ -#if UNITY_2020_3_OR_NEWER -#define SENTRY_SCENE_MANAGER_TRACING_INTEGRATION -#endif - using System; using System.Collections; using NUnit.Framework; using Sentry.Unity.Tests; -using UnityEngine; +using Sentry.Unity.Tests.Stubs; using UnityEngine.SceneManagement; using UnityEngine.TestTools; @@ -14,8 +10,7 @@ namespace Sentry.Unity { public class SentrySceneTracingIntegrationTests { -#if SENTRY_SCENE_MANAGER_TRACING_INTEGRATION - private SentryUnityOptions _options; + private SentryUnityOptions _options = null!; // Set in Setup private TestHttpClientHandler _testHttpClientHandler = null!; // Set in Setup private readonly TimeSpan _eventReceiveTimeout = TimeSpan.FromSeconds(1); @@ -31,19 +26,37 @@ public void SetUp() }; } + [TearDown] + public void TearDown() => SceneManagerAPI.overrideAPI = null; + [UnityTest] public IEnumerator SceneManagerTracingIntegration_DuringSceneLoad_CreatesTransaction() { - SentryIntegrations.Configure(_options); - using var _ = SentryIntegrationsTests.InitSentrySdk(_options); + // Arrange + SentryUnitySdk.Init(_options); + // Act yield return SetupSceneCoroutine("1_Bugfarm"); + // Assert var triggeredEvent = _testHttpClientHandler.GetEvent("\"type\":\"transaction\"", _eventReceiveTimeout); - Assert.That(triggeredEvent, Does.Contain(SceneManagerTracingAPI.TransactionOperation)); } + [Test] + public void SceneManagerTracingIntegration_SampleRateSetToZero_SkipsAddingIntegration() + { + // Arrange + var sceneManagerTracingIntegration = new SceneManagerTracingIntegration(); + _options.TracesSampleRate = 0.0f; + + // Act + sceneManagerTracingIntegration.Register(new TestHub(), _options); + + // Assert + Assert.IsNull(SceneManagerAPI.overrideAPI); + } + internal static IEnumerator SetupSceneCoroutine(string sceneName) { LogAssert.ignoreFailingMessages = true; @@ -52,6 +65,5 @@ internal static IEnumerator SetupSceneCoroutine(string sceneName) // skip a frame for a Unity to properly load a scene yield return null; } -#endif } } From a66a5a670abe0ec6e3048a269370cedd2009dca2 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 14:26:18 +0200 Subject: [PATCH 14/42] updated CI to 2020 --- .github/workflows/build.yml | 2 +- .github/workflows/ci.yml | 30 +++++++------------ .../workflows/smoke-test-build-android.yml | 8 ----- .github/workflows/smoke-test-build-ios.yml | 8 ----- .github/workflows/update-deps.yml | 4 +-- Directory.Build.props | 2 +- 6 files changed, 15 insertions(+), 39 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6de20b3e5..da60a46fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: type: string env: - LOWEST_SUPPORTED_UNITY_VERSION: 2019 + LOWEST_SUPPORTED_UNITY_VERSION: 2020 DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: 1 GITHUB_ACTOR: ${{ github.actor }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a495e083d..56117b6ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: workflow_dispatch: # e.g. to manually trigger on foreign PRs env: - LOWEST_SUPPORTED_UNITY_VERSION: 2019 + LOWEST_SUPPORTED_UNITY_VERSION: 2020 DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: 1 @@ -54,7 +54,7 @@ jobs: fail-fast: false matrix: # Building the SDK with Unity 2022 and newer requires ns2.1 - skipping for now - unity-version: ["2019", "2020", "2021"] + unity-version: ["2020", "2021"] uses: ./.github/workflows/build.yml with: unity-version: ${{ matrix.unity-version }} @@ -87,7 +87,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] uses: ./.github/workflows/smoke-test-create.yml with: unity-version: ${{ matrix.unity-version }} @@ -101,7 +101,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] platform: ["WebGL", "Linux"] include: - platform: WebGL @@ -140,14 +140,6 @@ jobs: run: ./scripts/ci-docker.sh '${{ matrix.unity-version }}' '${{ matrix.platform }}${{ matrix.image-suffix }}' '${{ secrets.UNITY_LICENSE_SERVER_CONFIG }}' shell: bash - # Workaround for missing libMonoPosixHelper.so - # See https://github.com/getsentry/sentry-unity/pull/1295 - - name: Install mono-devel - if: ${{ matrix.unity-version == '2019' }} - run: | - docker exec --user root unity apt-get update - docker exec --user root unity apt-get -y -q install mono-devel - - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -213,7 +205,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] uses: ./.github/workflows/smoke-test-build-android.yml with: unity-version: ${{ matrix.unity-version }} @@ -232,7 +224,7 @@ jobs: matrix: api-level: [30, 31, 34] # last updated January 2025 init-type: ["runtime", "buildtime"] - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] smoke-test-build-ios: name: Build iOS ${{ matrix.unity-version }} Smoke Test @@ -242,7 +234,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] uses: ./.github/workflows/smoke-test-build-ios.yml with: unity-version: ${{ matrix.unity-version }} @@ -255,7 +247,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] init-type: ["runtime", "buildtime"] uses: ./.github/workflows/smoke-test-compile-ios.yml with: @@ -274,7 +266,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] # Check https://support.apple.com/en-us/HT201222 for the latest minor version for a given major one. # https://developer.apple.com/support/app-store/ shows that of all iOS devices # - `iOS 17`: 86 % @@ -295,7 +287,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] platform: ["WebGL", "Linux"] steps: - name: Checkout @@ -333,7 +325,7 @@ jobs: strategy: fail-fast: false matrix: - unity-version: ["2019", "2022", "6000"] + unity-version: ["2020", "2022", "6000"] # os: ["windows", "macos"] os: ["windows"] include: diff --git a/.github/workflows/smoke-test-build-android.yml b/.github/workflows/smoke-test-build-android.yml index 8a817eaf3..84db570b6 100644 --- a/.github/workflows/smoke-test-build-android.yml +++ b/.github/workflows/smoke-test-build-android.yml @@ -37,14 +37,6 @@ jobs: run: ./scripts/ci-docker.sh "${UNITY_VERSION}" 'android' '${{ secrets.UNITY_LICENSE_SERVER_CONFIG }}' shell: bash - # Workaround for missing libMonoPosixHelper.so - # See https://github.com/getsentry/sentry-unity/pull/1295 - - name: Install mono-devel - if: ${{ inputs.unity-version == '2019' }} - run: | - docker exec --user root unity apt-get update - docker exec --user root unity apt-get -y -q install mono-devel - - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: diff --git a/.github/workflows/smoke-test-build-ios.yml b/.github/workflows/smoke-test-build-ios.yml index 3d7ee8c12..a8ea21efb 100644 --- a/.github/workflows/smoke-test-build-ios.yml +++ b/.github/workflows/smoke-test-build-ios.yml @@ -43,14 +43,6 @@ jobs: run: ./scripts/ci-docker.sh "${UNITY_VERSION}" 'iOS' '${{ secrets.UNITY_LICENSE_SERVER_CONFIG }}' shell: bash - # Workaround for missing libMonoPosixHelper.so - # See https://github.com/getsentry/sentry-unity/pull/1295 - - name: Install mono-devel - if: ${{ inputs.unity-version == '2019' }} - run: | - docker exec --user root unity apt-get update - docker exec --user root unity apt-get -y -q install mono-devel - - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: diff --git a/.github/workflows/update-deps.yml b/.github/workflows/update-deps.yml index 1423b8e66..47e42798c 100644 --- a/.github/workflows/update-deps.yml +++ b/.github/workflows/update-deps.yml @@ -44,7 +44,7 @@ jobs: strategy: fail-fast: false matrix: - unity-prefix: ['2019', '2020', '2021', '2022', '6000'] + unity-prefix: ['2020', '2021', '2022', '6000'] steps: - name: Find the latest Unity version id: version-select @@ -61,7 +61,7 @@ jobs: ssh-key: ${{ secrets.CI_DEPLOY_KEY }} - name: Update sample ProjectVersion.txt - if: ${{ matrix.unity-prefix == '2019' }} + if: ${{ matrix.unity-prefix == '2020' }} run: | "m_EditorVersion: ${{ steps.version-select.outputs.version }}`nm_EditorVersionWithRevision: ${{ steps.version-select.outputs.version }} (${{ steps.version-select.outputs.changeset }})" ` | Out-File "samples\unity-of-bugs\ProjectSettings\ProjectVersion.txt" diff --git a/Directory.Build.props b/Directory.Build.props index 21b3dbb64..d3cab9008 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ strict false - + netstandard2.0 From 857e49244883d1c388e677c29faaa471fe33c306 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 14:27:10 +0200 Subject: [PATCH 15/42] env bump --- scripts/ci-env.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/ci-env.ps1 b/scripts/ci-env.ps1 index 2a42b2640..448f9c53b 100644 --- a/scripts/ci-env.ps1 +++ b/scripts/ci-env.ps1 @@ -4,9 +4,6 @@ param ( ) switch ($name) { - "unity2019" { - return "2019.4.40f1" - } "unity2020" { return "2020.3.48f1" } From eefad7005f0a1d8b84eccc387d5dc479f6c7cded Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 14:28:29 +0200 Subject: [PATCH 16/42] removed now redundant test --- .../Tests/Runtime/SentryIntegrationsTests.cs | 65 ------------------- src/Sentry.Unity/UnityWebRequestTransport.cs | 7 +- .../Editor/Builder.cs | 5 -- 3 files changed, 1 insertion(+), 76 deletions(-) delete mode 100644 package-dev/Tests/Runtime/SentryIntegrationsTests.cs diff --git a/package-dev/Tests/Runtime/SentryIntegrationsTests.cs b/package-dev/Tests/Runtime/SentryIntegrationsTests.cs deleted file mode 100644 index 38bcfd66f..000000000 --- a/package-dev/Tests/Runtime/SentryIntegrationsTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -#if UNITY_2020_3_OR_NEWER -#define SENTRY_SCENE_MANAGER_TRACING_INTEGRATION -#endif - -using System; -using System.Collections; -using NUnit.Framework; -using Sentry.Unity.Tests; -using UnityEngine.SceneManagement; -using UnityEngine.TestTools; - -namespace Sentry.Unity -{ - public class SentryIntegrationsTests - { -#if SENTRY_SCENE_MANAGER_TRACING_INTEGRATION - [UnityTest] - public IEnumerator Configure_TranceSampleRateOne_AddsSceneManagerTracingIntegration() - { - var options = new SentryUnityOptions - { - Dsn = "http://publickey@localhost:8000/12345", - TracesSampleRate = 1.0f - }; - - SentryIntegrations.Configure(options); - using var _ = InitSentrySdk(options); - - yield return null; - - Assert.IsNotNull(SceneManagerAPI.overrideAPI); - Assert.AreEqual(typeof(SceneManagerTracingAPI), SceneManagerAPI.overrideAPI.GetType()); - } - - // TODO: To be fixed: Currently fails if run after the integration has successfully been added. (because it doesn't get removed) - // [UnityTest] - // public IEnumerator Configure_TranceSampleRateZero_DoesNotAddSceneManagerTracingIntegration() - // { - // var options = new SentryUnityOptions - // { - // Dsn = "https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417", - // TracesSampleRate = 0f - // }; - // - // SentryIntegrations.Configure(options); - // using var _ = InitSentrySdk(options); - // - // yield return null; - // - // Assert.IsNull(SceneManagerAPI.overrideAPI); - // } - - public static IDisposable InitSentrySdk(SentryUnityOptions options) - { - SentryUnity.Init(options); - return new SentryDisposable(); - } - - private sealed class SentryDisposable : IDisposable - { - public void Dispose() => SentrySdk.Close(); - } -#endif - } -} diff --git a/src/Sentry.Unity/UnityWebRequestTransport.cs b/src/Sentry.Unity/UnityWebRequestTransport.cs index a72dad527..7db7ad2c5 100644 --- a/src/Sentry.Unity/UnityWebRequestTransport.cs +++ b/src/Sentry.Unity/UnityWebRequestTransport.cs @@ -89,12 +89,7 @@ private UnityWebRequest CreateWebRequest(HttpRequestMessage message) private HttpResponseMessage? GetResponse(UnityWebRequest www) { - // Let's disable treating "warning:obsolete" as an error here because the alternative of putting a static - // function to user code (to be able to use #if UNITY_2019) is just ugly. -#pragma warning disable 618 - // if (www.result == UnityWebRequest.Result.ConnectionError) // Unity 2020.1+; `.result` not present on 2019 - if (www.isNetworkError) // Unity 2019; obsolete (error) on later versions -#pragma warning restore 618 + if (www.result == UnityWebRequest.Result.ConnectionError) { _options.DiagnosticLogger?.LogWarning("Failed to send request: {0}", www.error); return null; diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs index 025fc0c9c..86d64d0b8 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -86,11 +86,6 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, Debug.Log($"Builder: Creating output directory at '{outputDir}'"); Directory.CreateDirectory(outputDir); -#if !UNITY_2020_1_OR_NEWER - Debug.Log("Builder: Raising the minSdkVersion to 21"); - PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel21; -#endif - Debug.Log("Builder: Enabling minify"); #if UNITY_2020_1_OR_NEWER PlayerSettings.Android.minifyDebug = PlayerSettings.Android.minifyRelease = true; From 16c9fcf1c29748087534327dcf4a2bf7d92ad5f2 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 14:44:32 +0200 Subject: [PATCH 17/42] bumped uniy-of-bugs to 2020 --- .../Editor/SentryUserFeedbackEditor.cs.meta | 11 +++++- samples/unity-of-bugs/Assets/Editor.meta | 8 +++++ samples/unity-of-bugs/Packages/manifest.json | 11 +++--- .../unity-of-bugs/Packages/packages-lock.json | 35 ++++++++++++------- .../PackageManagerSettings.asset | 9 ++++- .../ProjectSettings/ProjectVersion.txt | 4 +-- .../VersionControlSettings.asset | 8 +++++ 7 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 samples/unity-of-bugs/Assets/Editor.meta create mode 100644 samples/unity-of-bugs/ProjectSettings/VersionControlSettings.asset diff --git a/package-dev/Editor/SentryUserFeedbackEditor.cs.meta b/package-dev/Editor/SentryUserFeedbackEditor.cs.meta index 5039054b7..51e8fcf6e 100644 --- a/package-dev/Editor/SentryUserFeedbackEditor.cs.meta +++ b/package-dev/Editor/SentryUserFeedbackEditor.cs.meta @@ -1,2 +1,11 @@ fileFormatVersion: 2 -guid: 788f34b6497dc4ab881dffd0e4932cd9 \ No newline at end of file +guid: 788f34b6497dc4ab881dffd0e4932cd9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/unity-of-bugs/Assets/Editor.meta b/samples/unity-of-bugs/Assets/Editor.meta new file mode 100644 index 000000000..68c2abf49 --- /dev/null +++ b/samples/unity-of-bugs/Assets/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f020a34d28f004fcd9ef49d9556b8c41 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/unity-of-bugs/Packages/manifest.json b/samples/unity-of-bugs/Packages/manifest.json index 0c707dc20..5ba22e101 100644 --- a/samples/unity-of-bugs/Packages/manifest.json +++ b/samples/unity-of-bugs/Packages/manifest.json @@ -1,10 +1,11 @@ { "dependencies": { - "com.unity.ide.rider": "1.2.1", - "com.unity.mobile.android-logcat": "1.2.3", - "com.unity.test-framework": "1.1.31", - "com.unity.toolchain.macos-x86_64-linux-x86_64": "2.0.0", - "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.1", + "com.unity.ide.rider": "3.0.36", + "com.unity.ide.visualstudio": "2.0.23", + "com.unity.mobile.android-logcat": "1.3.2", + "com.unity.test-framework": "1.1.33", + "com.unity.toolchain.macos-x86_64-linux-x86_64": "2.0.10", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", "com.unity.ugui": "1.0.0", "io.sentry.unity.dev": "file:../../../package-dev", "com.unity.modules.androidjni": "1.0.0", diff --git a/samples/unity-of-bugs/Packages/packages-lock.json b/samples/unity-of-bugs/Packages/packages-lock.json index 077a76a09..81fa778ef 100644 --- a/samples/unity-of-bugs/Packages/packages-lock.json +++ b/samples/unity-of-bugs/Packages/packages-lock.json @@ -8,39 +8,48 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "1.2.1", + "version": "3.0.36", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.1" + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.23", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" }, "url": "https://packages.unity.com" }, "com.unity.mobile.android-logcat": { - "version": "1.2.3", + "version": "1.3.2", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.sysroot": { - "version": "2.0.2", + "version": "2.0.10", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.sysroot.linux-x86_64": { - "version": "2.0.1", + "version": "2.0.9", "depth": 1, "source": "registry", "dependencies": { - "com.unity.sysroot": "2.0.2" + "com.unity.sysroot": "2.0.10" }, "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.31", + "version": "1.1.33", "depth": 0, "source": "registry", "dependencies": { @@ -51,22 +60,22 @@ "url": "https://packages.unity.com" }, "com.unity.toolchain.macos-x86_64-linux-x86_64": { - "version": "2.0.0", + "version": "2.0.10", "depth": 0, "source": "registry", "dependencies": { - "com.unity.sysroot": "2.0.0", - "com.unity.sysroot.linux-x86_64": "2.0.0" + "com.unity.sysroot": "2.0.10", + "com.unity.sysroot.linux-x86_64": "2.0.9" }, "url": "https://packages.unity.com" }, "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.1", + "version": "2.0.10", "depth": 0, "source": "registry", "dependencies": { - "com.unity.sysroot": "2.0.2", - "com.unity.sysroot.linux-x86_64": "2.0.1" + "com.unity.sysroot": "2.0.10", + "com.unity.sysroot.linux-x86_64": "2.0.9" }, "url": "https://packages.unity.com" }, diff --git a/samples/unity-of-bugs/ProjectSettings/PackageManagerSettings.asset b/samples/unity-of-bugs/ProjectSettings/PackageManagerSettings.asset index ca9e773f0..6457bee88 100644 --- a/samples/unity-of-bugs/ProjectSettings/PackageManagerSettings.asset +++ b/samples/unity-of-bugs/ProjectSettings/PackageManagerSettings.asset @@ -9,9 +9,12 @@ MonoBehaviour: m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 13960, guid: 0000000000000000e000000000000000, type: 0} + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: + m_EnablePreviewPackages: 0 + m_EnablePackageDependencies: 0 + m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 oneTimeWarningShown: 0 m_Registries: @@ -20,6 +23,8 @@ MonoBehaviour: m_Url: https://packages.unity.com m_Scopes: [] m_IsDefault: 1 + m_Capabilities: 7 + m_ConfigSource: 0 m_UserSelectedRegistryName: m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: @@ -30,6 +35,8 @@ MonoBehaviour: m_Url: m_Scopes: [] m_IsDefault: 0 + m_Capabilities: 0 + m_ConfigSource: 0 m_Modified: 0 m_Name: m_Url: diff --git a/samples/unity-of-bugs/ProjectSettings/ProjectVersion.txt b/samples/unity-of-bugs/ProjectSettings/ProjectVersion.txt index 4c191298a..da76706d2 100644 --- a/samples/unity-of-bugs/ProjectSettings/ProjectVersion.txt +++ b/samples/unity-of-bugs/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2019.4.40f1 -m_EditorVersionWithRevision: 2019.4.40f1 (ffc62b691db5) +m_EditorVersion: 2020.3.48f1 +m_EditorVersionWithRevision: 2020.3.48f1 (b805b124c6b7) diff --git a/samples/unity-of-bugs/ProjectSettings/VersionControlSettings.asset b/samples/unity-of-bugs/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 000000000..dca288142 --- /dev/null +++ b/samples/unity-of-bugs/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 From 6542706a2514b7cfd3fd11970736e56b3824979f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 15:23:40 +0200 Subject: [PATCH 18/42] updated the package --- samples/unity-of-bugs/Packages/manifest.json | 32 ++- .../unity-of-bugs/Packages/packages-lock.json | 245 +++++++++++++++++- 2 files changed, 273 insertions(+), 4 deletions(-) diff --git a/samples/unity-of-bugs/Packages/manifest.json b/samples/unity-of-bugs/Packages/manifest.json index 5ba22e101..86a16e5e6 100644 --- a/samples/unity-of-bugs/Packages/manifest.json +++ b/samples/unity-of-bugs/Packages/manifest.json @@ -1,17 +1,47 @@ { "dependencies": { + "com.unity.collab-proxy": "2.0.7", "com.unity.ide.rider": "3.0.36", "com.unity.ide.visualstudio": "2.0.23", + "com.unity.ide.vscode": "1.2.5", "com.unity.mobile.android-logcat": "1.3.2", "com.unity.test-framework": "1.1.33", + "com.unity.textmeshpro": "3.0.9", + "com.unity.timeline": "1.4.8", "com.unity.toolchain.macos-x86_64-linux-x86_64": "2.0.10", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", "com.unity.ugui": "1.0.0", "io.sentry.unity.dev": "file:../../../package-dev", + "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", "com.unity.modules.ui": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" } } diff --git a/samples/unity-of-bugs/Packages/packages-lock.json b/samples/unity-of-bugs/Packages/packages-lock.json index 81fa778ef..d807f43db 100644 --- a/samples/unity-of-bugs/Packages/packages-lock.json +++ b/samples/unity-of-bugs/Packages/packages-lock.json @@ -1,5 +1,12 @@ { "dependencies": { + "com.unity.collab-proxy": { + "version": "2.0.7", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.ext.nunit": { "version": "1.0.6", "depth": 1, @@ -25,6 +32,13 @@ }, "url": "https://packages.unity.com" }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.mobile.android-logcat": { "version": "1.3.2", "depth": 0, @@ -59,6 +73,27 @@ }, "url": "https://packages.unity.com" }, + "com.unity.textmeshpro": { + "version": "3.0.9", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.4.8", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, "com.unity.toolchain.macos-x86_64-linux-x86_64": { "version": "2.0.10", "depth": 0, @@ -94,33 +129,86 @@ "source": "local", "dependencies": {} }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.androidjni": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.audio": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, "com.unity.modules.imageconversion": { "version": "1.0.0", - "depth": 1, + "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.imgui": { "version": "1.0.0", - "depth": 1, + "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.jsonserialize": { "version": "1.0.0", - "depth": 1, + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, "source": "builtin", "dependencies": {} }, @@ -132,17 +220,168 @@ "com.unity.modules.imageconversion": "1.0.0" } }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, "com.unity.modules.ui": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.uielementsnative": "1.0.0" + } + }, + "com.unity.modules.uielementsnative": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, "com.unity.modules.unitywebrequest": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } } } } From b48e60d73866b50d79521ed606bfea2f6c2e3ff9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 15:25:12 +0200 Subject: [PATCH 19/42] removed now redundant asmdef --- .../io.sentry.unity.dev.runtimetests.asmdef | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 package-dev/Tests/Runtime/io.sentry.unity.dev.runtimetests.asmdef diff --git a/package-dev/Tests/Runtime/io.sentry.unity.dev.runtimetests.asmdef b/package-dev/Tests/Runtime/io.sentry.unity.dev.runtimetests.asmdef deleted file mode 100644 index c56e5a077..000000000 --- a/package-dev/Tests/Runtime/io.sentry.unity.dev.runtimetests.asmdef +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "io.sentry.unity.dev.runtimetests", - "rootNamespace": "", - "references": [ - "UnityEngine.TestRunner", - "UnityEditor.TestRunner", - "io.sentry.unity.dev.runtime" - ], - "includePlatforms": [], - "excludePlatforms": [ - "Android", - "GameCoreScarlett", - "GameCoreXboxOne", - "iOS", - "LinuxStandalone64", - "CloudRendering", - "Lumin", - "macOSStandalone", - "PS4", - "PS5", - "Stadia", - "Switch", - "tvOS", - "WSA", - "WebGL", - "WindowsStandalone32", - "WindowsStandalone64", - "XboxOne" - ], - "allowUnsafeCode": false, - "overrideReferences": true, - "precompiledReferences": [ - "Sentry.dll", - "Sentry.Unity.dll", - "Sentry.Unity.Tests.dll", - "nunit.framework.dll" - ], - "autoReferenced": false, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} \ No newline at end of file From 76e05b27c2905e2613d130df3b2a851bd14de89d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 16:55:19 +0200 Subject: [PATCH 20/42] Updated CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9e5f07a..14472cb2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Updated Unity support by updating to Unity versions (2020+), removing Unity 2019 which reached End of Life in 2022 ([#2231](https://github.com/getsentry/sentry-unity/pull/2231)) + ### Features - The SDK now comes with a `SentryUserFeedback` prefab ready to be used. You can drag and drop it into your scene or From 3fa372c7773cc1b56bbe3615edf9705aba4f0f3d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 17:05:41 +0200 Subject: [PATCH 21/42] missing option field? --- .../unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset index e41049353..b45b58bff 100644 --- a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset +++ b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset @@ -40,6 +40,7 @@ MonoBehaviour: k__BackingField: 1 k__BackingField: 1 k__BackingField: 1 + k__BackingField: 1 k__BackingField: 100 k__BackingField: 1 k__BackingField: 0 From 83deeec8aaaf2979b95493128d74c32b313a854d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 8 Jul 2025 17:43:36 +0200 Subject: [PATCH 22/42] finished fixing the startup tracing --- package-dev/Runtime/SentryInitialization.cs | 37 ++---------- package-dev/Runtime/SentryIntegrations.cs | 63 ++++++++++++++++----- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index f8d1b96d4..e47a1ecab 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -43,12 +43,6 @@ namespace Sentry.Unity { internal static class SentryInitialization { - public const string StartupTransactionOperation = "app.start"; - public static ISpan InitSpan; - private const string InitSpanOperation = "runtime.init"; - public static ISpan SubSystemRegistrationSpan; - private const string SubSystemSpanOperation = "runtime.init.subsystem"; - #if SENTRY_WEBGL // On WebGL SubsystemRegistration is too early for the UnityWebRequestTransport and errors with 'URI empty' [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] @@ -65,14 +59,13 @@ internal static void Init() var options = ScriptableSentryUnityOptions.LoadSentryUnityOptions(); if (options != null && options.ShouldInitializeSdk()) { - // Certain integrations require access to preprocessor directives so we provide them as `.cs` and - // compile them with the game instead of pre-compiling them with the rest of the SDK. - // i.e. SceneManagerAPI requires UNITY_2020_3_OR_NEWER - // TODO: Find a way to move this into `SentrySdk.Init` too - SentryIntegrations.Configure(options); + // We have to keep the StartupTracing outside the SDK as the integration relies on the `RuntimeInitializeOnLoadMethod` + // attribute. + SentryStartupTracing.SetUpTracingIntration(options); SentrySdk.Init(options); - SetupStartupTracing(options); + + SentryStartupTracing.StartTracing(); } else { @@ -97,26 +90,6 @@ private static void SetupPlatformConfiguration() SentryPlatformServices.PlatformConfiguration = SentryNative.Configure; #elif SENTRY_WEBGL SentryPlatformServices.PlatformConfiguration = SentryWebGL.Configure; -#endif - } - - private static void SetupStartupTracing(SentryUnityOptions options) - { -#if !SENTRY_WEBGL - if (options.TracesSampleRate > 0.0f && options.AutoStartupTraces) - { - options.DiagnosticLogger?.LogInfo("Creating '{0}' transaction for runtime initialization.", - StartupTransactionOperation); - - var runtimeStartTransaction = - SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); - SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); - - options.DiagnosticLogger?.LogDebug("Creating '{0}' span.", InitSpanOperation); - InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); - options.DiagnosticLogger?.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); - SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); - } #endif } } diff --git a/package-dev/Runtime/SentryIntegrations.cs b/package-dev/Runtime/SentryIntegrations.cs index 15ed934e1..0a03d9cc2 100644 --- a/package-dev/Runtime/SentryIntegrations.cs +++ b/package-dev/Runtime/SentryIntegrations.cs @@ -6,15 +6,15 @@ using Sentry.Extensibility; using Sentry.Integrations; -using Sentry.Unity; using UnityEngine; -using UnityEngine.SceneManagement; namespace Sentry.Unity { - public static class SentryIntegrations + internal static class SentryStartupTracing { - public static void Configure(SentryUnityOptions options) + private static StartupTracingIntegration StartupTracingIntegration; + + public static void SetUpTracingIntration(SentryUnityOptions options) { if (options.TracesSampleRate > 0.0) { @@ -22,16 +22,31 @@ public static void Configure(SentryUnityOptions options) #if !SENTRY_WEBGL if (options.AutoStartupTraces) { - options.AddIntegration(new StartupTracingIntegration()); + StartupTracingIntegration = new StartupTracingIntegration(); + options.AddIntegration(StartupTracingIntegration); } #endif } } + + public static void StartTracing() + { + if (StartupTracingIntegration != null) + { + StartupTracingIntegration.StartTracing(); + } + } + } #if !SENTRY_WEBGL - public class StartupTracingIntegration : ISdkIntegration + internal class StartupTracingIntegration : ISdkIntegration { + private const string StartupTransactionOperation = "app.start"; + private static ISpan InitSpan; + private const string InitSpanOperation = "runtime.init"; + public static ISpan SubSystemRegistrationSpan; + private const string SubSystemSpanOperation = "runtime.init.subsystem"; private static ISpan AfterAssembliesSpan; private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; private static ISpan SplashScreenSpan; @@ -51,6 +66,26 @@ public void Register(IHub hub, SentryOptions options) IntegrationRegistered = true; } + public static void StartTracing() + { + if (!IntegrationRegistered || StartupAlreadyCaptured) + { + return; + } + + Logger.LogInfo("Creating '{0}' transaction for runtime initialization.", + StartupTransactionOperation); + + var runtimeStartTransaction = + SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); + SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); + + Logger.LogDebug("Creating '{0}' span.", InitSpanOperation); + InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); + Logger.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); + SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); + } + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] public static void AfterAssembliesLoaded() { @@ -59,11 +94,11 @@ public static void AfterAssembliesLoaded() return; } - SentryInitialization.SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); - SentryInitialization.SubSystemRegistrationSpan = null; + SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); + SubSystemRegistrationSpan = null; Logger?.LogDebug("Creating '{0}' span.", AfterAssembliesSpanOperation); - AfterAssembliesSpan = SentryInitialization.InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); + AfterAssembliesSpan = InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] @@ -78,7 +113,7 @@ public static void BeforeSplashScreen() AfterAssembliesSpan = null; Logger?.LogDebug("Creating '{0}' span.", SplashScreenSpanOperation); - SplashScreenSpan = SentryInitialization.InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); + SplashScreenSpan = InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] @@ -93,7 +128,7 @@ public static void BeforeSceneLoad() SplashScreenSpan = null; Logger?.LogDebug("Creating '{0}' span.", FirstSceneLoadSpanOperation); - FirstSceneLoadSpan = SentryInitialization.InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); + FirstSceneLoadSpan = InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] @@ -107,10 +142,10 @@ public static void AfterSceneLoad() FirstSceneLoadSpan?.Finish(SpanStatus.Ok); FirstSceneLoadSpan = null; - SentryInitialization.InitSpan?.Finish(SpanStatus.Ok); - SentryInitialization.InitSpan = null; + InitSpan?.Finish(SpanStatus.Ok); + InitSpan = null; - Logger?.LogInfo("Finishing '{0}' transaction.", SentryInitialization.StartupTransactionOperation); + Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); SentrySdk.ConfigureScope(s => { s.Transaction?.Finish(SpanStatus.Ok); From 2a46b5fa6462f055dc94c2c8d4b07b420499f6a8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 13:15:24 +0200 Subject: [PATCH 23/42] bump android API level to 21 for 2020 --- test/Scripts.Integration.Test/Editor/Builder.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs index 86d64d0b8..e2955b439 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -86,6 +86,12 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, Debug.Log($"Builder: Creating output directory at '{outputDir}'"); Directory.CreateDirectory(outputDir); +#if UNITY_2020_3 + // The default for 2020.3 is 19. There will be no further updates to 2020.3. + Debug.Log("Builder: Raising the minSdkVersion to 21"); + PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel21; +#endif + Debug.Log("Builder: Enabling minify"); #if UNITY_2020_1_OR_NEWER PlayerSettings.Android.minifyDebug = PlayerSettings.Android.minifyRelease = true; From 43f5ae9965ba95358bafffcde5cc9d437532c51c Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 13:49:08 +0200 Subject: [PATCH 24/42] updated test to ignore harmless warning --- test/Sentry.Unity.Tests/UnityEventScopeTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Sentry.Unity.Tests/UnityEventScopeTests.cs b/test/Sentry.Unity.Tests/UnityEventScopeTests.cs index ed9c7d795..2085e1682 100644 --- a/test/Sentry.Unity.Tests/UnityEventScopeTests.cs +++ b/test/Sentry.Unity.Tests/UnityEventScopeTests.cs @@ -82,7 +82,11 @@ public void SentrySdkCaptureEvent_OnNotUIThread_Succeeds() SentrySdk.FlushAsync(TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); // assert - var logsFound = _testLogger.Logs.Where(log => log.logLevel >= SentryLevel.Warning && log.message != "Cache directory is empty.").ToList(); + var logsFound = _testLogger.Logs.Where(log => + log.logLevel >= SentryLevel.Warning && + // Ignore expected or harmless warnings + log.message != "Cache directory is empty." && + log.message != "The SDK's Platform Services have not been set up. Native support will be limited.").ToList(); Assert.Zero(logsFound.Count, FormatLogs(logsFound)); From ad0ae02ee7d6fac22cbf94cf9d60efdafdf1fe12 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 17:07:21 +0200 Subject: [PATCH 25/42] move startup tracing into the SDK --- package-dev/Runtime/SentryInitialization.cs | 33 +---- package-dev/Runtime/SentryIntegrations.cs | 122 ---------------- .../Resources/Sentry/SentryOptions.asset | 2 +- .../Integrations/StartupTracingIntegration.cs | 134 ++++++++++++++++++ src/Sentry.Unity/SentryUnity.cs | 7 + src/Sentry.Unity/SentryUnityOptions.cs | 1 + src/Sentry.Unity/SentryUnitySDK.cs | 4 + 7 files changed, 148 insertions(+), 155 deletions(-) delete mode 100644 package-dev/Runtime/SentryIntegrations.cs create mode 100644 src/Sentry.Unity/Integrations/StartupTracingIntegration.cs diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 918cc2165..e98ce8318 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -41,12 +41,6 @@ namespace Sentry.Unity { public static class SentryInitialization { - public const string StartupTransactionOperation = "app.start"; - public static ISpan InitSpan; - private const string InitSpanOperation = "runtime.init"; - public static ISpan SubSystemRegistrationSpan; - private const string SubSystemSpanOperation = "runtime.init.subsystem"; - #if SENTRY_WEBGL // On WebGL SubsystemRegistration is too early for the UnityWebRequestTransport and errors with 'URI empty' [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] @@ -60,14 +54,9 @@ public static void Init() var options = ScriptableSentryUnityOptions.LoadSentryUnityOptions(unityInfo); if (options != null && options.ShouldInitializeSdk()) { - // Certain integrations require access to preprocessor directives so we provide them as `.cs` and - // compile them with the game instead of precompiling them with the rest of the SDK. - // i.e. SceneManagerAPI requires UNITY_2020_3_OR_NEWER - SentryIntegrations.Configure(options); // Configures scope sync and (by default) initializes the native SDK. SetupNativeSdk(options, unityInfo); SentryUnity.Init(options); - SetupStartupTracing(options); } else { @@ -92,7 +81,7 @@ private static void SetupNativeSdk(SentryUnityOptions options, SentryUnityInfo u #elif SENTRY_NATIVE SentryNative.Configure(options, unityInfo); #elif SENTRY_WEBGL - SentryWebGL.Configure(options); + SentryWebGL.Configure(options); #endif } catch (DllNotFoundException e) @@ -106,26 +95,6 @@ private static void SetupNativeSdk(SentryUnityOptions options, SentryUnityInfo u options.DiagnosticLogger?.LogError(e, "Sentry native error capture configuration failed."); } } - - private static void SetupStartupTracing(SentryUnityOptions options) - { -#if !SENTRY_WEBGL - if (options.TracesSampleRate > 0.0f && options.AutoStartupTraces) - { - options.DiagnosticLogger?.LogInfo("Creating '{0}' transaction for runtime initialization.", - StartupTransactionOperation); - - var runtimeStartTransaction = - SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); - SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); - - options.DiagnosticLogger?.LogDebug("Creating '{0}' span.", InitSpanOperation); - InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); - options.DiagnosticLogger?.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); - SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); - } -#endif - } } public class SentryUnityInfo : ISentryUnityInfo diff --git a/package-dev/Runtime/SentryIntegrations.cs b/package-dev/Runtime/SentryIntegrations.cs deleted file mode 100644 index 1c1e64c9b..000000000 --- a/package-dev/Runtime/SentryIntegrations.cs +++ /dev/null @@ -1,122 +0,0 @@ -#if !UNITY_EDITOR -#if UNITY_WEBGL -#define SENTRY_WEBGL -#endif -#endif - -using Sentry.Extensibility; -using Sentry.Integrations; -using UnityEngine; -using UnityEngine.SceneManagement; - -namespace Sentry.Unity -{ - public static class SentryIntegrations - { - public static void Configure(SentryUnityOptions options) - { - if (options.TracesSampleRate > 0.0) - { -// On WebGL the SDK initializes on BeforeScene so the Startup Tracing won't work properly. https://github.com/getsentry/sentry-unity/issues/1000 -#if !SENTRY_WEBGL - if (options.AutoStartupTraces) - { - options.AddIntegration(new StartupTracingIntegration()); - } -#endif - } - } - } - -#if !SENTRY_WEBGL - public class StartupTracingIntegration : ISdkIntegration - { - private static ISpan AfterAssembliesSpan; - private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; - private static ISpan SplashScreenSpan; - private const string SplashScreenSpanOperation = "runtime.init.splashscreen"; - private static ISpan FirstSceneLoadSpan; - private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; - - // Flag to make sure we create spans through the runtime initialization only once - private static bool StartupAlreadyCaptured; - private static bool IntegrationRegistered; - - private static IDiagnosticLogger Logger; - - public void Register(IHub hub, SentryOptions options) - { - Logger = options.DiagnosticLogger; - IntegrationRegistered = true; - } - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] - public static void AfterAssembliesLoaded() - { - if (!IntegrationRegistered || StartupAlreadyCaptured) - { - return; - } - - SentryInitialization.SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); - SentryInitialization.SubSystemRegistrationSpan = null; - - Logger?.LogDebug("Creating '{0}' span.", AfterAssembliesSpanOperation); - AfterAssembliesSpan = SentryInitialization.InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); - } - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] - public static void BeforeSplashScreen() - { - if (!IntegrationRegistered || StartupAlreadyCaptured) - { - return; - } - - AfterAssembliesSpan?.Finish(SpanStatus.Ok); - AfterAssembliesSpan = null; - - Logger?.LogDebug("Creating '{0}' span.", SplashScreenSpanOperation); - SplashScreenSpan = SentryInitialization.InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); - } - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - public static void BeforeSceneLoad() - { - if (!IntegrationRegistered || StartupAlreadyCaptured) - { - return; - } - - SplashScreenSpan?.Finish(SpanStatus.Ok); - SplashScreenSpan = null; - - Logger?.LogDebug("Creating '{0}' span.", FirstSceneLoadSpanOperation); - FirstSceneLoadSpan = SentryInitialization.InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); - } - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] - public static void AfterSceneLoad() - { - if (!IntegrationRegistered || StartupAlreadyCaptured) - { - return; - } - - FirstSceneLoadSpan?.Finish(SpanStatus.Ok); - FirstSceneLoadSpan = null; - - SentryInitialization.InitSpan?.Finish(SpanStatus.Ok); - SentryInitialization.InitSpan = null; - - Logger?.LogInfo("Finishing '{0}' transaction.", SentryInitialization.StartupTransactionOperation); - SentrySdk.ConfigureScope(s => - { - s.Transaction?.Finish(SpanStatus.Ok); - }); - - StartupAlreadyCaptured = true; - } - } -#endif -} diff --git a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset index b45b58bff..153017e4e 100644 --- a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset +++ b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset @@ -19,7 +19,7 @@ MonoBehaviour: k__BackingField: 1000 k__BackingField: 1000 k__BackingField: 1000 - k__BackingField: 0 + k__BackingField: 1 k__BackingField: 1 k__BackingField: 1 k__BackingField: 0 diff --git a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs new file mode 100644 index 000000000..83934e88e --- /dev/null +++ b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs @@ -0,0 +1,134 @@ +using Sentry.Extensibility; +using Sentry.Integrations; +using UnityEngine; + +namespace Sentry.Unity.Integrations; + +internal class StartupTracingIntegration : ISdkIntegration + { + private const string StartupTransactionOperation = "app.start"; + private static ISpan? InitSpan; + private const string InitSpanOperation = "runtime.init"; + private static ISpan? SubSystemRegistrationSpan; + private const string SubSystemSpanOperation = "runtime.init.subsystem"; + private static ISpan? AfterAssembliesSpan; + private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; + private static ISpan? SplashScreenSpan; + private const string SplashScreenSpanOperation = "runtime.init.splashscreen"; + private static ISpan? FirstSceneLoadSpan; + private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; + + // Flag to make sure we create spans through the runtime initialization only once + private static bool StartupAlreadyCaptured; + private static bool IntegrationRegistered; + + private static IDiagnosticLogger? Logger; + + public void Register(IHub hub, SentryOptions options) + { + Logger = options.DiagnosticLogger; + + if (options is SentryUnityOptions { TracesSampleRate: > 0, AutoStartupTraces: true }) + { + IntegrationRegistered = true; + } + } + + internal static bool IsStartupTracingAllowed() + { + if (Application.isEditor + || Application.platform == RuntimePlatform.WebGLPlayer + || !IntegrationRegistered + || StartupAlreadyCaptured) + { + return false; + } + + return true; + } + + public static void StartTracing() + { + if (!IsStartupTracingAllowed()) + { + return; + } + + Logger?.LogInfo("Creating '{0}' transaction for runtime initialization.", + StartupTransactionOperation); + + var runtimeStartTransaction = + SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); + SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); + + Logger?.LogDebug("Creating '{0}' span.", InitSpanOperation); + InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); + Logger?.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); + SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] + public static void AfterAssembliesLoaded() + { + if (!IsStartupTracingAllowed()) + { + return; + } + + SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); + SubSystemRegistrationSpan = null; + + Logger?.LogDebug("Creating '{0}' span.", AfterAssembliesSpanOperation); + AfterAssembliesSpan = InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + public static void BeforeSplashScreen() + { + if (!IsStartupTracingAllowed()) + { + return; + } + + AfterAssembliesSpan?.Finish(SpanStatus.Ok); + AfterAssembliesSpan = null; + + Logger?.LogDebug("Creating '{0}' span.", SplashScreenSpanOperation); + SplashScreenSpan = InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + public static void BeforeSceneLoad() + { + if (!IsStartupTracingAllowed()) + { + return; + } + + SplashScreenSpan?.Finish(SpanStatus.Ok); + SplashScreenSpan = null; + + Logger?.LogDebug("Creating '{0}' span.", FirstSceneLoadSpanOperation); + FirstSceneLoadSpan = InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + public static void AfterSceneLoad() + { + if (!IsStartupTracingAllowed()) + { + return; + } + + FirstSceneLoadSpan?.Finish(SpanStatus.Ok); + FirstSceneLoadSpan = null; + + InitSpan?.Finish(SpanStatus.Ok); + InitSpan = null; + + Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); + SentrySdk.ConfigureScope(s => s.Transaction?.Finish(SpanStatus.Ok)); + + StartupAlreadyCaptured = true; + } + } diff --git a/src/Sentry.Unity/SentryUnity.cs b/src/Sentry.Unity/SentryUnity.cs index 4306d8dce..75048bd28 100644 --- a/src/Sentry.Unity/SentryUnity.cs +++ b/src/Sentry.Unity/SentryUnity.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using Sentry.Extensibility; +using UnityEngine; namespace Sentry.Unity; @@ -11,6 +12,12 @@ public static class SentryUnity { private static SentryUnitySdk? UnitySdk; + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + private static void DoTheThing() + { + Debug.Log("What the actual fuck."); + } + /// /// Initializes Sentry Unity SDK while configuring the options. /// diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 548ea1a2b..ed1184db2 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -315,6 +315,7 @@ internal SentryUnityOptions(SentryMonoBehaviour behaviour, IApplication applicat this.AddIntegration(new UnityLogHandlerIntegration(this)); this.AddIntegration(new UnityApplicationLoggingIntegration()); + this.AddIntegration(new StartupTracingIntegration()); this.AddIntegration(new AnrIntegration(behaviour)); this.AddIntegration(new UnityScopeIntegration(application, unityInfo)); this.AddIntegration(new UnityBeforeSceneLoadIntegration()); diff --git a/src/Sentry.Unity/SentryUnitySDK.cs b/src/Sentry.Unity/SentryUnitySDK.cs index 4119c312f..2228ef8db 100644 --- a/src/Sentry.Unity/SentryUnitySDK.cs +++ b/src/Sentry.Unity/SentryUnitySDK.cs @@ -51,6 +51,10 @@ private SentryUnitySdk(SentryUnityOptions options) unitySdk._dotnetSdk = SentrySdk.Init(options); + // We can safely call this during initialization. If the SDK self-initialized we're right on time. If the SDK + // was initialized manually, the RuntimeOnLoad attributes already triggered, making this call a no-op. + StartupTracingIntegration.StartTracing(); + if (options.NativeContextWriter is { } contextWriter) { SentrySdk.ConfigureScope((scope) => From bb6f80a870ee58c8bcefb69232d26f8c3b6a6595 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 17:08:21 +0200 Subject: [PATCH 26/42] . --- src/Sentry.Unity/SentryUnity.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Sentry.Unity/SentryUnity.cs b/src/Sentry.Unity/SentryUnity.cs index 75048bd28..c3b69806e 100644 --- a/src/Sentry.Unity/SentryUnity.cs +++ b/src/Sentry.Unity/SentryUnity.cs @@ -12,12 +12,6 @@ public static class SentryUnity { private static SentryUnitySdk? UnitySdk; - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] - private static void DoTheThing() - { - Debug.Log("What the actual fuck."); - } - /// /// Initializes Sentry Unity SDK while configuring the options. /// From c53ccb5ddc1a459830caf5279a7e98a28ec62c04 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 17:19:37 +0200 Subject: [PATCH 27/42] fix naming --- .../Integrations/StartupTracingIntegration.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs index 83934e88e..4506b0049 100644 --- a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs +++ b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs @@ -18,9 +18,8 @@ internal class StartupTracingIntegration : ISdkIntegration private static ISpan? FirstSceneLoadSpan; private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; - // Flag to make sure we create spans through the runtime initialization only once - private static bool StartupAlreadyCaptured; - private static bool IntegrationRegistered; + private static bool GameStartupFinished; // Flag to make sure we only create spans during the game's startup. + private static bool IsIntegrationRegistered; private static IDiagnosticLogger? Logger; @@ -30,21 +29,21 @@ public void Register(IHub hub, SentryOptions options) if (options is SentryUnityOptions { TracesSampleRate: > 0, AutoStartupTraces: true }) { - IntegrationRegistered = true; + IsIntegrationRegistered = true; } } internal static bool IsStartupTracingAllowed() { - if (Application.isEditor - || Application.platform == RuntimePlatform.WebGLPlayer - || !IntegrationRegistered - || StartupAlreadyCaptured) + if (!Application.isEditor + || Application.platform != RuntimePlatform.WebGLPlayer // Startup Tracing does not properly work on WebGL + || IsIntegrationRegistered + || !GameStartupFinished) { - return false; + return true; } - return true; + return false; } public static void StartTracing() @@ -117,6 +116,8 @@ public static void AfterSceneLoad() { if (!IsStartupTracingAllowed()) { + // To make sure late init calls don't try to trace the startup + GameStartupFinished = true; return; } @@ -129,6 +130,6 @@ public static void AfterSceneLoad() Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); SentrySdk.ConfigureScope(s => s.Transaction?.Finish(SpanStatus.Ok)); - StartupAlreadyCaptured = true; + GameStartupFinished = true; } } From d100d4fe56d4deaa450fbed289ff80680a992778 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 9 Jul 2025 15:22:03 +0000 Subject: [PATCH 28/42] Format code --- .../Integrations/StartupTracingIntegration.cs | 198 +++++++++--------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs index 83934e88e..093e47e0d 100644 --- a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs +++ b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs @@ -5,130 +5,130 @@ namespace Sentry.Unity.Integrations; internal class StartupTracingIntegration : ISdkIntegration +{ + private const string StartupTransactionOperation = "app.start"; + private static ISpan? InitSpan; + private const string InitSpanOperation = "runtime.init"; + private static ISpan? SubSystemRegistrationSpan; + private const string SubSystemSpanOperation = "runtime.init.subsystem"; + private static ISpan? AfterAssembliesSpan; + private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; + private static ISpan? SplashScreenSpan; + private const string SplashScreenSpanOperation = "runtime.init.splashscreen"; + private static ISpan? FirstSceneLoadSpan; + private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; + + // Flag to make sure we create spans through the runtime initialization only once + private static bool StartupAlreadyCaptured; + private static bool IntegrationRegistered; + + private static IDiagnosticLogger? Logger; + + public void Register(IHub hub, SentryOptions options) { - private const string StartupTransactionOperation = "app.start"; - private static ISpan? InitSpan; - private const string InitSpanOperation = "runtime.init"; - private static ISpan? SubSystemRegistrationSpan; - private const string SubSystemSpanOperation = "runtime.init.subsystem"; - private static ISpan? AfterAssembliesSpan; - private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; - private static ISpan? SplashScreenSpan; - private const string SplashScreenSpanOperation = "runtime.init.splashscreen"; - private static ISpan? FirstSceneLoadSpan; - private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; - - // Flag to make sure we create spans through the runtime initialization only once - private static bool StartupAlreadyCaptured; - private static bool IntegrationRegistered; - - private static IDiagnosticLogger? Logger; - - public void Register(IHub hub, SentryOptions options) - { - Logger = options.DiagnosticLogger; + Logger = options.DiagnosticLogger; - if (options is SentryUnityOptions { TracesSampleRate: > 0, AutoStartupTraces: true }) - { - IntegrationRegistered = true; - } + if (options is SentryUnityOptions { TracesSampleRate: > 0, AutoStartupTraces: true }) + { + IntegrationRegistered = true; } + } - internal static bool IsStartupTracingAllowed() + internal static bool IsStartupTracingAllowed() + { + if (Application.isEditor + || Application.platform == RuntimePlatform.WebGLPlayer + || !IntegrationRegistered + || StartupAlreadyCaptured) { - if (Application.isEditor - || Application.platform == RuntimePlatform.WebGLPlayer - || !IntegrationRegistered - || StartupAlreadyCaptured) - { - return false; - } - - return true; + return false; } - public static void StartTracing() + return true; + } + + public static void StartTracing() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - return; - } - - Logger?.LogInfo("Creating '{0}' transaction for runtime initialization.", - StartupTransactionOperation); - - var runtimeStartTransaction = - SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); - SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); - - Logger?.LogDebug("Creating '{0}' span.", InitSpanOperation); - InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); - Logger?.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); - SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); + return; } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] - public static void AfterAssembliesLoaded() - { - if (!IsStartupTracingAllowed()) - { - return; - } + Logger?.LogInfo("Creating '{0}' transaction for runtime initialization.", + StartupTransactionOperation); - SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); - SubSystemRegistrationSpan = null; + var runtimeStartTransaction = + SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); + SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); - Logger?.LogDebug("Creating '{0}' span.", AfterAssembliesSpanOperation); - AfterAssembliesSpan = InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); - } + Logger?.LogDebug("Creating '{0}' span.", InitSpanOperation); + InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); + Logger?.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); + SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); + } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] - public static void BeforeSplashScreen() + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] + public static void AfterAssembliesLoaded() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - return; - } + return; + } - AfterAssembliesSpan?.Finish(SpanStatus.Ok); - AfterAssembliesSpan = null; + SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); + SubSystemRegistrationSpan = null; - Logger?.LogDebug("Creating '{0}' span.", SplashScreenSpanOperation); - SplashScreenSpan = InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); - } + Logger?.LogDebug("Creating '{0}' span.", AfterAssembliesSpanOperation); + AfterAssembliesSpan = InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); + } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - public static void BeforeSceneLoad() + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + public static void BeforeSplashScreen() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - return; - } + return; + } - SplashScreenSpan?.Finish(SpanStatus.Ok); - SplashScreenSpan = null; + AfterAssembliesSpan?.Finish(SpanStatus.Ok); + AfterAssembliesSpan = null; - Logger?.LogDebug("Creating '{0}' span.", FirstSceneLoadSpanOperation); - FirstSceneLoadSpan = InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); + Logger?.LogDebug("Creating '{0}' span.", SplashScreenSpanOperation); + SplashScreenSpan = InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + public static void BeforeSceneLoad() + { + if (!IsStartupTracingAllowed()) + { + return; } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] - public static void AfterSceneLoad() + SplashScreenSpan?.Finish(SpanStatus.Ok); + SplashScreenSpan = null; + + Logger?.LogDebug("Creating '{0}' span.", FirstSceneLoadSpanOperation); + FirstSceneLoadSpan = InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + public static void AfterSceneLoad() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - return; - } + return; + } - FirstSceneLoadSpan?.Finish(SpanStatus.Ok); - FirstSceneLoadSpan = null; + FirstSceneLoadSpan?.Finish(SpanStatus.Ok); + FirstSceneLoadSpan = null; - InitSpan?.Finish(SpanStatus.Ok); - InitSpan = null; + InitSpan?.Finish(SpanStatus.Ok); + InitSpan = null; - Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); - SentrySdk.ConfigureScope(s => s.Transaction?.Finish(SpanStatus.Ok)); + Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); + SentrySdk.ConfigureScope(s => s.Transaction?.Finish(SpanStatus.Ok)); - StartupAlreadyCaptured = true; - } + StartupAlreadyCaptured = true; } +} From 7faafb94353537a62dbf3d9109a42b43b3a88608 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 18:49:09 +0200 Subject: [PATCH 29/42] added tests --- .../Integrations/StartupTracingIntegration.cs | 30 +-- .../StartupTracingIntegrationTests.cs | 221 ++++++++++++++++++ test/Sentry.Unity.Tests/Stubs/TestHub.cs | 24 +- 3 files changed, 248 insertions(+), 27 deletions(-) create mode 100644 test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs diff --git a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs index 4506b0049..e651c6d1b 100644 --- a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs +++ b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs @@ -7,22 +7,25 @@ namespace Sentry.Unity.Integrations; internal class StartupTracingIntegration : ISdkIntegration { private const string StartupTransactionOperation = "app.start"; - private static ISpan? InitSpan; + internal static ISpan? InitSpan; private const string InitSpanOperation = "runtime.init"; - private static ISpan? SubSystemRegistrationSpan; + internal static ISpan? SubSystemRegistrationSpan; private const string SubSystemSpanOperation = "runtime.init.subsystem"; - private static ISpan? AfterAssembliesSpan; + internal static ISpan? AfterAssembliesSpan; private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; - private static ISpan? SplashScreenSpan; + internal static ISpan? SplashScreenSpan; private const string SplashScreenSpanOperation = "runtime.init.splashscreen"; - private static ISpan? FirstSceneLoadSpan; + internal static ISpan? FirstSceneLoadSpan; private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; - private static bool GameStartupFinished; // Flag to make sure we only create spans during the game's startup. - private static bool IsIntegrationRegistered; + internal static bool IsGameStartupFinished; // Flag to make sure we only create spans during the game's startup. + internal static bool IsIntegrationRegistered; private static IDiagnosticLogger? Logger; + // For testing. Methods with the RuntimeLoad attribute cannot have arguments + internal static IApplication? Application = null; + public void Register(IHub hub, SentryOptions options) { Logger = options.DiagnosticLogger; @@ -35,10 +38,11 @@ public void Register(IHub hub, SentryOptions options) internal static bool IsStartupTracingAllowed() { - if (!Application.isEditor - || Application.platform != RuntimePlatform.WebGLPlayer // Startup Tracing does not properly work on WebGL - || IsIntegrationRegistered - || !GameStartupFinished) + Application ??= ApplicationAdapter.Instance; + if (!Application.IsEditor + && Application.Platform != RuntimePlatform.WebGLPlayer // Startup Tracing does not properly work on WebGL + && IsIntegrationRegistered + && !IsGameStartupFinished) { return true; } @@ -117,7 +121,7 @@ public static void AfterSceneLoad() if (!IsStartupTracingAllowed()) { // To make sure late init calls don't try to trace the startup - GameStartupFinished = true; + IsGameStartupFinished = true; return; } @@ -130,6 +134,6 @@ public static void AfterSceneLoad() Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); SentrySdk.ConfigureScope(s => s.Transaction?.Finish(SpanStatus.Ok)); - GameStartupFinished = true; + IsGameStartupFinished = true; } } diff --git a/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs b/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs new file mode 100644 index 000000000..ccd608c63 --- /dev/null +++ b/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs @@ -0,0 +1,221 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Sentry.Unity.Integrations; +using Sentry.Unity.Tests.SharedClasses; +using Sentry.Unity.Tests.Stubs; +using UnityEngine; + +namespace Sentry.Unity.Tests; + +public class StartupTracingIntegrationTests +{ + private class Fixture + { + public TestHub Hub { get; set; } = null!; + public SentryUnityOptions Options { get; set; } = null!; + public TestApplication Application { get; set; } = null!; + public TestLogger Logger { get; set; } = null!; + + public StartupTracingIntegration GetSut() => new(); + } + + private Fixture _fixture = null!; + + [SetUp] + public void SetUp() + { + _fixture = new Fixture(); + _fixture.Hub = new TestHub(); + _fixture.Logger = new TestLogger(); + _fixture.Application = new TestApplication(isEditor: false, platform: RuntimePlatform.WindowsPlayer); + _fixture.Options = new SentryUnityOptions + { + TracesSampleRate = 1.0f, + AutoStartupTraces = true, + DiagnosticLogger = _fixture.Logger + }; + + ResetStaticState(); + } + + [TearDown] + public void TearDown() + { + ResetStaticState(); + if (SentrySdk.IsEnabled) + { + SentrySdk.Close(); + } + } + + private void ResetStaticState() + { + StartupTracingIntegration.Application = null; + StartupTracingIntegration.IsGameStartupFinished = false; + StartupTracingIntegration.IsIntegrationRegistered = false; + } + + [Test] + [TestCase(true, false)] + [TestCase(false, true)] + public void IsStartupTracingAllowed_IsEditor_ReturnsExpected(bool isEditor, bool expected) + { + _fixture.Application.IsEditor = isEditor; + _fixture.Application.Platform = isEditor ? RuntimePlatform.WindowsEditor : RuntimePlatform.WindowsPlayer; + StartupTracingIntegration.Application = _fixture.Application; + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + + var result = StartupTracingIntegration.IsStartupTracingAllowed(); + + Assert.AreEqual(expected, result); + } + + [Test] + [TestCase(RuntimePlatform.WebGLPlayer, false)] + [TestCase(RuntimePlatform.WindowsPlayer, true)] + [TestCase(RuntimePlatform.Android, true)] + [TestCase(RuntimePlatform.IPhonePlayer, true)] + public void IsStartupTracingAllowed_Platform_ReturnsExpected(RuntimePlatform platform, bool expected) + { + _fixture.Application.IsEditor = false; + _fixture.Application.Platform = platform; + StartupTracingIntegration.Application = _fixture.Application; + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + + var result = StartupTracingIntegration.IsStartupTracingAllowed(); + + Assert.AreEqual(expected, result); + } + + [Test] + [TestCase(true, true)] + [TestCase(false, false)] + public void IsStartupTracingAllowed_IntegrationRegistered_ReturnsExpected(bool isRegistered, bool expected) + { + _fixture.Application.IsEditor = false; + _fixture.Application.Platform = RuntimePlatform.WindowsPlayer; + StartupTracingIntegration.Application = _fixture.Application; + + if (isRegistered) + { + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + } + + Assert.AreEqual(StartupTracingIntegration.IsIntegrationRegistered, isRegistered); // Sanity Check + + var result = StartupTracingIntegration.IsStartupTracingAllowed(); + + Assert.AreEqual(expected, result); + } + + [Test] + [TestCase(false, true)] + [TestCase(true, false)] + public void IsStartupTracingAllowed_GameStartupFinished_ReturnsExpected(bool isFinished, bool expected) + { + _fixture.Application.IsEditor = false; + _fixture.Application.Platform = RuntimePlatform.WindowsPlayer; + StartupTracingIntegration.Application = _fixture.Application; + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + StartupTracingIntegration.IsGameStartupFinished = isFinished; + + var result = StartupTracingIntegration.IsStartupTracingAllowed(); + + Assert.AreEqual(expected, result); + } + + [Test] + public void Register_WithTracesSampleRateZero_DoesNotSetIntegrationRegistered() + { + _fixture.Options.TracesSampleRate = 0.0f; + + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + + Assert.IsFalse(StartupTracingIntegration.IsIntegrationRegistered); + } + + [Test] + public void Register_WithAutoStartupTracesDisabled_DoesNotSetIntegrationRegistered() + { + _fixture.Options.AutoStartupTraces = false; + + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + + Assert.IsFalse(StartupTracingIntegration.IsIntegrationRegistered); + } + + [Test] + public void Register_WithValidOptions_SetsIntegrationRegistered() + { + _fixture.Options.TracesSampleRate = 1.0f; + _fixture.Options.AutoStartupTraces = true; + + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + + Assert.IsTrue(StartupTracingIntegration.IsIntegrationRegistered); + } + + [Test] + public void StartTracing_WhenNotAllowed_DoesNotCreateTransaction() + { + _fixture.Application.Platform = RuntimePlatform.WebGLPlayer; + StartupTracingIntegration.Application = _fixture.Application; + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + StartupTracingIntegration.IsGameStartupFinished = true; + + StartupTracingIntegration.StartTracing(); + StartupTracingIntegration.AfterSceneLoad(); // Contains the finish + + Assert.IsEmpty(_fixture.Hub.CapturedTransactions); + } + + [Test] + public void StartupSequence_CallsInOrder_CreatesAndFinishesTransactionCorrectly() + { + SentrySdk.UseHub(_fixture.Hub); + _fixture.Application.IsEditor = false; + _fixture.Application.Platform = RuntimePlatform.WindowsPlayer; + StartupTracingIntegration.Application = _fixture.Application; + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + + StartupTracingIntegration.StartTracing(); + StartupTracingIntegration.AfterAssembliesLoaded(); + StartupTracingIntegration.BeforeSplashScreen(); + StartupTracingIntegration.BeforeSceneLoad(); + StartupTracingIntegration.AfterSceneLoad(); + + // Verify that ConfigureScope was called at least twice (start transaction, finish transaction) + Assert.GreaterOrEqual(_fixture.Hub.ConfigureScopeCalls.Count, 2); // Sanity Check + + var mockScope = new Scope(); + + // Apply the transaction start + _fixture.Hub.ConfigureScopeCalls.First().Invoke(mockScope); + + Assert.IsNotNull(mockScope.Transaction); + Assert.AreEqual("runtime.initialization", mockScope.Transaction!.Name); + Assert.AreEqual("app.start", mockScope.Transaction.Operation); + Assert.IsFalse(mockScope.Transaction.IsFinished); + + // Dragging it out here because it gets cleared from the scope + var transaction = mockScope.Transaction; + // Apply the transaction finish + _fixture.Hub.ConfigureScopeCalls.Last().Invoke(mockScope); + + Assert.IsTrue(transaction.IsFinished); + } + + [Test] + [TestCase(true, true)] + [TestCase(false, true)] + public void AfterSceneLoad_SetsGameStartupFinished(bool isTracingAllowed, bool expectedIsGameStartupFinished) + { + _fixture.Application.IsEditor = !isTracingAllowed; + _fixture.GetSut().Register(_fixture.Hub, _fixture.Options); + + StartupTracingIntegration.AfterSceneLoad(); + + Assert.AreEqual(StartupTracingIntegration.IsGameStartupFinished, expectedIsGameStartupFinished); + } +} diff --git a/test/Sentry.Unity.Tests/Stubs/TestHub.cs b/test/Sentry.Unity.Tests/Stubs/TestHub.cs index 1407572dc..e9d7b2d86 100644 --- a/test/Sentry.Unity.Tests/Stubs/TestHub.cs +++ b/test/Sentry.Unity.Tests/Stubs/TestHub.cs @@ -8,9 +8,11 @@ namespace Sentry.Unity.Tests.Stubs; internal sealed class TestHub : IHub { private readonly List _capturedEvents = new(); + private readonly List _capturedTransactions = new(); private readonly List> _configureScopeCalls = new(); public IReadOnlyList CapturedEvents => _capturedEvents; + public IReadOnlyList CapturedTransactions => _capturedTransactions; public IReadOnlyList> ConfigureScopeCalls => _configureScopeCalls; public TestHub(bool isEnabled = true) @@ -36,18 +38,14 @@ public void CaptureUserFeedback(UserFeedback userFeedback) throw new NotImplementedException(); } - public void CaptureTransaction(SentryTransaction transaction) - { - } + public void CaptureTransaction(SentryTransaction transaction) => + _capturedTransactions.Add(transaction); - public void CaptureTransaction(SentryTransaction transaction, Scope? scope, SentryHint? hint) - { - } + public void CaptureTransaction(SentryTransaction transaction, Scope? scope, SentryHint? hint) => + _capturedTransactions.Add(transaction); - public void CaptureTransaction(SentryTransaction transaction, SentryHint? hint) - { - throw new NotImplementedException(); - } + public void CaptureTransaction(SentryTransaction transaction, SentryHint? hint) => + _capturedTransactions.Add(transaction); public void CaptureSession(SessionUpdate sessionUpdate) { @@ -120,10 +118,8 @@ public void WithScope(Action scopeCallback) public SentryId LastEventId { get; } - public ITransactionTracer StartTransaction(ITransactionContext context, IReadOnlyDictionary customSamplingContext) - { - throw new NotImplementedException(); - } + public ITransactionTracer StartTransaction(ITransactionContext context, IReadOnlyDictionary customSamplingContext) => + new TransactionTracer(this, context); public void BindException(Exception exception, ISpan span) { From 2f42440f3022f6178132d07d0399387afe8be7f8 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 9 Jul 2025 16:53:37 +0000 Subject: [PATCH 30/42] Format code --- .../Integrations/StartupTracingIntegration.cs | 208 +++++++++--------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs index e651c6d1b..e63655f7e 100644 --- a/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs +++ b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs @@ -5,135 +5,135 @@ namespace Sentry.Unity.Integrations; internal class StartupTracingIntegration : ISdkIntegration +{ + private const string StartupTransactionOperation = "app.start"; + internal static ISpan? InitSpan; + private const string InitSpanOperation = "runtime.init"; + internal static ISpan? SubSystemRegistrationSpan; + private const string SubSystemSpanOperation = "runtime.init.subsystem"; + internal static ISpan? AfterAssembliesSpan; + private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; + internal static ISpan? SplashScreenSpan; + private const string SplashScreenSpanOperation = "runtime.init.splashscreen"; + internal static ISpan? FirstSceneLoadSpan; + private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; + + internal static bool IsGameStartupFinished; // Flag to make sure we only create spans during the game's startup. + internal static bool IsIntegrationRegistered; + + private static IDiagnosticLogger? Logger; + + // For testing. Methods with the RuntimeLoad attribute cannot have arguments + internal static IApplication? Application = null; + + public void Register(IHub hub, SentryOptions options) { - private const string StartupTransactionOperation = "app.start"; - internal static ISpan? InitSpan; - private const string InitSpanOperation = "runtime.init"; - internal static ISpan? SubSystemRegistrationSpan; - private const string SubSystemSpanOperation = "runtime.init.subsystem"; - internal static ISpan? AfterAssembliesSpan; - private const string AfterAssembliesSpanOperation = "runtime.init.afterassemblies"; - internal static ISpan? SplashScreenSpan; - private const string SplashScreenSpanOperation = "runtime.init.splashscreen"; - internal static ISpan? FirstSceneLoadSpan; - private const string FirstSceneLoadSpanOperation = "runtime.init.firstscene"; - - internal static bool IsGameStartupFinished; // Flag to make sure we only create spans during the game's startup. - internal static bool IsIntegrationRegistered; - - private static IDiagnosticLogger? Logger; - - // For testing. Methods with the RuntimeLoad attribute cannot have arguments - internal static IApplication? Application = null; - - public void Register(IHub hub, SentryOptions options) - { - Logger = options.DiagnosticLogger; + Logger = options.DiagnosticLogger; - if (options is SentryUnityOptions { TracesSampleRate: > 0, AutoStartupTraces: true }) - { - IsIntegrationRegistered = true; - } + if (options is SentryUnityOptions { TracesSampleRate: > 0, AutoStartupTraces: true }) + { + IsIntegrationRegistered = true; } + } - internal static bool IsStartupTracingAllowed() + internal static bool IsStartupTracingAllowed() + { + Application ??= ApplicationAdapter.Instance; + if (!Application.IsEditor + && Application.Platform != RuntimePlatform.WebGLPlayer // Startup Tracing does not properly work on WebGL + && IsIntegrationRegistered + && !IsGameStartupFinished) { - Application ??= ApplicationAdapter.Instance; - if (!Application.IsEditor - && Application.Platform != RuntimePlatform.WebGLPlayer // Startup Tracing does not properly work on WebGL - && IsIntegrationRegistered - && !IsGameStartupFinished) - { - return true; - } - - return false; + return true; } - public static void StartTracing() + return false; + } + + public static void StartTracing() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - return; - } - - Logger?.LogInfo("Creating '{0}' transaction for runtime initialization.", - StartupTransactionOperation); - - var runtimeStartTransaction = - SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); - SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); - - Logger?.LogDebug("Creating '{0}' span.", InitSpanOperation); - InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); - Logger?.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); - SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); + return; } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] - public static void AfterAssembliesLoaded() - { - if (!IsStartupTracingAllowed()) - { - return; - } + Logger?.LogInfo("Creating '{0}' transaction for runtime initialization.", + StartupTransactionOperation); - SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); - SubSystemRegistrationSpan = null; + var runtimeStartTransaction = + SentrySdk.StartTransaction("runtime.initialization", StartupTransactionOperation); + SentrySdk.ConfigureScope(scope => scope.Transaction = runtimeStartTransaction); - Logger?.LogDebug("Creating '{0}' span.", AfterAssembliesSpanOperation); - AfterAssembliesSpan = InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); - } + Logger?.LogDebug("Creating '{0}' span.", InitSpanOperation); + InitSpan = runtimeStartTransaction.StartChild(InitSpanOperation, "runtime initialization"); + Logger?.LogDebug("Creating '{0}' span.", SubSystemSpanOperation); + SubSystemRegistrationSpan = InitSpan.StartChild(SubSystemSpanOperation, "subsystem registration"); + } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] - public static void BeforeSplashScreen() + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] + public static void AfterAssembliesLoaded() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - return; - } + return; + } - AfterAssembliesSpan?.Finish(SpanStatus.Ok); - AfterAssembliesSpan = null; + SubSystemRegistrationSpan?.Finish(SpanStatus.Ok); + SubSystemRegistrationSpan = null; - Logger?.LogDebug("Creating '{0}' span.", SplashScreenSpanOperation); - SplashScreenSpan = InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); - } + Logger?.LogDebug("Creating '{0}' span.", AfterAssembliesSpanOperation); + AfterAssembliesSpan = InitSpan?.StartChild(AfterAssembliesSpanOperation, "after assemblies"); + } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - public static void BeforeSceneLoad() + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] + public static void BeforeSplashScreen() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - return; - } + return; + } - SplashScreenSpan?.Finish(SpanStatus.Ok); - SplashScreenSpan = null; + AfterAssembliesSpan?.Finish(SpanStatus.Ok); + AfterAssembliesSpan = null; - Logger?.LogDebug("Creating '{0}' span.", FirstSceneLoadSpanOperation); - FirstSceneLoadSpan = InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); - } + Logger?.LogDebug("Creating '{0}' span.", SplashScreenSpanOperation); + SplashScreenSpan = InitSpan?.StartChild(SplashScreenSpanOperation, "splashscreen"); + } - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] - public static void AfterSceneLoad() + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + public static void BeforeSceneLoad() + { + if (!IsStartupTracingAllowed()) { - if (!IsStartupTracingAllowed()) - { - // To make sure late init calls don't try to trace the startup - IsGameStartupFinished = true; - return; - } - - FirstSceneLoadSpan?.Finish(SpanStatus.Ok); - FirstSceneLoadSpan = null; + return; + } - InitSpan?.Finish(SpanStatus.Ok); - InitSpan = null; + SplashScreenSpan?.Finish(SpanStatus.Ok); + SplashScreenSpan = null; - Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); - SentrySdk.ConfigureScope(s => s.Transaction?.Finish(SpanStatus.Ok)); + Logger?.LogDebug("Creating '{0}' span.", FirstSceneLoadSpanOperation); + FirstSceneLoadSpan = InitSpan?.StartChild(FirstSceneLoadSpanOperation, "first scene load"); + } + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + public static void AfterSceneLoad() + { + if (!IsStartupTracingAllowed()) + { + // To make sure late init calls don't try to trace the startup IsGameStartupFinished = true; + return; } + + FirstSceneLoadSpan?.Finish(SpanStatus.Ok); + FirstSceneLoadSpan = null; + + InitSpan?.Finish(SpanStatus.Ok); + InitSpan = null; + + Logger?.LogInfo("Finishing '{0}' transaction.", StartupTransactionOperation); + SentrySdk.ConfigureScope(s => s.Transaction?.Finish(SpanStatus.Ok)); + + IsGameStartupFinished = true; } +} From d2709d1ee14caa270ccc0c2641fcb82131d74762 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 19:53:25 +0200 Subject: [PATCH 31/42] updated snapshot --- test/Scripts.Tests/package-release.zip.snapshot | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/Scripts.Tests/package-release.zip.snapshot b/test/Scripts.Tests/package-release.zip.snapshot index 48cdc6d4b..96778f1a6 100644 --- a/test/Scripts.Tests/package-release.zip.snapshot +++ b/test/Scripts.Tests/package-release.zip.snapshot @@ -1572,8 +1572,6 @@ Runtime/Sentry.xml Runtime/Sentry.xml.meta Runtime/SentryInitialization.cs Runtime/SentryInitialization.cs.meta -Runtime/SentryIntegrations.cs -Runtime/SentryIntegrations.cs.meta Runtime/SentryUserFeedback.cs Runtime/SentryUserFeedback.cs.meta Samples~/unity-of-bugs/Scenes.meta From 1183370eae8816ec4ff3c291cbf87418e4e4bdc9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 20:15:04 +0200 Subject: [PATCH 32/42] . --- test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs b/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs index ccd608c63..403b7d8e9 100644 --- a/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs +++ b/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs @@ -188,7 +188,7 @@ public void StartupSequence_CallsInOrder_CreatesAndFinishesTransactionCorrectly( // Verify that ConfigureScope was called at least twice (start transaction, finish transaction) Assert.GreaterOrEqual(_fixture.Hub.ConfigureScopeCalls.Count, 2); // Sanity Check - var mockScope = new Scope(); + var mockScope = new Scope(_fixture.Options); // Apply the transaction start _fixture.Hub.ConfigureScopeCalls.First().Invoke(mockScope); From 594dc4a762f4e5788cac6c3d406068bc46b4be63 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 10 Jul 2025 10:21:27 +0200 Subject: [PATCH 33/42] fixed test --- test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs b/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs index 403b7d8e9..d72cdeafb 100644 --- a/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs +++ b/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs @@ -173,7 +173,7 @@ public void StartTracing_WhenNotAllowed_DoesNotCreateTransaction() [Test] public void StartupSequence_CallsInOrder_CreatesAndFinishesTransactionCorrectly() { - SentrySdk.UseHub(_fixture.Hub); + Sentry.SentrySdk.UseHub(_fixture.Hub); _fixture.Application.IsEditor = false; _fixture.Application.Platform = RuntimePlatform.WindowsPlayer; StartupTracingIntegration.Application = _fixture.Application; From 3bfc5fa505245e58535462c3d4ea876e99f92dd9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 10 Jul 2025 11:36:27 +0200 Subject: [PATCH 34/42] use platform services where applicable --- package-dev/Runtime/SentryInitialization.cs | 4 ++-- src/Sentry.Unity.Android/SentryNativeAndroid.cs | 16 ++++++++++------ src/Sentry.Unity.Native/SentryNative.cs | 8 ++++++-- src/Sentry.Unity.Native/SentryNativeBridge.cs | 8 +++----- src/Sentry.Unity.iOS/SentryNativeCocoa.cs | 16 ++++++++-------- .../NativeUtils/SentryPlatformServices.cs | 10 +++++++++- src/Sentry.Unity/SentrySdk.Dotnet.cs | 3 --- src/Sentry.Unity/SentrySdk.cs | 4 +++- 8 files changed, 41 insertions(+), 28 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 316f061ca..0a6a5399d 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -66,9 +66,9 @@ internal static void Init() // If the SDK is not `enabled` we're closing down the native layer as well. This is especially relevant // in a `built-time-initialization` scenario where the native SDKs self-initialize. #if SENTRY_NATIVE_COCOA - SentryNativeCocoa.Close(options, SentryPlatformServices.UnityInfo); + SentryNativeCocoa.Close(options); #elif SENTRY_NATIVE_ANDROID - SentryNativeAndroid.Close(options, SentryPlatformServices.UnityInfo); + SentryNativeAndroid.Close(options); #endif } } diff --git a/src/Sentry.Unity.Android/SentryNativeAndroid.cs b/src/Sentry.Unity.Android/SentryNativeAndroid.cs index 76ee1f7ae..b31109779 100644 --- a/src/Sentry.Unity.Android/SentryNativeAndroid.cs +++ b/src/Sentry.Unity.Android/SentryNativeAndroid.cs @@ -1,6 +1,7 @@ using System; using Sentry.Extensibility; using Sentry.Unity.Integrations; +using Sentry.Unity.NativeUtils; using UnityEngine; using UnityEngine.Analytics; @@ -19,7 +20,10 @@ public static class SentryNativeAndroid /// Configures the native Android support. /// /// The Sentry Unity options to use. - public static void Configure(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo) + public static void Configure(SentryUnityOptions options) => + Configure(options, SentryPlatformServices.UnityInfo); + + internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo) { MainThreadData.CollectData(); @@ -105,7 +109,7 @@ public static void Configure(SentryUnityOptions options, ISentryUnityInfo sentry e, "Failed to reinstall backend. Captured native crashes will miss scope data and tag."); } - options.NativeSupportCloseCallback = () => Close(options, sentryUnityInfo); + options.NativeSupportCloseCallback = () => Close(options); options.DiagnosticLogger?.LogDebug("Fetching installation ID"); @@ -136,14 +140,14 @@ public static void Configure(SentryUnityOptions options, ISentryUnityInfo sentry /// /// Closes the native Android support. /// - public static void Close(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo) => - Close(options, sentryUnityInfo, ApplicationAdapter.Instance.Platform); + public static void Close(SentryUnityOptions options) => + Close(options, SentryPlatformServices.UnityInfo, ApplicationAdapter.Instance.Platform); - internal static void Close(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo, RuntimePlatform platform) + internal static void Close(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo, RuntimePlatform platform) { options.DiagnosticLogger?.LogInfo("Attempting to close the Android SDK"); - if (!sentryUnityInfo.IsNativeSupportEnabled(options, platform)) + if (!sentryUnityInfo?.IsNativeSupportEnabled(options, platform) ?? false) { options.DiagnosticLogger?.LogDebug("Android Native Support is not enabled. Skipping closing the Android SDK"); return; diff --git a/src/Sentry.Unity.Native/SentryNative.cs b/src/Sentry.Unity.Native/SentryNative.cs index 6f15a65f8..446fac03d 100644 --- a/src/Sentry.Unity.Native/SentryNative.cs +++ b/src/Sentry.Unity.Native/SentryNative.cs @@ -2,6 +2,7 @@ using Sentry.Extensibility; using Sentry.Unity.Integrations; using System.Collections.Generic; +using Sentry.Unity.NativeUtils; using UnityEngine; using UnityEngine.Analytics; @@ -22,13 +23,16 @@ public static class SentryNative /// /// The Sentry Unity options to use. /// Infos about the current Unity environment - public static void Configure(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo) + public static void Configure(SentryUnityOptions options) => + Configure(options, SentryPlatformServices.UnityInfo); + + internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo) { Logger = options.DiagnosticLogger; Logger?.LogInfo("Attempting to configure native support via the Native SDK"); - if (!sentryUnityInfo.IsNativeSupportEnabled(options, ApplicationAdapter.Instance.Platform)) + if (!sentryUnityInfo?.IsNativeSupportEnabled(options, ApplicationAdapter.Instance.Platform) ?? false) { Logger?.LogDebug("Native support is disabled for '{0}'.", ApplicationAdapter.Instance.Platform); return; diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs index 7d67d3325..774055213 100644 --- a/src/Sentry.Unity.Native/SentryNativeBridge.cs +++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs @@ -12,13 +12,11 @@ namespace Sentry.Unity.Native; /// P/Invoke to `sentry-native` functions. /// /// -public static class SentryNativeBridge +internal static class SentryNativeBridge { - public static bool CrashedLastRun; - - public static bool Init(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo) + public static bool Init(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo) { - _isLinux = sentryUnityInfo.IsLinux(); + _isLinux = sentryUnityInfo?.IsLinux() ?? false; var cOptions = sentry_options_new(); diff --git a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs index 766fe5a55..a2858b15e 100644 --- a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs +++ b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs @@ -1,5 +1,6 @@ using Sentry.Extensibility; using Sentry.Unity.Integrations; +using Sentry.Unity.NativeUtils; using UnityEngine; using UnityEngine.Analytics; @@ -14,15 +15,14 @@ public static class SentryNativeCocoa /// Configures the native support. /// /// The Sentry Unity options to use. - /// Infos about the current Unity environment - public static void Configure(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo) => - Configure(options, sentryUnityInfo, ApplicationAdapter.Instance.Platform); + public static void Configure(SentryUnityOptions options) => + Configure(options, SentryPlatformServices.UnityInfo, ApplicationAdapter.Instance.Platform); - internal static void Configure(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo, RuntimePlatform platform) + internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo, RuntimePlatform platform) { options.DiagnosticLogger?.LogInfo("Attempting to configure native support via the Cocoa SDK"); - if (!sentryUnityInfo.IsNativeSupportEnabled(options, platform)) + if (!sentryUnityInfo?.IsNativeSupportEnabled(options, platform) ?? false) { options.DiagnosticLogger?.LogDebug("Native support is disabled for: '{0}'", platform); return; @@ -67,7 +67,7 @@ internal static void Configure(SentryUnityOptions options, ISentryUnityInfo sent }; options.NativeSupportCloseCallback += () => Close(options, sentryUnityInfo, platform); - if (sentryUnityInfo.IL2CPP) + if (sentryUnityInfo?.IL2CPP ?? false) { options.DefaultUserId = SentryCocoaBridgeProxy.GetInstallationId(); if (string.IsNullOrEmpty(options.DefaultUserId)) @@ -99,11 +99,11 @@ internal static void Configure(SentryUnityOptions options, ISentryUnityInfo sent public static void Close(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo) => Close(options, sentryUnityInfo, ApplicationAdapter.Instance.Platform); - internal static void Close(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo, RuntimePlatform platform) + internal static void Close(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo, RuntimePlatform platform) { options.DiagnosticLogger?.LogInfo("Attempting to close the Cocoa SDK"); - if (!sentryUnityInfo.IsNativeSupportEnabled(options, platform)) + if (!sentryUnityInfo?.IsNativeSupportEnabled(options, platform) ?? false) { options.DiagnosticLogger?.LogDebug("Cocoa Native Support is not enable. Skipping closing the Cocoa SDK"); return; diff --git a/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs index c3df5fefc..d04c1ef2a 100644 --- a/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs +++ b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace Sentry.Unity.NativeUtils; @@ -10,6 +11,13 @@ namespace Sentry.Unity.NativeUtils; /// Consider this internal. public static class SentryPlatformServices { + /// + /// The UnityInfo holds methods that rely on conditionally compilation, i.e. IL2CPP backend. + /// public static ISentryUnityInfo? UnityInfo { get; set; } - public static Action? PlatformConfiguration { get; set; } + + /// + /// The PlatformConfiguration callback is responsible for configuring the native SDK and setting up scope sync. + /// + public static Action? PlatformConfiguration { get; set; } } diff --git a/src/Sentry.Unity/SentrySdk.Dotnet.cs b/src/Sentry.Unity/SentrySdk.Dotnet.cs index 00341dc3d..f9548d83e 100644 --- a/src/Sentry.Unity/SentrySdk.Dotnet.cs +++ b/src/Sentry.Unity/SentrySdk.Dotnet.cs @@ -2,11 +2,8 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.Threading; using System.Threading.Tasks; -using Sentry.Extensibility; using Sentry.Infrastructure; -using Sentry.Internal; using Sentry.Protocol.Envelopes; namespace Sentry.Unity; diff --git a/src/Sentry.Unity/SentrySdk.cs b/src/Sentry.Unity/SentrySdk.cs index a12c89ee1..e85900286 100644 --- a/src/Sentry.Unity/SentrySdk.cs +++ b/src/Sentry.Unity/SentrySdk.cs @@ -36,11 +36,13 @@ public static void Init(SentryUnityOptions options) options.LogWarning("The SDK has already been initialized. Skipping initialization."); } + // The SDK expects these to be set from `SentryInitialization.cs` via `RuntimeInitializeOnLoadMethod` attribute if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is not null) { try { - SentryPlatformServices.PlatformConfiguration.Invoke(options, SentryPlatformServices.UnityInfo); + // Since it mutates the options, this has to happen before initializing the SDK + SentryPlatformServices.PlatformConfiguration.Invoke(options); } catch (DllNotFoundException e) { From 0843ba57d61c62c9a3b4da4918368a7070bdbb26 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 10 Jul 2025 12:02:52 +0200 Subject: [PATCH 35/42] forgot webgl --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index c35a68f3f..2a0f2911e 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -1,4 +1,5 @@ using Sentry.Extensibility; +using Sentry.Unity.NativeUtils; using UnityEngine.Analytics; namespace Sentry.Unity.WebGL; @@ -12,7 +13,10 @@ public static class SentryWebGL /// Configures the WebGL support. /// /// The Sentry Unity options to use. - public static void Configure(SentryUnityOptions options, ISentryUnityInfo unityInfo) + public static void Configure(SentryUnityOptions options) => + Configure(options, SentryPlatformServices.UnityInfo); + + internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? unityInfo) { options.DiagnosticLogger?.LogDebug("Updating configuration for Unity WebGL."); From a4cbc591660a66bde53c1dba27b4b6faf6f8d3c5 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 10 Jul 2025 12:03:03 +0200 Subject: [PATCH 36/42] rename setup platform services --- package-dev/Runtime/SentryInitialization.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 0a6a5399d..d5a29b0ac 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -53,7 +53,7 @@ internal static void Init() { // We're setting up `UnityInfo` and the platform specific configure callbacks as the very first thing to be // available during initialization. - SetupPlatformConfiguration(); + SetupPlatformServices(); // Loading the options invokes the ScriptableOption`Configure` callback. Users can disable the SDK via code. var options = ScriptableSentryUnityOptions.LoadSentryUnityOptions(); @@ -73,9 +73,10 @@ internal static void Init() } } - private static void SetupPlatformConfiguration() + private static void SetupPlatformServices() { SentryPlatformServices.UnityInfo = new SentryUnityInfo(); + #if SENTRY_NATIVE_COCOA SentryPlatformServices.PlatformConfiguration = SentryNativeCocoa.Configure; #elif SENTRY_NATIVE_ANDROID From 32876acfb73cda62b9a42117649ddf8de95318b8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 10 Jul 2025 12:04:17 +0200 Subject: [PATCH 37/42] public close cutback --- src/Sentry.Unity.iOS/SentryNativeCocoa.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs index a2858b15e..fee45e392 100644 --- a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs +++ b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs @@ -96,8 +96,8 @@ internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sen /// /// Closes the native Cocoa support. /// - public static void Close(SentryUnityOptions options, ISentryUnityInfo sentryUnityInfo) => - Close(options, sentryUnityInfo, ApplicationAdapter.Instance.Platform); + public static void Close(SentryUnityOptions options) => + Close(options, SentryPlatformServices.UnityInfo, ApplicationAdapter.Instance.Platform); internal static void Close(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo, RuntimePlatform platform) { From c6cbb0c06ddffb9e2348463ab15ffaef0f9ed22f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 10 Jul 2025 16:01:06 +0200 Subject: [PATCH 38/42] review --- CHANGELOG.md | 6 +++--- package-dev/Runtime/SentryInitialization.cs | 12 +++++++++--- .../unity-of-bugs/Assets/Scripts/BugFarmButtons.cs | 1 - src/Sentry.Unity/SentrySdk.cs | 7 ++++--- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce09ac957..07d81b223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,18 +4,18 @@ ### Breaking Changes +- Removed Unity 2019 support, which reached End of Life in 2022. Minimum supported version now is 2020 ([#2231](https://github.com/getsentry/sentry-unity/pull/2231)) + - **Breaking Change**: The Unity SDK's static API has been simplified moved from `Sentry.Unity.SentryUnity` and `Sentry.SentrySdk` to `Sentry.Unity.SentrySdk`. This change enables manual SDK initialization with full functionality, previously only available through auto-initialization. The underlying .NET SDK's `SentrySdk` class is now internal, and several previously public classes like `SentryInitialization` and `SentryIntegrations` are now internal. - **Migration**: Update your using statements from `using Sentry;` to `using Sentry.Unity;`. IDEs like Rider can automatically + **Migration**: Update your `using` directives from `using Sentry;` to `using Sentry.Unity;`. IDEs like Rider can automatically import the missing references. In some cases, you may need both `using Sentry.Unity;` (for the static API) and `using Sentry;` (for types like `SentryId`). No changes are required to your actual SDK method calls (e.g., `SentrySdk.CaptureException()` remains the same). ([#2227](https://github.com/getsentry/sentry-unity/pull/2227)) -- Updated Unity support by updating to Unity versions (2020+), removing Unity 2019 which reached End of Life in 2022 ([#2231](https://github.com/getsentry/sentry-unity/pull/2231)) -- Dropped support for Unity 2019. It reached End of Life in 2022 ([#2231](https://github.com/getsentry/sentry-unity/pull/2231)) ### Features diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index d5a29b0ac..dc0485125 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -43,16 +43,22 @@ namespace Sentry.Unity { internal static class SentryInitialization { + /// + /// This is intended for internal use only. + /// The SDK relies on SetupPlatformServices getting called as the very first thing during the game's + /// startup. This ensures that features like line number and native support are set up and configured properly. + /// This is also the case when initializing manually from code. + /// #if SENTRY_WEBGL // On WebGL SubsystemRegistration is too early for the UnityWebRequestTransport and errors with 'URI empty' [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] #else [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] #endif - internal static void Init() + private static void Init() { - // We're setting up `UnityInfo` and the platform specific configure callbacks as the very first thing to be - // available during initialization. + // We're setting up `UnityInfo` and the platform specific configure callbacks as the very first thing. + // These are required to be available during initialization. SetupPlatformServices(); // Loading the options invokes the ScriptableOption`Configure` callback. Users can disable the SDK via code. diff --git a/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs b/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs index 0126bdd6e..f43152920 100644 --- a/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs +++ b/samples/unity-of-bugs/Assets/Scripts/BugFarmButtons.cs @@ -5,7 +5,6 @@ using UnityEngine; using UnityEngine.Assertions; - public class BugFarmButtons : MonoBehaviour { private void Awake() diff --git a/src/Sentry.Unity/SentrySdk.cs b/src/Sentry.Unity/SentrySdk.cs index e85900286..44c225646 100644 --- a/src/Sentry.Unity/SentrySdk.cs +++ b/src/Sentry.Unity/SentrySdk.cs @@ -37,12 +37,13 @@ public static void Init(SentryUnityOptions options) } // The SDK expects these to be set from `SentryInitialization.cs` via `RuntimeInitializeOnLoadMethod` attribute - if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is not null) + // if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is not null) + if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is {} platformConfiguration) { try { - // Since it mutates the options, this has to happen before initializing the SDK - SentryPlatformServices.PlatformConfiguration.Invoke(options); + // Since this mutates the options (i.e. adding scope observer) we have to invoke before initializing the SDK + platformConfiguration.Invoke(options); } catch (DllNotFoundException e) { From 3a2328bf8e1355c486e0dbf8a966fa83787edf9c Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 10 Jul 2025 14:02:28 +0000 Subject: [PATCH 39/42] Format code --- modules/sentry-java | 2 +- src/Sentry.Unity/SentrySdk.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sentry-java b/modules/sentry-java index e687305e2..385ce0639 160000 --- a/modules/sentry-java +++ b/modules/sentry-java @@ -1 +1 @@ -Subproject commit e687305e2ce67488952ee023d07b6656ee3ca7ab +Subproject commit 385ce0639702b672c847c7c96f4bded8618b782d diff --git a/src/Sentry.Unity/SentrySdk.cs b/src/Sentry.Unity/SentrySdk.cs index 44c225646..aaff9e986 100644 --- a/src/Sentry.Unity/SentrySdk.cs +++ b/src/Sentry.Unity/SentrySdk.cs @@ -38,7 +38,7 @@ public static void Init(SentryUnityOptions options) // The SDK expects these to be set from `SentryInitialization.cs` via `RuntimeInitializeOnLoadMethod` attribute // if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is not null) - if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is {} platformConfiguration) + if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is { } platformConfiguration) { try { From ac366e75166ee8bf966a415884ef1fa895f4dac1 Mon Sep 17 00:00:00 2001 From: Stefan Jandl Date: Fri, 11 Jul 2025 14:36:39 +0200 Subject: [PATCH 40/42] Update CHANGELOG.md Co-authored-by: Bruno Garcia --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efe46a88a..40dd39dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - **Breaking Change**: The Unity SDK's static API has been simplified moved from `Sentry.Unity.SentryUnity` and `Sentry.SentrySdk` to `Sentry.Unity.SentrySdk`. - This change enables manual SDK initialization with full functionality, previously only available through auto-initialization. + This change enables [manual/programatic SDK initialization](https://docs.sentry.io/platforms/unity/configuration/options/programmatic-configuration/) with full functionality, previously only available through auto-initialization. The underlying .NET SDK's `SentrySdk` class is now internal, and several previously public classes like `SentryInitialization` and `SentryIntegrations` are now internal. From 1f520ee53cf0cd6d13ce9427210f60bcc3d47d99 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 11 Jul 2025 18:43:49 +0200 Subject: [PATCH 41/42] hold platformservices in options and handle tests --- .../SentryNativeAndroid.cs | 12 ++----- .../SentryScriptableObject.cs | 2 +- src/Sentry.Unity.Native/SentryNative.cs | 9 ++---- src/Sentry.Unity.Native/SentryNativeBridge.cs | 4 +-- src/Sentry.Unity.iOS/SentryNativeCocoa.cs | 19 +++++------- src/Sentry.Unity/Il2CppEventProcessor.cs | 6 ++-- .../NativeUtils/SentryPlatformServices.cs | 17 ++++++++-- .../ScriptableSentryUnityOptions.cs | 16 +++++----- src/Sentry.Unity/SentrySdk.cs | 31 +++++++------------ src/Sentry.Unity/SentryUnityOptions.cs | 12 ++++++- .../SentryUnityOptionsExtensions.cs | 11 ++----- src/Sentry.Unity/WebGL/SentryWebGL.cs | 5 +-- .../SentryPlatformServicesTests.cs | 20 ++++++++++++ .../SentryNativeIosTests.cs | 15 +++++++-- 14 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 test/Sentry.Unity.Tests/SentryPlatformServicesTests.cs diff --git a/src/Sentry.Unity.Android/SentryNativeAndroid.cs b/src/Sentry.Unity.Android/SentryNativeAndroid.cs index b31109779..6c3e6daea 100644 --- a/src/Sentry.Unity.Android/SentryNativeAndroid.cs +++ b/src/Sentry.Unity.Android/SentryNativeAndroid.cs @@ -20,10 +20,7 @@ public static class SentryNativeAndroid /// Configures the native Android support. /// /// The Sentry Unity options to use. - public static void Configure(SentryUnityOptions options) => - Configure(options, SentryPlatformServices.UnityInfo); - - internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo) + public static void Configure(SentryUnityOptions options) { MainThreadData.CollectData(); @@ -140,14 +137,11 @@ internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sen /// /// Closes the native Android support. /// - public static void Close(SentryUnityOptions options) => - Close(options, SentryPlatformServices.UnityInfo, ApplicationAdapter.Instance.Platform); - - internal static void Close(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo, RuntimePlatform platform) + public static void Close(SentryUnityOptions options) { options.DiagnosticLogger?.LogInfo("Attempting to close the Android SDK"); - if (!sentryUnityInfo?.IsNativeSupportEnabled(options, platform) ?? false) + if (!options.UnityInfo.IsNativeSupportEnabled(options, ApplicationAdapter.Instance.Platform)) { options.DiagnosticLogger?.LogDebug("Android Native Support is not enabled. Skipping closing the Android SDK"); return; diff --git a/src/Sentry.Unity.Editor/SentryScriptableObject.cs b/src/Sentry.Unity.Editor/SentryScriptableObject.cs index 7764c119f..844e43f93 100644 --- a/src/Sentry.Unity.Editor/SentryScriptableObject.cs +++ b/src/Sentry.Unity.Editor/SentryScriptableObject.cs @@ -36,7 +36,7 @@ internal static (SentryUnityOptions?, SentryCliOptions?) ConfiguredBuildTimeOpti SentryUnityOptions? options = null; if (scriptableOptions is not null) { - options = scriptableOptions.ToSentryUnityOptions(isBuilding: true, unityInfo: null); + options = scriptableOptions.ToSentryUnityOptions(isBuilding: true); // TODO: Move this into `Load` once we remove Runtime- and BuildTimeConfig // We're calling `Configure` here and not in `Load` so the new Config does not overwrite the BuildTimeConfig diff --git a/src/Sentry.Unity.Native/SentryNative.cs b/src/Sentry.Unity.Native/SentryNative.cs index 446fac03d..d4b132d52 100644 --- a/src/Sentry.Unity.Native/SentryNative.cs +++ b/src/Sentry.Unity.Native/SentryNative.cs @@ -23,16 +23,13 @@ public static class SentryNative /// /// The Sentry Unity options to use. /// Infos about the current Unity environment - public static void Configure(SentryUnityOptions options) => - Configure(options, SentryPlatformServices.UnityInfo); - - internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo) + public static void Configure(SentryUnityOptions options) { Logger = options.DiagnosticLogger; Logger?.LogInfo("Attempting to configure native support via the Native SDK"); - if (!sentryUnityInfo?.IsNativeSupportEnabled(options, ApplicationAdapter.Instance.Platform) ?? false) + if (!options.UnityInfo.IsNativeSupportEnabled(options, ApplicationAdapter.Instance.Platform)) { Logger?.LogDebug("Native support is disabled for '{0}'.", ApplicationAdapter.Instance.Platform); return; @@ -40,7 +37,7 @@ internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sen try { - if (!SentryNativeBridge.Init(options, sentryUnityInfo)) + if (!SentryNativeBridge.Init(options)) { Logger?.LogWarning("Sentry native initialization failed - native crashes are not captured."); return; diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs index 774055213..bdfab9f00 100644 --- a/src/Sentry.Unity.Native/SentryNativeBridge.cs +++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs @@ -14,9 +14,9 @@ namespace Sentry.Unity.Native; /// internal static class SentryNativeBridge { - public static bool Init(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo) + public static bool Init(SentryUnityOptions options) { - _isLinux = sentryUnityInfo?.IsLinux() ?? false; + _isLinux = options.UnityInfo.IsLinux(); var cOptions = sentry_options_new(); diff --git a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs index fee45e392..6da06232d 100644 --- a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs +++ b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs @@ -1,6 +1,5 @@ using Sentry.Extensibility; using Sentry.Unity.Integrations; -using Sentry.Unity.NativeUtils; using UnityEngine; using UnityEngine.Analytics; @@ -16,13 +15,14 @@ public static class SentryNativeCocoa /// /// The Sentry Unity options to use. public static void Configure(SentryUnityOptions options) => - Configure(options, SentryPlatformServices.UnityInfo, ApplicationAdapter.Instance.Platform); + Configure(options, ApplicationAdapter.Instance.Platform); - internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo, RuntimePlatform platform) + // For testing + internal static void Configure(SentryUnityOptions options, RuntimePlatform platform) { options.DiagnosticLogger?.LogInfo("Attempting to configure native support via the Cocoa SDK"); - if (!sentryUnityInfo?.IsNativeSupportEnabled(options, platform) ?? false) + if (!options.UnityInfo.IsNativeSupportEnabled(options, platform)) { options.DiagnosticLogger?.LogDebug("Native support is disabled for: '{0}'", platform); return; @@ -66,8 +66,8 @@ internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sen return crashedLastRun; }; - options.NativeSupportCloseCallback += () => Close(options, sentryUnityInfo, platform); - if (sentryUnityInfo?.IL2CPP ?? false) + options.NativeSupportCloseCallback += () => Close(options); + if (options.UnityInfo.IL2CPP) { options.DefaultUserId = SentryCocoaBridgeProxy.GetInstallationId(); if (string.IsNullOrEmpty(options.DefaultUserId)) @@ -96,14 +96,11 @@ internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? sen /// /// Closes the native Cocoa support. /// - public static void Close(SentryUnityOptions options) => - Close(options, SentryPlatformServices.UnityInfo, ApplicationAdapter.Instance.Platform); - - internal static void Close(SentryUnityOptions options, ISentryUnityInfo? sentryUnityInfo, RuntimePlatform platform) + public static void Close(SentryUnityOptions options) { options.DiagnosticLogger?.LogInfo("Attempting to close the Cocoa SDK"); - if (!sentryUnityInfo?.IsNativeSupportEnabled(options, platform) ?? false) + if (!options.UnityInfo.IsNativeSupportEnabled(options, ApplicationAdapter.Instance.Platform)) { options.DiagnosticLogger?.LogDebug("Cocoa Native Support is not enable. Skipping closing the Cocoa SDK"); return; diff --git a/src/Sentry.Unity/Il2CppEventProcessor.cs b/src/Sentry.Unity/Il2CppEventProcessor.cs index 612dcbc93..68247ef77 100644 --- a/src/Sentry.Unity/Il2CppEventProcessor.cs +++ b/src/Sentry.Unity/Il2CppEventProcessor.cs @@ -17,11 +17,11 @@ internal class UnityIl2CppEventExceptionProcessor : ISentryEventExceptionProcess private static ISentryUnityInfo UnityInfo = null!; // private static will be initialized in the constructor private readonly Il2CppMethods _il2CppMethods; - public UnityIl2CppEventExceptionProcessor(SentryUnityOptions options, ISentryUnityInfo unityInfo) + public UnityIl2CppEventExceptionProcessor(SentryUnityOptions options) { Options = options; - UnityInfo = unityInfo; - _il2CppMethods = unityInfo.Il2CppMethods ?? throw new ArgumentNullException(nameof(unityInfo.Il2CppMethods), + UnityInfo = options.UnityInfo; + _il2CppMethods = UnityInfo.Il2CppMethods ?? throw new ArgumentNullException(nameof(UnityInfo.Il2CppMethods), "Unity IL2CPP methods are not available."); Options.SdkIntegrationNames.Add("IL2CPPLineNumbers"); diff --git a/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs index d04c1ef2a..3070de469 100644 --- a/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs +++ b/src/Sentry.Unity/NativeUtils/SentryPlatformServices.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.CompilerServices; namespace Sentry.Unity.NativeUtils; @@ -11,10 +10,24 @@ namespace Sentry.Unity.NativeUtils; /// Consider this internal. public static class SentryPlatformServices { + private static ISentryUnityInfo? _unityInfo; + /// /// The UnityInfo holds methods that rely on conditionally compilation, i.e. IL2CPP backend. /// - public static ISentryUnityInfo? UnityInfo { get; set; } + public static ISentryUnityInfo UnityInfo + { + get => _unityInfo ?? throw new InvalidOperationException("UnityInfo is null."); + set + { + if (_unityInfo != null) + { + throw new InvalidOperationException("Should not set twice. lol."); + } + + _unityInfo = value; + } + } /// /// The PlatformConfiguration callback is responsible for configuring the native SDK and setting up scope sync. diff --git a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs index 2b401242d..3c7ec50c9 100644 --- a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs +++ b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs @@ -126,17 +126,17 @@ public static string GetConfigPath(string? notDefaultConfigName = null) var scriptableOptions = Resources.Load($"{ConfigRootFolder}/{ConfigName}"); if (scriptableOptions is not null) { - return scriptableOptions.ToSentryUnityOptions(false, SentryPlatformServices.UnityInfo); + return scriptableOptions.ToSentryUnityOptions(false); } return null; } - internal SentryUnityOptions ToSentryUnityOptions(bool isBuilding, ISentryUnityInfo? unityInfo, IApplication? application = null) + internal SentryUnityOptions ToSentryUnityOptions(bool isBuilding, IApplication? application = null) { application ??= ApplicationAdapter.Instance; - var options = new SentryUnityOptions(isBuilding, application, unityInfo) + var options = new SentryUnityOptions(isBuilding, application) { Enabled = Enabled, Dsn = Dsn, @@ -236,12 +236,12 @@ internal SentryUnityOptions ToSentryUnityOptions(bool isBuilding, ISentryUnityIn options.AddEventProcessor(new ScreenshotEventProcessor(options)); } - if (!application.IsEditor && options.Il2CppLineNumberSupportEnabled && unityInfo is not null) + if (!application.IsEditor && options.Il2CppLineNumberSupportEnabled) { - options.AddIl2CppExceptionProcessor(unityInfo); + options.AddIl2CppExceptionProcessor(); } - HandlePlatformRestrictedOptions(options, unityInfo, application); + HandlePlatformRestrictedOptions(options, application); HandleExceptionFilter(options); if (!AnrDetectionEnabled) @@ -252,9 +252,9 @@ internal SentryUnityOptions ToSentryUnityOptions(bool isBuilding, ISentryUnityIn return options; } - internal void HandlePlatformRestrictedOptions(SentryUnityOptions options, ISentryUnityInfo? unityInfo, IApplication application) + internal void HandlePlatformRestrictedOptions(SentryUnityOptions options, IApplication application) { - if (unityInfo?.IsKnownPlatform() == false) + if (!options.UnityInfo.IsKnownPlatform()) { options.DisableFileWrite = true; diff --git a/src/Sentry.Unity/SentrySdk.cs b/src/Sentry.Unity/SentrySdk.cs index aaff9e986..d5034a267 100644 --- a/src/Sentry.Unity/SentrySdk.cs +++ b/src/Sentry.Unity/SentrySdk.cs @@ -36,29 +36,20 @@ public static void Init(SentryUnityOptions options) options.LogWarning("The SDK has already been initialized. Skipping initialization."); } - // The SDK expects these to be set from `SentryInitialization.cs` via `RuntimeInitializeOnLoadMethod` attribute - // if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is not null) - if (SentryPlatformServices.UnityInfo is not null && SentryPlatformServices.PlatformConfiguration is { } platformConfiguration) + try { - try - { - // Since this mutates the options (i.e. adding scope observer) we have to invoke before initializing the SDK - platformConfiguration.Invoke(options); - } - catch (DllNotFoundException e) - { - options.LogError(e, - "Sentry native-error capture configuration failed to load a native library. This usually " + - "means the library is missing from the application bundle or the installation directory."); - } - catch (Exception e) - { - options.LogError(e, "Sentry native error capture configuration failed."); - } + // Since this mutates the options (i.e. adding scope observer) we have to invoke before initializing the SDK + options.PlatformConfiguration.Invoke(options); } - else + catch (DllNotFoundException e) { - options.LogWarning("The SDK's Platform Services have not been set up. Native support will be limited."); + options.LogError(e, + "Sentry native-error capture configuration failed to load a native library. This usually " + + "means the library is missing from the application bundle or the installation directory."); + } + catch (Exception e) + { + options.LogError(e, "Sentry native error capture configuration failed."); } UnitySdk = SentryUnitySdk.Init(options); diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index ed1184db2..94bed43f8 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using Sentry.Unity.Integrations; using Sentry.Extensibility; +using Sentry.Unity.NativeUtils; using UnityEngine; using CompressionLevel = System.IO.Compression.CompressionLevel; @@ -294,6 +295,9 @@ internal string? DefaultUserId internal List SdkIntegrationNames { get; set; } = new(); + internal ISentryUnityInfo UnityInfo { get; private set; } + internal Action PlatformConfiguration { get; private set; } + public SentryUnityOptions() : this(false, ApplicationAdapter.Instance) { } internal SentryUnityOptions(bool isBuilding, IApplication application, ISentryUnityInfo? unityInfo = null) : @@ -301,8 +305,14 @@ internal SentryUnityOptions(bool isBuilding, IApplication application, ISentryUn { } // For testing - internal SentryUnityOptions(SentryMonoBehaviour behaviour, IApplication application, bool isBuilding, ISentryUnityInfo? unityInfo = null) + private SentryUnityOptions(SentryMonoBehaviour behaviour, IApplication application, bool isBuilding, ISentryUnityInfo? unityInfo) { + // This should never happen. The PlatformServices are set through the RuntimeLoad attribute in 'SentryInitialization.cs' + // and required to be present. + UnityInfo = unityInfo ?? SentryPlatformServices.UnityInfo; + PlatformConfiguration = SentryPlatformServices.PlatformConfiguration + ?? throw new ArgumentNullException(nameof(SentryPlatformServices.PlatformConfiguration), "PlatformConfiguration is null."); + // IL2CPP doesn't support Process.GetCurrentProcess().StartupTime DetectStartupTime = StartupTimeDetectionMode.Fast; diff --git a/src/Sentry.Unity/SentryUnityOptionsExtensions.cs b/src/Sentry.Unity/SentryUnityOptionsExtensions.cs index 773e45391..ea97618f5 100644 --- a/src/Sentry.Unity/SentryUnityOptionsExtensions.cs +++ b/src/Sentry.Unity/SentryUnityOptionsExtensions.cs @@ -63,16 +63,9 @@ internal static void SetupUnityLogging(this SentryUnityOptions options) } } - internal static void AddIl2CppExceptionProcessor(this SentryUnityOptions options, ISentryUnityInfo unityInfo) + internal static void AddIl2CppExceptionProcessor(this SentryUnityOptions options) { - if (unityInfo.Il2CppMethods is not null) - { - options.AddExceptionProcessor(new UnityIl2CppEventExceptionProcessor(options, unityInfo)); - } - else - { - options.DiagnosticLogger?.LogWarning("Failed to find required IL2CPP methods - Skipping line number support"); - } + options.AddExceptionProcessor(new UnityIl2CppEventExceptionProcessor(options)); } /// diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 2a0f2911e..a863a23b5 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -13,10 +13,7 @@ public static class SentryWebGL /// Configures the WebGL support. /// /// The Sentry Unity options to use. - public static void Configure(SentryUnityOptions options) => - Configure(options, SentryPlatformServices.UnityInfo); - - internal static void Configure(SentryUnityOptions options, ISentryUnityInfo? unityInfo) + public static void Configure(SentryUnityOptions options) { options.DiagnosticLogger?.LogDebug("Updating configuration for Unity WebGL."); diff --git a/test/Sentry.Unity.Tests/SentryPlatformServicesTests.cs b/test/Sentry.Unity.Tests/SentryPlatformServicesTests.cs new file mode 100644 index 000000000..8957e396c --- /dev/null +++ b/test/Sentry.Unity.Tests/SentryPlatformServicesTests.cs @@ -0,0 +1,20 @@ +using NUnit.Framework; +using Sentry.Unity.NativeUtils; + +namespace Sentry.Unity.Tests; + +[SetUpFixture] +public class SentryPlatformServicesTests +{ + [OneTimeSetUp] + public void OneTimeSetUp() + { + SentryPlatformServices.UnityInfo = new TestUnityInfo(); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + + } +} diff --git a/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs b/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs index 133cef2ab..b27bef0ff 100644 --- a/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs +++ b/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs @@ -1,20 +1,29 @@ using System; using NUnit.Framework; +using Sentry.Unity.NativeUtils; +using Sentry.Unity.Tests.Stubs; using UnityEngine; namespace Sentry.Unity.iOS.Tests; public class SentryNativeCocoaTests { + [TearDown] + public void TearDown() + { + SentryPlatformServices.UnityInfo = null; + } + [Test] public void Configure_DefaultConfiguration_iOS() { - var unityInfo = new TestUnityInfo { IL2CPP = false }; + SentryPlatformServices.UnityInfo = new TestUnityInfo { IL2CPP = false };; var options = new SentryUnityOptions(); + // Note: can't test iOS - throws because it tries to call SentryCocoaBridgeProxy.Init() // but the bridge isn't loaded now... Assert.Throws(() => - SentryNativeCocoa.Configure(options, unityInfo, RuntimePlatform.IPhonePlayer)); + SentryNativeCocoa.Configure(options, RuntimePlatform.IPhonePlayer)); } [Test] @@ -22,7 +31,7 @@ public void Configure_NativeSupportDisabled_iOS() { var unityInfo = new TestUnityInfo(true, false, false) { IL2CPP = false }; var options = new SentryUnityOptions { IosNativeSupportEnabled = false }; - SentryNativeCocoa.Configure(options, unityInfo, RuntimePlatform.IPhonePlayer); + SentryNativeCocoa.Configure(options, RuntimePlatform.IPhonePlayer); Assert.Null(options.ScopeObserver); Assert.Null(options.CrashedLastRun); Assert.False(options.EnableScopeSync); From a47872d1e50f78ea56039ea622623da016a983eb Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 11 Jul 2025 16:45:12 +0000 Subject: [PATCH 42/42] Format code --- test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs b/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs index b27bef0ff..c79c78753 100644 --- a/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs +++ b/test/Sentry.Unity.iOS.Tests/SentryNativeIosTests.cs @@ -17,7 +17,8 @@ public void TearDown() [Test] public void Configure_DefaultConfiguration_iOS() { - SentryPlatformServices.UnityInfo = new TestUnityInfo { IL2CPP = false };; + SentryPlatformServices.UnityInfo = new TestUnityInfo { IL2CPP = false }; + ; var options = new SentryUnityOptions(); // Note: can't test iOS - throws because it tries to call SentryCocoaBridgeProxy.Init()