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..e63655f7e --- /dev/null +++ b/src/Sentry.Unity/Integrations/StartupTracingIntegration.cs @@ -0,0 +1,139 @@ +using Sentry.Extensibility; +using Sentry.Integrations; +using UnityEngine; + +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) + { + Logger = options.DiagnosticLogger; + + if (options is SentryUnityOptions { TracesSampleRate: > 0, AutoStartupTraces: true }) + { + IsIntegrationRegistered = true; + } + } + + internal static bool IsStartupTracingAllowed() + { + Application ??= ApplicationAdapter.Instance; + if (!Application.IsEditor + && Application.Platform != RuntimePlatform.WebGLPlayer // Startup Tracing does not properly work on WebGL + && IsIntegrationRegistered + && !IsGameStartupFinished) + { + return true; + } + + return false; + } + + 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()) + { + // 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; + } +} diff --git a/src/Sentry.Unity/SentryUnity.cs b/src/Sentry.Unity/SentryUnity.cs index 4306d8dce..c3b69806e 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; 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) => 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 diff --git a/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs b/test/Sentry.Unity.Tests/StartupTracingIntegrationTests.cs new file mode 100644 index 000000000..403b7d8e9 --- /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(_fixture.Options); + + // 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) {