Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Behavioral Changes

- The SDK no longer relies on UnityEngine.Analytics.AnalyticsSessionInfo to determine unique users but uses SDK-internal mechanisms instead. ([#2625](https://github.com/getsentry/sentry-unity/pull/2625))

### Fixes

- When targeting iOS or macOS, the SDK now correctly passes on the `CaptureFailedRequests` flag and set status code ranged ([#2619](https://github.com/getsentry/sentry-unity/pull/2619))
Expand Down
26 changes: 7 additions & 19 deletions src/Sentry.Unity.Android/SentryNativeAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Sentry.Unity.Integrations;
using Sentry.Unity.NativeUtils;
using UnityEngine;
using UnityEngine.Analytics;

namespace Sentry.Unity.Android;

Expand Down Expand Up @@ -113,25 +112,14 @@ public static void Configure(SentryUnityOptions options)

Logger?.LogDebug("Fetching installation ID");

options.DefaultUserId = SentryJava.GetInstallationId();
if (string.IsNullOrEmpty(options.DefaultUserId))
var installationId = SentryJava.GetInstallationId();
if (!string.IsNullOrEmpty(installationId))
{
// In case we can't get an installation ID we create one and sync that down to the native layer
Logger?.LogDebug(
"Failed to fetch 'Installation ID' from the native SDK. Creating new 'Default User ID'.");

// We fall back to Unity's Analytics Session Info: https://docs.unity3d.com/ScriptReference/Analytics.AnalyticsSessionInfo-userId.html
// It's a randomly generated GUID that gets created immediately after installation helping
// to identify the same instance of the game
options.DefaultUserId = AnalyticsSessionInfo.userId;
if (options.DefaultUserId is not null)
{
options.ScopeObserver.SetUser(new SentryUser { Id = options.DefaultUserId });
}
else
{
Logger?.LogDebug("Failed to create new 'Default User ID'.");
}
options.DefaultUserId = installationId;
}
else
{
Logger?.LogDebug("Failed to fetch 'Installation ID' from the native SDK.");
}

Logger?.LogInfo("Successfully configured the Android SDK");
Expand Down
31 changes: 0 additions & 31 deletions src/Sentry.Unity.Native/SentryNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Sentry.Unity.Integrations;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Analytics;

namespace Sentry.Unity.Native;

Expand Down Expand Up @@ -67,12 +66,6 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf
options.NativeContextWriter = new NativeContextWriter();
options.NativeDebugImageProvider = new NativeDebugImageProvider();

options.DefaultUserId = GetInstallationId();
if (options.DefaultUserId is not null)
{
options.ScopeObserver.SetUser(new SentryUser { Id = options.DefaultUserId });
}

// Note: we must actually call the function now and on every other call use the value we get here.
// Additionally, we cannot call this multiple times for the same directory, because the result changes
// on subsequent runs. Therefore, we cache the value during the whole runtime of the application.
Expand Down Expand Up @@ -123,28 +116,4 @@ private static void ReinstallBackend()
Logger?.LogError(e, "Native dependency not found. Did you delete sentry.dll or move files around?");
}
}

private static string? GetInstallationId(IApplication? application = null)
{
application ??= ApplicationAdapter.Instance;
switch (application.Platform)
{
case RuntimePlatform.Switch:
case RuntimePlatform.PS5:
case RuntimePlatform.XboxOne:
case RuntimePlatform.GameCoreXboxSeries:
case RuntimePlatform.GameCoreXboxOne:
// TODO: Fetch the installation ID from sentry-native
// See https://github.com/getsentry/sentry-native/issues/1324
return null;

case RuntimePlatform.WindowsPlayer:
case RuntimePlatform.WindowsEditor:
case RuntimePlatform.LinuxPlayer:
case RuntimePlatform.LinuxEditor:
return AnalyticsSessionInfo.userId;
default:
return null;
}
}
}
25 changes: 7 additions & 18 deletions src/Sentry.Unity.iOS/SentryNativeCocoa.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Sentry.Extensibility;
using Sentry.Unity.Integrations;
using UnityEngine;
using UnityEngine.Analytics;

namespace Sentry.Unity.iOS;

Expand Down Expand Up @@ -72,24 +71,14 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf
options.NativeSupportCloseCallback += () => Close(options);
if (options.UnityInfo.IL2CPP)
{
options.DefaultUserId = SentryCocoaBridgeProxy.GetInstallationId();
if (string.IsNullOrEmpty(options.DefaultUserId))
var installationId = SentryCocoaBridgeProxy.GetInstallationId();
if (!string.IsNullOrEmpty(installationId))
{
// In case we can't get an installation ID we create one and sync that down to the native layer
Logger?.LogDebug("Failed to fetch 'Installation ID' from the native SDK. Creating new 'Default User ID'.");

// We fall back to Unity's Analytics Session Info: https://docs.unity3d.com/ScriptReference/Analytics.AnalyticsSessionInfo-userId.html
// It's a randomly generated GUID that gets created immediately after installation helping
// to identify the same instance of the game
options.DefaultUserId = AnalyticsSessionInfo.userId;
if (options.DefaultUserId is not null)
{
options.ScopeObserver.SetUser(new SentryUser { Id = options.DefaultUserId });
}
else
{
Logger?.LogDebug("Failed to create new 'Default User ID'.");
}
options.DefaultUserId = installationId;
}
else
{
Logger?.LogDebug("Failed to fetch 'Installation ID' from the native SDK.");
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/Sentry.Unity/Integrations/UnityScopeIntegration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,17 @@ private void PopulateUnity(Protocol.Unity unity)

private void PopulateUser(Scope scope)
{
if (scope.User.Id is not null)
{
return;
}

// Only set the native installation ID here. The .NET SDK's Enricher handles
// the fallback to InstallationId after the HasUser() check, which allows
// IsEnvironmentUser/SendDefaultPii to set the Username first.
if (_options.DefaultUserId is not null)
{
if (scope.User.Id is null)
{
scope.User.Id = _options.DefaultUserId;
}
scope.User.Id = _options.DefaultUserId;
}
}
}
4 changes: 0 additions & 4 deletions src/Sentry.Unity/WebGL/SentryWebGL.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Sentry.Extensibility;
using Sentry.Unity.Integrations;
using UnityEngine.Analytics;

namespace Sentry.Unity.WebGL;

Expand Down Expand Up @@ -46,9 +45,6 @@ public static void Configure(SentryUnityOptions options)
options.LogWarning("IL2CPP line number support is unsupported on WebGL - disabling.");
}

// Use AnalyticsSessionInfo.userId as the default UserID
options.DefaultUserId = AnalyticsSessionInfo.userId;

// Indicate that this platform doesn't support running background threads.
options.MultiThreading = false;
}
Expand Down
15 changes: 13 additions & 2 deletions test/Sentry.Unity.Android.Tests/SentryNativeAndroidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,25 @@ public void Configure_NativeAndroidSupportDisabled_DoesNotReInitializeNativeBack
}

