Skip to content

Track adoption of opt-in features via sdk.integrations markers #6415

Description

@antonis

Summary

Several opt-in features shipped in the past year leave no distinctive fingerprint on events, so we have no way to measure adoption in our telemetry. Extend the marker-integration pattern already used by feedback/lazy.ts to cover them.

Background

The RN SDK already tracks feedback adoption by registering empty named integrations (MobileFeedback, AutoInjectMobileFeedback, etc.) whose names flow through to event.sdk.integrations via @sentry/core's prepareEvent (Hex table: sdk_integration_array).

Confirmed via telemetry:

  • sdk_features_array is empty for sentry.javascript.react-native%@sentry/core has no setSdkFeature/addSdkFeature API and doesn't populate event.sdk.features. Not a viable channel today.
  • sdk_integration_array is the working channel; e.g., AppStart currently surfaces there (~9,983 projects).

Features to add markers for

Marker name Feature Registration mechanism
CaptureAppStartErrors Native pre-JS init via sentry.options.json (iOS RNSentrySDK.start, Android equivalent) — 8.0.0 Native records the flag on successful bootstrap; JS reads via the bridge at init and calls client.addIntegration({ name: 'CaptureAppStartErrors' })
StandaloneAppStart enableStandaloneAppStartTracing — 8.17.0 Register at Sentry.init when option is set
ExtendedAppStart extendAppStart / finishExtendedAppStart / getExtendedAppStartSpan — Unreleased Register on first call
MobileReplayNetworkDetails mobileReplayIntegration({ networkDetailAllowUrls }) — 8.15.0 Register at replay integration setup when networkDetailAllowUrls is non-empty
MobileReplayNetworkBodies mobileReplayIntegration({ networkCaptureBodies: true }) — 8.15.0 Register at replay integration setup when option is true
GlobalErrorBoundary Sentry.GlobalErrorBoundary / withGlobalErrorBoundary — 8.9.1 Lazy — register on component mount
ExpoRouterErrorBoundary Sentry.wrapExpoRouterErrorBoundary manual wrap — 8.16.0 Lazy — register on first wrap call
AutoInjectExpoRouterErrorBoundary Babel transform via autoWrapExpoRouterErrorBoundary: true — 8.17.0 Register from the wrap runtime when invoked from the Babel-injected call site (distinguish from manual wrap)
NavigationContainer Sentry.NavigationContainer drop-in — 8.13.0 Lazy — register on component mount
AppLoaded Sentry.appLoaded() — 8.7.0 Register on first call

Two flavours of registration:

  • Init-time (config-flag driven): CaptureAppStartErrors, StandaloneAppStart, MobileReplayNetworkDetails, MobileReplayNetworkBodies — check the option at client / integration setup and register if set. Cluster in client.ts or the relevant integration file.
  • Lazy on first use: ExtendedAppStart, GlobalErrorBoundary, ExpoRouterErrorBoundary, AutoInjectExpoRouterErrorBoundary, NavigationContainer, AppLoaded — register on component mount / first API call, guarded by getClient()?.getIntegrationByName(...) for idempotency.

Precedents in the codebase:

  • Lazy component-mount markers: packages/core/src/js/feedback/lazy.ts

Why markers, not real integrations

For the component/API features the opt-in surface is the component/API itself; requiring users to also add integrations: [...] in Sentry.init would be a double opt-in and hurt DX for no functional benefit. CaptureAppStartErrors runs before JS exists, so a JS integration can't gate it; a bridged marker is the only honest signal. AutoInjectExpoRouterErrorBoundary is configured in Metro/Expo config, not Sentry.init, and doesn't map to a JS integration either. The two replay sub-options are gated by fields on the existing mobileReplayIntegration — a separate integration would be a double opt-in.

Notes on partial existing signals

  • StandaloneAppStart emits a top-level app.start transaction, distinguishable in the performance dataset — but projects on errors-only or with low tracing sample rates are invisible. The marker gives cross-event adoption.
  • MobileReplayNetworkDetails / MobileReplayNetworkBodies — the parent MobileReplay integration is already tracked, but the sub-options (headers vs. bodies) aren't distinguishable without the markers. Two markers give a clean funnel: replay → network details → bodies.

Acceptance criteria

  • Marker registered idempotently for each of the features listed above
  • Names verified not to collide with any integration in @sentry/core / @sentry/react
  • iOS + Android parity for CaptureAppStartErrors
  • AutoInjectExpoRouterErrorBoundary distinguishable from manual ExpoRouterErrorBoundary (same split precedent as MobileFeedback vs. AutoInjectMobileFeedback)
  • No CHANGELOG entry needed — purely internal telemetry

Follow-up (out of scope)

Lower-value API-only opt-ins to consider in a second pass: pauseAppHangTracking/resumeAppHangTracking, onNativeLog, enableAutoConsoleLogs, top-level Sentry.setAttribute/setAttributes, beforeErrorSampling for mobileReplayIntegration, sentry-span-attributes prop.

Metadata

Metadata

Assignees

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions