Skip to content

feat: Trace Generation Integration #2123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### Features

- The trace used to connect errors on different layers of your game now gets regenerated every time the app gains focus again, or the active scene changes ([#2123](https://github.com/getsentry/sentry-unity/pull/2123))
- The SDK now links errors and events (managed and native errors) via `trace ID`. This allows you to correlate events captured from different layers of your game ([#1997](https://github.com/getsentry/sentry-unity/pull/1997), [#2089](https://github.com/getsentry/sentry-unity/pull/2089), [#2106](https://github.com/getsentry/sentry-unity/pull/2106))
- Drastically improved performance of scope sync when targeting Android ([#2107](https://github.com/getsentry/sentry-unity/pull/2107))
- The SDK now reports the game's name as part of the app context ([2083](https://github.com/getsentry/sentry-unity/pull/2083))
Expand Down
2 changes: 0 additions & 2 deletions src/Sentry.Unity/Integrations/SessionIntegration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ public void Register(IHub hub, SentryOptions options)
return;
}

options.DiagnosticLogger?.LogDebug("Registering Session integration.");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hub already logs which integration get registered. This is redundant.


_sentryMonoBehaviour.ApplicationResuming += () =>
{
options.DiagnosticLogger?.LogDebug("Resuming session.");
Expand Down
39 changes: 39 additions & 0 deletions src/Sentry.Unity/Integrations/TraceGenerationIntegration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Sentry.Extensibility;
using Sentry.Integrations;

namespace Sentry.Unity.Integrations;

internal sealed class TraceGenerationIntegration : ISdkIntegration
{
private readonly ISceneManager _sceneManager;
private readonly ISentryMonoBehaviour _sentryMonoBehaviour;

public TraceGenerationIntegration(SentryMonoBehaviour sentryMonoBehaviour) : this(sentryMonoBehaviour, SceneManagerAdapter.Instance)
{ }

internal TraceGenerationIntegration(ISentryMonoBehaviour sentryMonoBehaviour, ISceneManager sceneManager)
{
_sceneManager = sceneManager;
_sentryMonoBehaviour = sentryMonoBehaviour;
}

public void Register(IHub hub, SentryOptions options)
{
hub.ConfigureScope(UpdatePropagationContext);

_sentryMonoBehaviour.ApplicationResuming += () =>
{
options.DiagnosticLogger?.LogDebug("Application resumed. Creating new Trace.");
hub.ConfigureScope(UpdatePropagationContext);
};

_sceneManager.ActiveSceneChanged += (_, _) =>
{
options.DiagnosticLogger?.LogDebug("Active Scene changed. Creating new Trace.");
hub.ConfigureScope(UpdatePropagationContext);
};
}

private static void UpdatePropagationContext(Scope scope) =>
scope.SetPropagationContext(new SentryPropagationContext());
}
7 changes: 6 additions & 1 deletion src/Sentry.Unity/SentryMonoBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@

namespace Sentry.Unity;

internal interface ISentryMonoBehaviour
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm creating an internal interface to be able to mock this in the tests.

{
event Action? ApplicationResuming;
}

/// <summary>
/// Singleton and DontDestroyOnLoad setup.
/// </summary>
[AddComponentMenu("")] // Hides it from being added as a component in the inspector
public partial class SentryMonoBehaviour : MonoBehaviour
public partial class SentryMonoBehaviour : MonoBehaviour, ISentryMonoBehaviour
{
private static SentryMonoBehaviour? _instance;
public static SentryMonoBehaviour Instance
Expand Down
1 change: 1 addition & 0 deletions src/Sentry.Unity/SentryUnityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ internal SentryUnityOptions(SentryMonoBehaviour behaviour, IApplication applicat
this.AddIntegration(new UnityBeforeSceneLoadIntegration());
this.AddIntegration(new SceneManagerIntegration());
this.AddIntegration(new SessionIntegration(behaviour));
this.AddIntegration(new TraceGenerationIntegration(behaviour));

this.AddExceptionFilter(new UnityBadGatewayExceptionFilter());
this.AddExceptionFilter(new UnityWebExceptionFilter());
Expand Down
103 changes: 103 additions & 0 deletions test/Sentry.Unity.Tests/TraceGenerationIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Linq;
using NUnit.Framework;
using Sentry.Unity.Integrations;
using Sentry.Unity.Tests.SharedClasses;
using Sentry.Unity.Tests.Stubs;
using static Sentry.Unity.Tests.SceneManagerIntegrationTests;

namespace Sentry.Unity.Tests;

public class TraceGenerationIntegrationTests
{
private class Fixture
{
public FakeSceneManager SceneManager { get; set; } = new();
public TestSentryMonoBehaviour SentryMonoBehaviour { get; set; } = new();
public TestHub TestHub { get; set; } = new();
public TestLogger Logger { get; set; } = new();
public SentryOptions SentryOptions { get; set; }

public Fixture() => SentryOptions = new SentryOptions { DiagnosticLogger = Logger };

public TraceGenerationIntegration GetSut() => new(SentryMonoBehaviour, SceneManager);
}

private readonly Fixture _fixture = new();

[SetUp]
public void SetUp()
{
_fixture.TestHub = new TestHub();
SentrySdk.UseHub(_fixture.TestHub);
}

[Test]
public void TraceGeneration_OnRegister_GeneratesInitialTrace()
{
// Arrange
var sut = _fixture.GetSut();

// Act
sut.Register(_fixture.TestHub, _fixture.SentryOptions);

// Assert
var configureScope = _fixture.TestHub.ConfigureScopeCalls.Single();
var scope = new Scope(_fixture.SentryOptions);
var initialPropagationContext = scope.PropagationContext;
configureScope(scope);

Assert.AreNotEqual(initialPropagationContext, scope.PropagationContext);
}

[Test]
public void TraceGeneration_OnApplicationResume_GeneratesNewTrace()
{
// Arrange
var sut = _fixture.GetSut();
sut.Register(_fixture.TestHub, _fixture.SentryOptions);
var initialCallsCount = _fixture.TestHub.ConfigureScopeCalls.Count;

// Act
_fixture.SentryMonoBehaviour.ResumeApplication();

// Assert
// Calling 'Register' already generated a trace, so we expect 1+1 calls to ConfigureScope
Assert.AreEqual(initialCallsCount + 1, _fixture.TestHub.ConfigureScopeCalls.Count);
var configureScope = _fixture.TestHub.ConfigureScopeCalls.Last();
var scope = new Scope(_fixture.SentryOptions);
var initialPropagationContext = scope.PropagationContext;
configureScope(scope);

Assert.AreNotEqual(initialPropagationContext, scope.PropagationContext);
}

[Test]
public void TraceGeneration_OnActiveSceneChange_GeneratesNewTrace()
{
// Arrange
var sut = _fixture.GetSut();
sut.Register(_fixture.TestHub, _fixture.SentryOptions);
var initialCallsCount = _fixture.TestHub.ConfigureScopeCalls.Count;

// Act
_fixture.SceneManager.OnActiveSceneChanged(new SceneAdapter("from scene name"), new SceneAdapter("to scene name"));

// Assert
// Calling 'Register' already generated a trace, so we expect 1+1 calls to ConfigureScope
Assert.AreEqual(initialCallsCount + 1, _fixture.TestHub.ConfigureScopeCalls.Count);
var configureScope = _fixture.TestHub.ConfigureScopeCalls.Last();
var scope = new Scope(_fixture.SentryOptions);
var initialPropagationContext = scope.PropagationContext;
configureScope(scope);

Assert.AreNotEqual(initialPropagationContext, scope.PropagationContext);
}

internal class TestSentryMonoBehaviour : ISentryMonoBehaviour
{
public event Action? ApplicationResuming;

public void ResumeApplication() => ApplicationResuming?.Invoke();
}
}