Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e75f147
Unseal
bitsandfoxes Oct 22, 2025
7f88a98
Added structured logging capture to
bitsandfoxes Oct 22, 2025
e2ae095
Updated sample LogError message
bitsandfoxes Oct 22, 2025
3da5fc9
Opt-in check for breadcrumb collection
bitsandfoxes Oct 22, 2025
7834bc0
Added logging
bitsandfoxes Oct 23, 2025
34823dc
Options
bitsandfoxes Oct 23, 2025
73eb7ed
Wrapup for Logging
bitsandfoxes Oct 24, 2025
ee9dfc0
Fixed debounce logic
bitsandfoxes Oct 24, 2025
dc8a638
Interation on working base
bitsandfoxes Oct 24, 2025
90b12a8
Added beforesend
bitsandfoxes Oct 24, 2025
ddc9605
Added logger to Unity SDK static API
bitsandfoxes Oct 24, 2025
e62733c
Updated CHANGELOG.md
bitsandfoxes Oct 24, 2025
7f9950d
Don't access internal structured logger
bitsandfoxes Oct 24, 2025
5facd76
Missed enable check and test
bitsandfoxes Oct 24, 2025
5eaec84
Bump global.json
bitsandfoxes Oct 24, 2025
590cf2e
Renamed scriptable options
bitsandfoxes Oct 24, 2025
3218a7f
Bump underlying version6
bitsandfoxes Oct 24, 2025
83f8538
Merge branch 'feat/bump-version6' into feat/structured-logging
bitsandfoxes Oct 24, 2025
5e48eea
Merge branch 'feat/bump-version6' into feat/structured-logging
bitsandfoxes Oct 24, 2025
6914ca9
Merge branch 'feat/bump-version6' into feat/structured-logging
bitsandfoxes Oct 24, 2025
fe9d609
Merge branch 'feat/bump-version6' into feat/structured-logging
bitsandfoxes Oct 28, 2025
4d11606
Merge branch 'feat/bump-version6' into feat/structured-logging
bitsandfoxes Oct 28, 2025
4dd1fd2
Unified life cycle things in LifeCycleIntegration
bitsandfoxes Oct 28, 2025
78c80ad
Use the logger on the hub
bitsandfoxes Oct 28, 2025
7493a59
Logmessage
bitsandfoxes Oct 28, 2025
8c3f129
Cleanup
bitsandfoxes Oct 28, 2025
6106033
Fixed no-logger-fallback
bitsandfoxes Oct 29, 2025
4f44a3b
Merged Structured Logging
bitsandfoxes Oct 29, 2025
97dbe03
Removed now obsolete test
bitsandfoxes Oct 29, 2025
16852f0
Format code
getsentry-bot Oct 29, 2025
faffaf8
Updated CHANGELOG.md
bitsandfoxes Oct 29, 2025
0534886
Merge branch 'feat/structured-logging-followup1' of https://github.co…
bitsandfoxes Oct 29, 2025
026fb26
Merged feat/bump-version6
bitsandfoxes Oct 30, 2025
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- `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))

### Behavioural Changes

- 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))

### Features

- Added support for Structured Logging. The `SentrySdk.Logger` API is now exposed for Unity users, enabling structured log capture. The SDK can also automatically capture and send Debug logs based on the options configured. ([#2368](https://github.com/getsentry/sentry-unity/pull/2368))
Expand Down
81 changes: 81 additions & 0 deletions src/Sentry.Unity/Integrations/LifeCycleIntegration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using Sentry.Extensibility;
using Sentry.Integrations;

namespace Sentry.Unity.Integrations;

internal class LifeCycleIntegration : ISdkIntegration
{
private IHub? _hub;
private SentryUnityOptions _options = null!; // Set during register

private readonly SentryMonoBehaviour _sentryMonoBehaviour;
private readonly IApplication _application;

public LifeCycleIntegration(SentryMonoBehaviour sentryMonoBehaviour, IApplication? application = null)
{
_application = application ?? ApplicationAdapter.Instance;
_sentryMonoBehaviour = sentryMonoBehaviour;
}

public void Register(IHub hub, SentryOptions sentryOptions)
{
_hub = hub;
// This should never happen, but if it does...
_options = sentryOptions as SentryUnityOptions ?? throw new ArgumentException("Options is not of type 'SentryUnityOptions'.");

if (!_options.AutoSessionTracking)
{
return;
}
Copy link

Choose a reason for hiding this comment

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

Bug: Session Tracking Affects Event Flushing

The OnQuitting event handler, which flushes pending events, is only registered when AutoSessionTracking is enabled. This prevents FlushAsync from being called on application shutdown if session tracking is disabled, potentially causing event loss.

Fix in Cursor Fix in Web


_sentryMonoBehaviour.ApplicationResuming += () =>
{
if (!hub.IsEnabled)
{
return;
}

hub.AddBreadcrumb(message: "App regained focus.", category: "app.lifecycle");

_options.DiagnosticLogger?.LogDebug("Resuming session.");
hub.ResumeSession();
};
_sentryMonoBehaviour.ApplicationPausing += () =>
{
if (!hub.IsEnabled)
{
return;
}

hub.AddBreadcrumb(message: "App lost focus.", category: "app.lifecycle");

_options.DiagnosticLogger?.LogDebug("Pausing session.");
hub.PauseSession();
};

_application.Quitting += OnQuitting;
}

private void OnQuitting()
{
// Platform-specific behavior notes:
// - iOS: Applications are usually suspended and do not quit. If `Exit on Suspend` is enabled in Player Settings,
// the application will be terminated on suspend instead of calling this method. In that case,
// `OnApplicationPause` will be called instead.
// - Windows Store Apps/Windows Phone 8.1: No application quit event exists. Use OnApplicationFocus instead.
// - WebGL: OnApplicationQuit cannot be implemented due to browser tab closing behavior.

// Session handling on shutdown:
// This method is invoked even when an uncaught exception occurs (including crashes in native layers).
// We pause the session here rather than ending it to ensure the .NET SDK can properly detect crashes
// on the next startup (via the CrashedLastRun callback). The session will then be closed with the
// correct timestamp during initialization.
if (_options.AutoSessionTracking)
{
_hub?.PauseSession();
}

_hub?.FlushAsync(_options.ShutdownTimeout).GetAwaiter().GetResult();
}
}
33 changes: 0 additions & 33 deletions src/Sentry.Unity/Integrations/SessionIntegration.cs

This file was deleted.

6 changes: 0 additions & 6 deletions src/Sentry.Unity/Integrations/TraceGenerationIntegration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ public void Register(IHub hub, SentryOptions options)
return;
}

_sentryMonoBehaviour.ApplicationResuming += () =>
{
options.DiagnosticLogger?.LogDebug("Game resuming. Creating new Trace.");
hub.ConfigureScope(scope => scope.SetPropagationContext(new SentryPropagationContext()));
};

var isTracingEnabled = unityOptions.TracesSampleRate > 0.0f;

// Create initial trace context if tracing is disabled or startup tracing is disabled
Expand Down
30 changes: 1 addition & 29 deletions src/Sentry.Unity/Integrations/UnityLogHandlerIntegration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,14 @@ namespace Sentry.Unity.Integrations;
/// </summary>
internal sealed class UnityLogHandlerIntegration : ISdkIntegration, ILogHandler
{
private readonly IApplication _application;
private readonly Func<SentryStructuredLogger>? _loggerFactory;
private IHub? _hub;
private SentryUnityOptions _options = null!; // Set during register
private ILogHandler _unityLogHandler = null!; // Set during register
private SentryStructuredLogger _structuredLogger = null!; // Set during register

public UnityLogHandlerIntegration(IApplication? application = null)
{
_application = application ?? ApplicationAdapter.Instance;
}

// For testing: allows injecting a custom logger factory
internal UnityLogHandlerIntegration(IApplication? application, Func<SentryStructuredLogger> loggerFactory)
: this(application)
internal UnityLogHandlerIntegration(Func<SentryStructuredLogger>? loggerFactory = null)
{
_loggerFactory = loggerFactory;
}
Expand All @@ -48,8 +41,6 @@ public void Register(IHub hub, SentryOptions sentryOptions)

_unityLogHandler = Debug.unityLogger.logHandler;
Debug.unityLogger.logHandler = this;

_application.Quitting += OnQuitting;
}

public void LogException(Exception exception, UnityEngine.Object context)
Expand Down Expand Up @@ -142,23 +133,4 @@ private void ProcessStructuredLog(LogType logType, string format, params object[
break;
}
}

private void OnQuitting()
{
_options.DiagnosticLogger?.LogInfo("OnQuitting was invoked. Unhooking log callback and pausing session.");

// 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.
// If "Exit on Suspend" is not ticked then you will see calls to OnApplicationPause instead.
// Note: On Windows Store Apps and Windows Phone 8.1 there is no application quit event. Consider using OnApplicationFocus event when focusStatus equals false.
// Note: On WebGL it is not possible to implement OnApplicationQuit due to nature of the browser tabs closing.

// 'OnQuitting' is invoked even when an uncaught exception happens in the ART. To make sure the .NET
// SDK checks with the native layer on restart if the previous run crashed (through the CrashedLastRun callback)
// we'll just pause sessions on shutdown. On restart they can be closed with the right timestamp and as 'exited'.
if (_options.AutoSessionTracking)
{
_hub?.PauseSession();
}
_hub?.FlushAsync(_options.ShutdownTimeout).GetAwaiter().GetResult();
}
}
2 changes: 1 addition & 1 deletion src/Sentry.Unity/SentryUnityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ internal SentryUnityOptions(IApplication? application = null,
AddIntegration(new UnityBeforeSceneLoadIntegration());
AddIntegration(new SceneManagerIntegration());
AddIntegration(new SceneManagerTracingIntegration());
AddIntegration(new SessionIntegration(behaviour));
AddIntegration(new LifeCycleIntegration(behaviour));
AddIntegration(new TraceGenerationIntegration(behaviour));

AddExceptionFilter(new UnityBadGatewayExceptionFilter());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Sentry.Unity.Tests;

public class SessionIntegrationTests
public class LifeCycleIntegrationTests
{
[UnityTest]
public IEnumerator SessionIntegration_Init_SentryMonoBehaviourCreated()
Expand Down
21 changes: 0 additions & 21 deletions test/Sentry.Unity.Tests/TraceGenerationIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,6 @@ public void Register_TracingEnabledAndAutoStartupTracesEnabled_DoesNotGenerateIn
Assert.IsEmpty(_fixture.TestHub.ConfigureScopeCalls);
}

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

// Act
_fixture.SentryMonoBehaviour.ResumeApplication();

// Assert
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);
}

[TestCase(0.0f, false)]
[TestCase(0.0f, true)]
[TestCase(1.0f, false)]
Expand Down
5 changes: 2 additions & 3 deletions test/Sentry.Unity.Tests/UnityLogHandlerIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ private class Fixture

public UnityLogHandlerIntegration GetSut()
{
var application = new TestApplication();
var integration = StructuredLogger != null
? new UnityLogHandlerIntegration(application, () => StructuredLogger)
: new UnityLogHandlerIntegration(application, () => DisabledSentryStructuredLogger.Instance);
? new UnityLogHandlerIntegration(() => StructuredLogger)
: new UnityLogHandlerIntegration(() => DisabledSentryStructuredLogger.Instance);
integration.Register(Hub, SentryOptions);
return integration;
}
Expand Down
Loading