From ad0ae02ee7d6fac22cbf94cf9d60efdafdf1fe12 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 9 Jul 2025 17:07:21 +0200 Subject: [PATCH 1/8] 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 2/8] . --- 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] . --- 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);