[Test]
public void Configure_NoInstallationIdReturned_SetsNewDefaultUserId()
public void Configure_InstallationIdReturned_SetsDefaultUserId()
{
var options = new SentryUnityOptions();
_testSentryJava.InstallationId = "test-installation-id";

SentryNativeAndroid.Configure(options);

Assert.AreEqual("test-installation-id", options.DefaultUserId);
}

[Test]
public void Configure_NoInstallationIdReturned_DoesNotSetDefaultUserId()
{
var options = new SentryUnityOptions();
_testSentryJava.InstallationId = string.Empty;

SentryNativeAndroid.Configure(options);

Assert.False(string.IsNullOrEmpty(options.DefaultUserId));
Assert.IsNull(options.DefaultUserId);
}

[Test]
Expand Down
3 changes: 3 additions & 0 deletions test/Sentry.Unity.Tests/ContextWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ public void Arguments()
Debug = true,
DiagnosticLogger = new TestLogger(),
NativeContextWriter = context,
CreateHttpMessageHandler = () => new TestHttpClientHandler(),
AutoSessionTracking = false,
CacheDirectoryPath = null,
};

// act
Expand Down
77 changes: 77 additions & 0 deletions test/Sentry.Unity.Tests/UnityEventScopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,65 @@ public void UserId_UnchangedIfNonEmpty()
Assert.AreEqual(scope.User.Id, "bar");
}

[Test]
public void UserId_DefaultUserIdIsSet()
{
// arrange
var options = new SentryUnityOptions(application: _testApplication) { DefaultUserId = "native-id" };

var sut = new UnityScopeUpdater(options, _testApplication);
var scope = new Scope(options);

// act
sut.ConfigureScope(scope);

// assert
Assert.AreEqual("native-id", scope.User.Id);
}

[Test]
public void UserId_ScopeSync_TriggeredWhenUserIdSet()
{
// arrange - enable scope sync with a tracking observer
var options = new SentryUnityOptions(application: _testApplication) { DefaultUserId = "sync-test-id" };
var observer = new TestScopeObserver(options);
options.ScopeObserver = observer;
options.EnableScopeSync = true;

var sut = new UnityScopeUpdater(options, _testApplication);
var scope = new Scope(options);

// act
sut.ConfigureScope(scope);

// assert - the observer should have received the SetUser call via PropertyChanged
Assert.IsNotNull(observer.LastUser, "ScopeObserver.SetUser should have been called");
Assert.AreEqual("sync-test-id", observer.LastUser!.Id);
}

[Test]
public void UserId_ScopeSync_NotTriggeredWhenUserAlreadySet()
{
// arrange - User.Id already set, PopulateUser should early-return
var options = new SentryUnityOptions(application: _testApplication) { DefaultUserId = "should-not-sync" };
var observer = new TestScopeObserver(options);
options.ScopeObserver = observer;
options.EnableScopeSync = true;

var sut = new UnityScopeUpdater(options, _testApplication);
var scope = new Scope(options);
scope.User.Id = "already-set";

// Reset observer after the initial scope.User.Id set above triggered it
observer.LastUser = null;

// act
sut.ConfigureScope(scope);

// assert - PopulateUser should not have set a new user
Assert.IsNull(observer.LastUser, "ScopeObserver.SetUser should not be called when User.Id is already set");
}

[Test]
public void OperatingSystemProtocol_Assigned()
{
Expand Down Expand Up @@ -600,3 +659,21 @@ internal sealed class TestSentrySystemInfo : ISentrySystemInfo
public Lazy<string>? RenderingThreadingMode { get; set; }
public Lazy<DateTimeOffset>? StartTime { get; set; }
}

internal sealed class TestScopeObserver : ScopeObserver
{
public SentryUser? LastUser { get; set; }

public TestScopeObserver(SentryOptions options) : base("Test", options) { }

public override void AddBreadcrumbImpl(Breadcrumb breadcrumb) { }
public override void SetExtraImpl(string key, string? value) { }
public override void SetTagImpl(string key, string value) { }
public override void UnsetTagImpl(string key) { }
public override void SetUserImpl(SentryUser user) => LastUser = user;
public override void UnsetUserImpl() => LastUser = null;
public override void SetTraceImpl(SentryId traceId, SpanId spanId) { }
public override void AddFileAttachmentImpl(string filePath, string fileName, string? contentType) { }
public override void AddByteAttachmentImpl(byte[] data, string fileName, string? contentType) { }
public override void ClearAttachmentsImpl() { }
}
Loading