From efd5005f873e701c6a44dc96f01653078d1c3645 Mon Sep 17 00:00:00 2001 From: Natalie Bunduwongse Date: Tue, 29 Sep 2026 12:12:14 +1300 Subject: [PATCH] feat(audience): add opt-in exception capture Adds ErrorTrackingConfig.CaptureExceptions, off by default. When set, the SDK listens for uncaught C# exceptions via Unity's logMessageReceivedThreaded and sends them as an exception_captured event through the existing Track pipeline. Rate limited per exception type via a token bucket (burst 20, refills 1 every 10s), not one shared counter, so a repeating exception can't flood the queue or crowd out a different, unrelated one. Matches how PostHog's client-side exception rate limiter is designed. Adds a Capture Exceptions toggle and a Throw Test Exception button to the sample app's Setup tab, plus PlayMode E2E coverage confirming the event queues when enabled and doesn't when disabled. No native crash capture (segfaults, ANRs). Managed exceptions only. Co-Authored-By: Claude Sonnet 5 --- .../SampleApp/Resources/AudienceSample.uxml | 9 ++ .../SampleApp/Scripts/AudienceSample.UI.cs | 25 +++- .../SampleApp/Scripts/AudienceSample.cs | 12 ++ .../Tests/Runtime/SampleAppLiveFireTests.cs | 44 ++++++ .../SampleApp/Tests/Runtime/SampleAppUi.cs | 3 + .../Audience/Runtime/AudienceConfig.cs | 6 + .../Audience/Runtime/Core/Constants.cs | 3 + .../Runtime/Core/ExceptionRateLimiter.cs | 95 +++++++++++++ .../Runtime/Core/ExceptionRateLimiter.cs.meta | 11 ++ .../Audience/Runtime/ErrorTrackingConfig.cs | 22 +++ .../Runtime/ErrorTrackingConfig.cs.meta | 11 ++ .../Audience/Runtime/Events/ReservedEvents.cs | 1 + .../Audience/Runtime/ImmutableAudience.cs | 28 ++++ .../Runtime/Unity/AudienceUnityHooks.cs | 17 +++ src/Packages/Audience/Runtime/Utility/Log.cs | 6 + .../Runtime/Core/ExceptionRateLimiterTests.cs | 106 ++++++++++++++ .../Core/ExceptionRateLimiterTests.cs.meta | 11 ++ .../Tests/Runtime/ImmutableAudienceTests.cs | 133 ++++++++++++++++++ 18 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 src/Packages/Audience/Runtime/Core/ExceptionRateLimiter.cs create mode 100644 src/Packages/Audience/Runtime/Core/ExceptionRateLimiter.cs.meta create mode 100644 src/Packages/Audience/Runtime/ErrorTrackingConfig.cs create mode 100644 src/Packages/Audience/Runtime/ErrorTrackingConfig.cs.meta create mode 100644 src/Packages/Audience/Tests/Runtime/Core/ExceptionRateLimiterTests.cs create mode 100644 src/Packages/Audience/Tests/Runtime/Core/ExceptionRateLimiterTests.cs.meta diff --git a/examples/audience/Assets/SampleApp/Resources/AudienceSample.uxml b/examples/audience/Assets/SampleApp/Resources/AudienceSample.uxml index 3c7182b9..e80f7fe5 100644 --- a/examples/audience/Assets/SampleApp/Resources/AudienceSample.uxml +++ b/examples/audience/Assets/SampleApp/Resources/AudienceSample.uxml @@ -102,6 +102,15 @@ text="Tags all outbound events with test: true so the backend can filter them from production analytics." /> + + + + + + + + ("debug"); _testMode = Require("test-mode"); _enableMobileAttribution = Require("enable-mobile-attribution"); + _captureExceptions = Require("capture-exceptions"); // Inject a tick Label — Unity 2021.3 runtime panels render the // checked state as a plain coloured square otherwise. USS hides // the tick when unchecked. - foreach (var toggle in new[] { _debug, _testMode, _enableMobileAttribution }) + foreach (var toggle in new[] { _debug, _testMode, _enableMobileAttribution, _captureExceptions }) { var checkmark = toggle.Q(className: "unity-toggle__checkmark"); if (checkmark == null) continue; @@ -218,6 +219,7 @@ private void BindElements() _customEventName = Require("custom-event-name"); _customEventProps = Require("custom-event-props"); _btnCustomEvent = Require