Skip to content

feature: add error reporting integration - #16

Open
ivnbogdan wants to merge 3 commits into
mainfrom
feat/error-reporting
Open

feature: add error reporting integration#16
ivnbogdan wants to merge 3 commits into
mainfrom
feat/error-reporting

Conversation

@ivnbogdan

@ivnbogdan ivnbogdan commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Integrates the Unity SDK with the error reporting feature added to the server in aptabase/aptabase#183, following the pattern of aptabase/aptabase-maui#15 and the React Native integration (aptabase/aptabase-react-native@94d7cf6). Rebased on top of #14 (per-URL WebRequestHelper, Task-returning Flush(CancellationToken), SetEnabled/SetResponseListener).

Public API

Aptabase.TrackError(ex);              // severity "error", kind "handled"
Aptabase.TrackError(ex, fatal: true); // severity "fatal", kind "crash"

Plus an opt-in EnableCrashReporting toggle on the AptabaseSettings asset (off by default, like the other SDKs). Aptabase.Flush() now flushes error reports too, and SetEnabled(false) drops them.

What's included

  • ErrorReport — the POST /api/v0/error body, built from an Exception or from a Unity LogType.Exception log entry ("Type: message" parsed, namespaced types shortened to match GetType().Name). Session/system context is stamped at capture time and every field is truncated to the server's limits so a report is never rejected with 400.
  • ErrorDispatcher — separate from the events dispatcher because the endpoint takes one report per request (so WebGL and the other platforms share it) and has different retry semantics: retry on connection error/408/429/5xx, drop on 403 (monthly quota exhausted, per the server's deliberate 403-not-429 choice) and other 4xx. Flush-on-enqueue since errors are high-value; failures ride along with the regular flushes. Queue capped at 25. Honors the CancellationToken like the events dispatcher.
  • CrashReporterApplication.logMessageReceivedThreaded (LogType.Exception only → unhandled/error), AppDomain.UnhandledException (crash/fatal when terminating, best-effort delivery), TaskScheduler.UnobservedTaskException (taskException). Re-entrancy guarded, registered once, unregistered on Application.quitting so domain-reload-off in the Editor doesn't stack handlers.
  • WebRequestHelper.CreateAndSendWebRequestWithResultAsync — same pipeline as the events path but returns the status code/response body so the error dispatcher can decide whether to retry. The events path is now built on it (same logging and response-listener behaviour as before).
  • Docs: README "Error Tracking" section, llms.txt, CHANGELOG; version 0.3.0 (Version.cs was drifted at 0.2.3 vs package.json).

Unity-specific decisions (where this diverges from MAUI/RN)

  • Unity swallows managed exceptions, so the primary hook is the log callback and those reports are unhandled/error, not fatal. Only AppDomain.UnhandledException with IsTerminating produces crash/fatal. Native crashes are not captured.
  • Dedupe per session. A throwing Update() fires 60×/s, which would hit the server's 20 req/s per-IP limit immediately and burn the monthly quota. Only the first occurrence of each unique (kind, type, message, stack) is reported per session, max 100 unique errors per session.
  • Threading. Crash hooks can fire off the main thread while UnityWebRequest is main-thread only, so the dispatcher queue is locked and off-thread enqueues post their flush to the captured SynchronizationContext. Session ids now use System.Random for the same reason.
  • WebGL osName. The events endpoint treats an empty osName as "web" and parses the browser User-Agent; the error endpoint doesn't, so error reports on WebGL send osName: "web" (same as React Native) with the browser-reported OS string in osVersion. Events are unchanged.
  • Debug.LogError / assertions are not reported (no exception type, too noisy). No disk persistence in this version (same as RN; MAUI has it). SetResponseListener stays events-only.

Also fixed along the way

  • Aptabase.Flush() threw a NullReferenceException when the SDK failed to initialize.
  • When the CancellationToken fires mid-request, the UnityWebRequest is now aborted and disposed (previously it kept running and the batch was re-sent later, so the server could receive it twice).

Also included

  • README samples now show using AptabaseSDK; and call out the namespace. Closes Add "using AptabaseSDK;" to documentation. #8.
  • The DEV region host now points to https://localhost:3000 like RN/MAUI. HTTPS requests whose parsed host is a loopback address (localhost, 127.0.0.1, ::1) attach a CertificateHandler that accepts the self-signed dev certificate (mirrors MAUI's LocalHttpsClientHandler, but matched on the parsed host so https://localhost.example.com can't disable verification); production hosts and WebGL are unaffected.

Verification

Unity 6000.5.9f1 (batch mode, package referenced from disk, against a local mock Aptabase server over plain HTTP and self-signed HTTPS):

  • Standalone and WebGL-target script compilation: clean, no warnings from the package.
  • EditMode, 13/13 with the real UnityWebRequest pipeline: structured report (headers, every body field, timestamp), fatal reports carry the stack trace, Debug.LogException reported once as unhandled (same call site ×3 → 1 request), Debug.LogError/assertions ignored, 403/400 dropped and never retried, 429/500 retried on the next Flush(), self-signed loopback HTTPS accepted by the CertificateHandler, events still batch to /api/v0/events, SetEnabled(false) drops reports, Flush() on an uninitialized SDK doesn't throw, TrackError from a worker thread is delivered.
  • PlayMode, 3/3 through the real [RuntimeInitializeOnLoadMethod(BeforeSceneLoad)] init: SynchronizationContext.Current is Unity's main-thread context at that point (so off-thread crash hooks can post their flush), a 5×-logged exception is reported once via the runtime log hook, and a report from a raw Thread is delivered.

Running in Unity surfaced one real bug that the .NET stub harness had missed (fixed, folded into the error reporting commit): a Flush() issued while a flush was in flight was silently skipped, so awaiting it didn't guarantee that re-queued reports (after a 429) were retried. Error flushes are now chained like in the React Native SDK, and only the first report of a batch kicks an automatic flush.

Also still green: the .NET 10 stub harness (Runtime sources compiled against stub UnityEngine types, payloads validated with the server's ErrorBody.cs), 62 checks including cancellation abort/dispose and the loopback trust-rule bypass cases.

Not covered locally: on-device Android/iOS behaviour and IL2CPP stack-trace quality, and the exact SystemInfo.operatingSystem string a WebGL build reports (only affects the osVersion shown for WebGL error reports).

🤖 Generated with Claude Code

https://claude.ai/code/session_01YWERdp5jK3YRMkkWM4RLcE

@ivnbogdan
ivnbogdan force-pushed the feat/error-reporting branch from 5d6eac8 to 84f763e Compare August 25, 2026 03:58
Integrates the Unity SDK with the error reporting feature added to the
server in aptabase/aptabase#183, following the pattern used by the MAUI
and React Native SDKs.

- Add Aptabase.TrackError(Exception, fatal) posting structured error
  reports (type, message, stack trace, severity, kind) to /api/v0/error
- Add optional automatic crash reporting via EnableCrashReporting:
  exceptions logged by Unity (unhandled), process-terminating exceptions
  (crash) and unobserved Task exceptions (taskException)
- Dedicated ErrorDispatcher shared by all platforms: flush on enqueue,
  retry on connection error/408/429/5xx, drop on 403 (quota) and other 4xx
- Report only the first occurrence of each unique error per session (max
  100), since Unity keeps running after an exception and a throwing
  Update() would otherwise flood the error quota
- Send osName "web" on WebGL error reports (the error endpoint does not
  infer the OS from the User-Agent like the events endpoint does)
- Fix stray $ characters in the User-Agent header
- Fix Aptabase.Flush() throwing when the SDK failed to initialize
- Bump to 0.3.0 and align Version.cs with package.json

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YWERdp5jK3YRMkkWM4RLcE
@ivnbogdan
ivnbogdan force-pushed the feat/error-reporting branch from 3664aad to de6acc4 Compare August 25, 2026 04:29
ivnbogdan and others added 2 commits August 25, 2026 07:30
The samples never showed a using directive, so the namespace was only
discoverable by reading the source.

Closes #8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YWERdp5jK3YRMkkWM4RLcE
Matches the local backend's HTTPS endpoint and the MAUI/React Native
SDKs. HTTPS requests whose parsed host is a loopback address get a
CertificateHandler that accepts the self-signed development
certificate; production hosts and WebGL (where the browser validates
TLS) are unaffected. The rule is matched on the parsed host rather than
a string prefix so that e.g. https://localhost.example.com never has
verification disabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YWERdp5jK3YRMkkWM4RLcE
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.

Add "using AptabaseSDK;" to documentation.

1 participant