Skip to content

feat: support for unity as a library on iOS - #2885

Merged
bitsandfoxes merged 29 commits into
mainfrom
feat/uaal
Oct 29, 2026
Merged

bitsandfoxes merged 29 commits into
mainfrom
feat/uaal

Conversation

@bitsandfoxes

@bitsandfoxes bitsandfoxes commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Adds experimental support for Unity as a Library on iOS: the game runs inside a native app that already uses Sentry, and attaches to that app's sentry-cocoa instead of bundling and starting its own. Part of #2416.

Until now the export always bundled SentryObjC.xcframework and started it from the game. Inside an app that ships its own sentry-cocoa (SPM), that gives two SDKs that fight over the crash handler and the scope, and the game's events come in with the game's release and their own trace.

Setup:

  • The app starts sentry-cocoa before it loads Unity, as it already does.
  • The game sets options.IosNativeInitializationType = NativeInitializationType.External in its options configuration. Using the app's DSN keeps both in one project, so crashes symbolicate with the symbols of both.
  • The app builds the exported UnityFramework target, which uploads the game's debug symbols (dSYM, IL2CPP line mappings) when symbol upload is configured.

How it works:

  • External never starts or closes sentry-cocoa. If the app hasn't started it, the game logs a warning and reports C# events only.
  • The export ships no Sentry framework and doesn't touch main.mm. It copies a separate bridge, SentryNativeBridgeExternal.m, that looks up the core classes of the app's SDK (SentrySDKInternal, SentryScope, SentryBreadcrumb) at runtime and checks every selector with respondsToSelector:, so a missing one is skipped. Regular exports keep the existing bridge: it compiles against the bundled SentryObjC headers, which an app on plain Sentry doesn't have, and a shared bridge would have lost that compile-time checking for everyone.
  • Breadcrumbs, tags, extras and the unity/gpu contexts go to the app's scope. User, environment and trace stay the app's: the game doesn't overwrite them. Sessions and app hangs are left to the app (C# session tracking and the ANR watchdog are off).
  • C# events and logs use the app's release and environment unless the game sets its own (in the options, or through the Editor's override fields, even when that value matches the default), and the app's user unless the game sets its own: events read it on every capture, logs at start and whenever the game resumes (NativeAppUser). They continue the app's trace through sentry-dotnet's ExternalPropagationContext (NativePropagationContext, read on every use). When the app has an active span, events and outgoing requests carry its baggage. The game's own transactions (startup, scene loads, SentrySdk.StartTransaction(name, op)) become children of the app's active span and inherit its sampling decision; without one they're roots in the app's trace and the game samples.
  • Application.Unload keeps the C# SDK alive; reloading doesn't re-init. The unity context stays on the app's scope after unloading.
  • The symbol upload phase moves to UnityFramework, with the dSYM declared as an input so it exists when the upload runs. An Append export that switches to External fails and asks for Replace instead of keeping SentryObjC linked.

Verified on an iPhone (iOS 27.0.1) at f334634, with SentryNativeBridgeExternal.m, in a native host app on SPM sentry-cocoa 9.29.0 embedding unity-of-bugs (Unity 6000.6.6f1, IL2CPP):

Scenario Result
App event before Unity loads app release, environment, user, trace
C# message, Debug.LogError, caught and unhandled exception app release, environment, user and trace, C# lines symbolicated
C# event while an app transaction is active in that transaction's trace, with its baggage (sample_rand, transaction)
Logs from app and game same trace, app release and environment
App changes its user while the game runs, game sets its own user (also with SendDefaultPii) C# events follow the app's user right away, logs after the game resumes, the game's user wins once set
Game startup transaction child of the app's ui.load transaction
Game breadcrumbs, unity context on the app's scope; the app's user stays the app's after the game sets its own
Installation ID C# default user ID is the app's installation ID
Unload, app event, reload, C# exception no re-init, same trace
Native crash in the game, also after unload and reload caught by the app's SDK, symbolicated down to the C# line (ForceCrashButtons.cs:6) and the app's frames, unity and gpu contexts, app release, environment, user and trace

The app event before Unity loads, the unhandled exception and SendDefaultPii are from the run on the earlier bridge; none of them goes through the bridge.

Known limits and follow-ups:

  • A user change in the app reaches C# logs only when the game resumes (events get it right away).
  • Only id, username, email and IP address of the app's user carry over; its name, geo and custom data don't.
  • Without an active app span, outgoing C# requests send sentry-trace: …-0 instead of no decision: sentry-dotnet's IExternalPropagationContext.IsSampled is a bool. Proposed upstream in fix: transactions continue the external propagation context's trace sentry-dotnet#5825 (bool?), then pass it through.
  • Only SentrySdk.StartTransaction(name, operation[, description]) joins the app's trace. Transactions started with a TransactionContext or through Sentry.SentrySdk/IHub start their own. fix: transactions continue the external propagation context's trace sentry-dotnet#5825 proposes doing this in the hub, which would replace the workaround.
  • The bridge relies on sentry-cocoa internals (SentrySDKInternal, SentryScope.propagationContextTraceHeader, SentryInstallation). If one of them changes, only the feature that uses it is skipped; the rest keeps working. Needs a stable hybrid entry point in sentry-cocoa, also for 10.x.
  • Android External comes with feat: support for unity as a library on Android #2895 and has to land with this one: on its own, AndroidNativeInitializationType = External behaves like BuildTime.
  • Docs: no user-facing page yet.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 61cd0a6

@bitsandfoxes bitsandfoxes changed the title feat: support for unity as a library feat: support for unity as a library on iOS Oct 21, 2026
Comment on lines +495 to +499
internal string? DefaultRelease { get; }
internal string DefaultEnvironment { get; }
// Set through the Editor's overrides, which may match the defaults.
internal bool ReleaseOverridden { get; set; }
internal bool EnvironmentOverridden { get; set; }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is an override option in the editor configuration window. And until now the SDK did not need to care where the values were coming from, project settings, overrides, didn't matter as long as we had values. Now that the game wants to take the values from the host we need to check whether these have been explicitly set. Will be picked up with #2914

[DebuggerStepThrough]
public static ITransactionTracer StartTransaction(string name, string operation)
=> Sentry.SentrySdk.StartTransaction(name, operation);
=> NativePropagationContext.StartTransaction(Sentry.SentrySdk.CurrentHub, name, operation);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a workaround for getsentry/sentry-dotnet#5825
The .NET SDK did not expect the ExternalPropagationContext to be used in conjunction with Sentry tracing.

@bitsandfoxes
bitsandfoxes marked this pull request as ready for review October 28, 2026 11:07
@bitsandfoxes
bitsandfoxes requested review from a team, jpnurmi, limbonaut and tustanivsky October 28, 2026 11:08

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6421df2. Configure here.

@bitsandfoxes
bitsandfoxes requested a review from mujacica October 29, 2026 12:50

@jpnurmi jpnurmi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A couple of potential findings. Feel free to reject if they're 💩

@bitsandfoxes
bitsandfoxes merged commit 20a042e into main Oct 29, 2026
127 of 129 checks passed
@bitsandfoxes
bitsandfoxes deleted the feat/uaal branch October 29, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants