Skip to content

feat: pass sessionOnError and sessionReplayOnError to the native SDKs - #21

Draft
Fiona2016 wants to merge 6 commits into
publishfrom
feat/session-on-error
Draft

Fiona2016 wants to merge 6 commits into
publishfrom
feat/session-on-error

Conversation

@Fiona2016

@Fiona2016 Fiona2016 commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

Draft — blocked on the native releases. The Android bridge calls RumConfiguration.Builder.setSessionOnError, which does not exist in the pinned native 0.5.0, so it fails to compile with Unresolved reference: setSessionOnError. Before merging:

  • Bump the native pins (NATIVE_SDK_VERSIONS.md, packages/*/android/build.gradle, *.podspec) to the first Android and iOS releases that contain error-session capture.
  • With that bump, update the native test mocks (MockRumMonitor.kt, MockRUMMonitor.swift) to cover setForcedSession() and getRemoteConfig().

Summary

Passes the native error-session capture switches through the React Native SDK.

  • DdSdkReactNativeConfiguration gains sessionOnError and sessionReplayOnError, both defaulting to false. They are also accepted in the partial config used by DatadogProvider and in datadog-configuration.json, including the schema.
    • sessionOnError: sessions that the session sample rate does not pick are kept only if they report an error.
    • sessionReplayOnError: the same for replays the replay sample rate does not pick.
  • Android: both switches are applied to the RUM and Session Replay configurations.
  • iOS: both switches are set on RUM.Configuration and handed to SessionReplay.Configuration when Session Replay is enabled.
  • sessionReplayOnError lives on the SDK configuration rather than on SessionReplay.enable. On Android the switch is part of the RUM configuration, which is built before Session Replay is enabled.
  • Release path: unhandled JS exceptions, console.error and DdRum.addError all reach the native error path, so they release a held session.
  • Dropped errors: an error dropped by errorEventMapper never reaches native code, so it does not release the session.
  • Android caveat: JS error tracking also forwards errors through DdLogs, and the native Android logger reports them to RUM as logger errors. To keep such a session held on Android, logEventMapper has to drop the log too. This is documented on the option.

Behaviour change for all users (Android)

JS error tracking reports a tracked error to RUM and to Logs. The native Android logger used to forward that log back to RUM as a second, logger-sourced error that bypassed errorEventMapper. A native RUM error mapper in both bridges now drops that logger copy, so each tracked JS error produces exactly one RUM error. The tracked-error log itself is still sent to Logs.

Testing

  • Jest: 887 passed, 1 skipped (already skipped before this change). There are new pass-through and defaults tests, and the snapshots were updated.
  • All 9 packages build.
  • Native bridge unit tests, run against local native builds that contain the feature:
    • Android: 148/148.
    • iOS: 139/139.
  • End-to-end on an Android emulator and an iOS simulator, against a local capture intake, with sampling at 0:
    • Switch on, no error: 0 RUM events.
    • Switch on, unhandled JS error: the buffered views, actions, resources and the error are released. Every event has session_sample_rate 0, and views carry sampled_for_error: true.
    • Switch off, same error (negative control): 0 RUM events.
    • errorEventMapper drops the error: 0 RUM events on iOS. On Android it stays held only when the log is dropped too, as described above.
    • A normal session at a rate of 100: no marker, rate 100.

Adds two init options to the SDK configuration, both off by default:

- sessionOnError keeps the sessions sessionSamplingRate leaves out, but
  only those that report an error. The native SDKs hold such a session in
  memory and upload its last minute when it reports one.
- sessionReplayOnError does the same for the replays the Session Replay
  sample rate leaves out.

Both sit on the SDK configuration rather than on SessionReplay.enable:
on Android the replay switch belongs to the RUM configuration, built when
the SDK starts, before Session Replay is enabled. On iOS it belongs to the
Session Replay configuration, so the bridge keeps it and hands it over
when Session Replay is enabled.

Both are also read from datadog-configuration.json for native
initialization.

JS errors reach the native addError path, so they release a held session;
an error dropped by errorEventMapper never reaches native code. On Android
an error that error tracking also logs through DdLogs reaches RUM again
from the native logger, which releases the session unless logEventMapper
drops that log as well; the option's documentation says so.

Needs native SDK releases that provide these options.
JS error tracking reports an error to RUM and, flagged with
`_dd.error_log.is_crash`, to Logs. The native logs feature forwards every
error-level log back to RUM as a `logger` error, so each tracked JS error
produced two RUM errors, and the second one bypassed the JS
`errorEventMapper`: an error the mapper dropped still counted - and, with
`sessionOnError`, still released a withheld session. Both bridges now set
a native RUM error mapper that drops the logger copy of a tracked error;
logs the application sends itself are untouched.

On iOS, `sessionReplayOnError` is now published before the core is handed
to on-core-initialized listeners, so Session Replay enabled from such a
listener reads the configured value.
The log that error tracking sends alongside a RUM error carried the RUM
crash flag too, so the native logger's copy of a fatal JS error reached RUM
as a crash, which no error mapper can drop: a fatal error the JS
`errorEventMapper` dropped still counted. The crash flag now stays on the
RUM error.

The marker the bridges drop that copy on is internal provenance, so
`DdLogs` restores it after the application's `logEventMapper`, as it
already does for the error source type.

On iOS, Session Replay reads `sessionReplayOnError` only once it holds the
core, which the SDK publishes after the switch.
The marker is restored after the application's `logEventMapper`, but it
was read from the context object the mapper receives, so a mapper editing
the context in place could still remove it.
Whether a JS error crashed the app is a fact of the error, not of its
context: `DdRum.addError` keeps `_dd.error.is_crash` on the RUM error
whatever the `errorEventMapper` does to the context. The log that error
tracking sends alongside carries the flag for the `logEventMapper` as
before; `DdLogs` strips it from the log itself, where it would make the
copy the native logger forwards to RUM a crash that no mapper can drop.
`DdRum.addError` read the crash flag off the context before validating
it, so a null context threw instead of being treated as empty. Also
covers the on-error switches set one at a time.

This branch has not been deployed

No deployments
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.

1 participant