Skip to content

Commit ba62623

Browse files
authored
feat: LifeCycleIntegration and changes to trace generation (#2374)
1 parent 49b4091 commit ba62623

File tree

9 files changed

+90
-94
lines changed

9 files changed

+90
-94
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- `sentry-native` is now built on Ubuntu 22.04 instead of Ubuntu 20.04, which reached EOL in May 2025. If you are running you game on a server on Ubuntu 20.04, you should update the OS before upgrading to this SDK version. ([#2355](https://github.com/getsentry/sentry-unity/pull/2355))
88

9+
### Behavioural Changes
10+
11+
- The SDK no longer refreshes the trace ID when the app loses and regains focus. This means that the trace ID persists from game start to game end. The SDK now also automatically adds breadcrumbs on those lifecycle events. ([#2374](https://github.com/getsentry/sentry-unity/pull/2374))
12+
913
### Features
1014

1115
- The SDK no longer ends sessions as crashed when capturing unhandled or logged exceptions. Instead, sessions get correctly marked as `SessionEndStatus.Unhandled` ([#2376](https://github.com/getsentry/sentry-unity/pull/2376))
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using Sentry.Extensibility;
3+
using Sentry.Integrations;
4+
5+
namespace Sentry.Unity.Integrations;
6+
7+
internal class LifeCycleIntegration : ISdkIntegration
8+
{
9+
private IHub? _hub;
10+
private SentryUnityOptions _options = null!; // Set during register
11+
12+
private readonly SentryMonoBehaviour _sentryMonoBehaviour;
13+
private readonly IApplication _application;
14+
15+
public LifeCycleIntegration(SentryMonoBehaviour sentryMonoBehaviour, IApplication? application = null)
16+
{
17+
_application = application ?? ApplicationAdapter.Instance;
18+
_sentryMonoBehaviour = sentryMonoBehaviour;
19+
}
20+
21+
public void Register(IHub hub, SentryOptions sentryOptions)
22+
{
23+
_hub = hub;
24+
// This should never happen, but if it does...
25+
_options = sentryOptions as SentryUnityOptions ?? throw new ArgumentException("Options is not of type 'SentryUnityOptions'.");
26+
27+
if (!_options.AutoSessionTracking)
28+
{
29+
return;
30+
}
31+
32+
_sentryMonoBehaviour.ApplicationResuming += () =>
33+
{
34+
if (!hub.IsEnabled)
35+
{
36+
return;
37+
}
38+
39+
hub.AddBreadcrumb(message: "App regained focus.", category: "app.lifecycle");
40+
41+
_options.DiagnosticLogger?.LogDebug("Resuming session.");
42+
hub.ResumeSession();
43+
};
44+
_sentryMonoBehaviour.ApplicationPausing += () =>
45+
{
46+
if (!hub.IsEnabled)
47+
{
48+
return;
49+
}
50+
51+
hub.AddBreadcrumb(message: "App lost focus.", category: "app.lifecycle");
52+
53+
_options.DiagnosticLogger?.LogDebug("Pausing session.");
54+
hub.PauseSession();
55+
};
56+
57+
_application.Quitting += OnQuitting;
58+
}
59+
60+
private void OnQuitting()
61+
{
62+
// Platform-specific behavior notes:
63+
// - iOS: Applications are usually suspended and do not quit. If `Exit on Suspend` is enabled in Player Settings,
64+
// the application will be terminated on suspend instead of calling this method. In that case,
65+
// `OnApplicationPause` will be called instead.
66+
// - Windows Store Apps/Windows Phone 8.1: No application quit event exists. Use OnApplicationFocus instead.
67+
// - WebGL: OnApplicationQuit cannot be implemented due to browser tab closing behavior.
68+
69+
// Session handling on shutdown:
70+
// This method is invoked even when an uncaught exception occurs (including crashes in native layers).
71+
// We pause the session here rather than ending it to ensure the .NET SDK can properly detect crashes
72+
// on the next startup (via the CrashedLastRun callback). The session will then be closed with the
73+
// correct timestamp during initialization.
74+
if (_options.AutoSessionTracking)
75+
{
76+
_hub?.PauseSession();
77+
}
78+
79+
_hub?.FlushAsync(_options.ShutdownTimeout).GetAwaiter().GetResult();
80+
}
81+
}

src/Sentry.Unity/Integrations/SessionIntegration.cs

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/Sentry.Unity/Integrations/TraceGenerationIntegration.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ public void Register(IHub hub, SentryOptions options)
2727
return;
2828
}
2929

30-
_sentryMonoBehaviour.ApplicationResuming += () =>
31-
{
32-
options.DiagnosticLogger?.LogDebug("Game resuming. Creating new Trace.");
33-
hub.ConfigureScope(scope => scope.SetPropagationContext(new SentryPropagationContext()));
34-
};
35-
3630
var isTracingEnabled = unityOptions.TracesSampleRate > 0.0f;
3731

3832
// Create initial trace context if tracing is disabled or startup tracing is disabled

src/Sentry.Unity/Integrations/UnityLogHandlerIntegration.cs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,14 @@ namespace Sentry.Unity.Integrations;
1212
/// </summary>
1313
internal sealed class UnityLogHandlerIntegration : ISdkIntegration, ILogHandler
1414
{
15-
private readonly IApplication _application;
1615
private readonly Func<SentryStructuredLogger>? _loggerFactory;
1716
private IHub? _hub;
1817
private SentryUnityOptions _options = null!; // Set during register
1918
private ILogHandler _unityLogHandler = null!; // Set during register
2019
private SentryStructuredLogger _structuredLogger = null!; // Set during register
2120

22-
public UnityLogHandlerIntegration(IApplication? application = null)
23-
{
24-
_application = application ?? ApplicationAdapter.Instance;
25-
}
26-
2721
// For testing: allows injecting a custom logger factory
28-
internal UnityLogHandlerIntegration(IApplication? application, Func<SentryStructuredLogger> loggerFactory)
29-
: this(application)
22+
internal UnityLogHandlerIntegration(Func<SentryStructuredLogger>? loggerFactory = null)
3023
{
3124
_loggerFactory = loggerFactory;
3225
}
@@ -48,8 +41,6 @@ public void Register(IHub hub, SentryOptions sentryOptions)
4841

4942
_unityLogHandler = Debug.unityLogger.logHandler;
5043
Debug.unityLogger.logHandler = this;
51-
52-
_application.Quitting += OnQuitting;
5344
}
5445

5546
public void LogException(Exception exception, UnityEngine.Object context)
@@ -141,23 +132,4 @@ private void ProcessStructuredLog(LogType logType, string format, params object[
141132
break;
142133
}
143134
}
144-
145-
private void OnQuitting()
146-
{
147-
_options.DiagnosticLogger?.LogInfo("OnQuitting was invoked. Unhooking log callback and pausing session.");
148-
149-
// Note: iOS applications are usually suspended and do not quit. You should tick "Exit on Suspend" in Player settings for iOS builds to cause the game to quit and not suspend, otherwise you may not see this call.
150-
// If "Exit on Suspend" is not ticked then you will see calls to OnApplicationPause instead.
151-
// Note: On Windows Store Apps and Windows Phone 8.1 there is no application quit event. Consider using OnApplicationFocus event when focusStatus equals false.
152-
// Note: On WebGL it is not possible to implement OnApplicationQuit due to nature of the browser tabs closing.
153-
154-
// 'OnQuitting' is invoked even when an uncaught exception happens in the ART. To make sure the .NET
155-
// SDK checks with the native layer on restart if the previous run crashed (through the CrashedLastRun callback)
156-
// we'll just pause sessions on shutdown. On restart they can be closed with the right timestamp and as 'exited'.
157-
if (_options.AutoSessionTracking)
158-
{
159-
_hub?.PauseSession();
160-
}
161-
_hub?.FlushAsync(_options.ShutdownTimeout).GetAwaiter().GetResult();
162-
}
163135
}

src/Sentry.Unity/SentryUnityOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ internal SentryUnityOptions(IApplication? application = null,
358358
AddIntegration(new UnityBeforeSceneLoadIntegration());
359359
AddIntegration(new SceneManagerIntegration());
360360
AddIntegration(new SceneManagerTracingIntegration());
361-
AddIntegration(new SessionIntegration(behaviour));
361+
AddIntegration(new LifeCycleIntegration(behaviour));
362362
AddIntegration(new TraceGenerationIntegration(behaviour));
363363

364364
AddExceptionFilter(new UnityBadGatewayExceptionFilter());

test/Sentry.Unity.Tests/SessionIntegrationTests.cs renamed to test/Sentry.Unity.Tests/LifeCycleIntegrationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Sentry.Unity.Tests;
88

9-
public class SessionIntegrationTests
9+
public class LifeCycleIntegrationTests
1010
{
1111
[UnityTest]
1212
public IEnumerator SessionIntegration_Init_SentryMonoBehaviourCreated()

test/Sentry.Unity.Tests/TraceGenerationIntegrationTests.cs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,6 @@ public void Register_TracingEnabledAndAutoStartupTracesEnabled_DoesNotGenerateIn
6868
Assert.IsEmpty(_fixture.TestHub.ConfigureScopeCalls);
6969
}
7070

71-
[Test]
72-
public void ApplicationResuming_WhenCalled_GeneratesNewTrace()
73-
{
74-
// Arrange
75-
var sut = _fixture.GetSut();
76-
sut.Register(_fixture.TestHub, _fixture.SentryOptions);
77-
var initialCallsCount = _fixture.TestHub.ConfigureScopeCalls.Count;
78-
79-
// Act
80-
_fixture.SentryMonoBehaviour.ResumeApplication();
81-
82-
// Assert
83-
Assert.AreEqual(initialCallsCount + 1, _fixture.TestHub.ConfigureScopeCalls.Count);
84-
var configureScope = _fixture.TestHub.ConfigureScopeCalls.Last();
85-
var scope = new Scope(_fixture.SentryOptions);
86-
var initialPropagationContext = scope.PropagationContext;
87-
configureScope(scope);
88-
89-
Assert.AreNotEqual(initialPropagationContext, scope.PropagationContext);
90-
}
91-
9271
[TestCase(0.0f, false)]
9372
[TestCase(0.0f, true)]
9473
[TestCase(1.0f, false)]

test/Sentry.Unity.Tests/UnityLogHandlerIntegrationTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ private class Fixture
2121

2222
public UnityLogHandlerIntegration GetSut()
2323
{
24-
var application = new TestApplication();
2524
var integration = StructuredLogger != null
26-
? new UnityLogHandlerIntegration(application, () => StructuredLogger)
27-
: new UnityLogHandlerIntegration(application, () => DisabledSentryStructuredLogger.Instance);
25+
? new UnityLogHandlerIntegration(() => StructuredLogger)
26+
: new UnityLogHandlerIntegration(() => DisabledSentryStructuredLogger.Instance);
2827
integration.Register(Hub, SentryOptions);
2928
return integration;
3029
}

0 commit comments

Comments
 (0)