feat(sentry): migrate to @sentry/react-native v8; exit telemetry as Application Metrics#73
Conversation
…it app-exit telemetry Post-mortem visibility on OS-driven kills, answering "does our backend stay alive long enough on this user's device?" per platform. Android (Phase 6): ExitReasonsCollector surfaces getHistoricalProcessExitReasons records (API 30+) as "android exit: REASON_*" events on the next process start — main process via a new ApplicationLifecycleListener, FGS from ComapeoCoreService.onCreate, each reporting its own process only. A per-process high-water timestamp dedupes across runs (first observation initialises it and emits nothing, so device updates never flood Sentry with the pre-feature backlog). oem.killer.suspected tags the SIGKILL-to-foreground(-service) signature of OEM custom killers. Wall-clock anchors (BackgroundAnchors, ProcessLifecycleOwner ON_STOP/ON_START) derive uptime/backgrounded-for buckets. Pre-30 devices set a one-time exitReasons.supported=false scope tag. iOS (Phase 7a): AppExitMetricsCollector subscribes to MXMetricPayload — the MetricKit metric side sentry-cocoa deliberately skips — and forwards MXAppExitMetric buckets as "ios exit: <cohort>_<bucket>" events with cause-class/intentional tags and a stable window_id. The decode logic (AppExitDecoder) is MetricKit-free so it compiles and tests on macOS via swift test. Phase 9b.8's tier reclassification ships here rather than later: the duration-derived Android fields and the iOS per-exit duplication only flow when captureApplicationData is on; the records themselves are diagnostic-tier. Divergence from plan: the pre-iOS-14 supported=false branch was dropped (podspec floor is 15.1 — dead code). Phase 7b (UserDefaults killed-in-background heuristic) stays in the plan as optional follow-up. Tests: 25 new JVM tests (high-water, tag/level mapping, tier gating, bucket boundaries, decode tables), 11 new Swift tests (duplication semantics, tier gating, unknown-bucket fallthrough). MetricKit usage typechecked against the iOS SDK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…try as Application Metrics Sentry re-launched metrics as Application Metrics (count/gauge/ distribution with attributes, no trace context required), and @sentry/react-native v8 lock-steps native SDKs that have it: sentry-android 8.43.0 and sentry-cocoa 9.15.0. This migration adopts v8 across the module and flips the exit telemetry (Phases 6/7a) from events to metrics — the goal of that telemetry is aggregate statistics, and metrics have no issue lifecycle, so nothing sits "unresolved" in the Issues UI and nothing fires regression alerts. ## Dependency bumps - peer + dev dep @sentry/react-native ^7.13.0 → ^8.13.0 (breaking for consumers — upgrade the host app's Sentry SDK in the same release) - ios: Sentry/HybridSDK 8.58.0 → Sentry 9.15.0 (cocoa 9 dropped the HybridSDK subspec; RNSentry v8 depends on the plain pod), matching exact pin in Package.swift - android: sentry-android-core 8.32.0 → 8.43.0 - check-sentry-cocoa-pin.mjs learns RN v8's podspec shape (plain `Sentry` dependency, version via `sentry_cocoa_version` variable) cocoa 9 API drift was one call site (TransactionContext gained nullable sampleRate/sampleRand); the @_spi(Private) surfaces the bridge relies on (SentryEventDecoder, PrivateSentrySDKOnly) survived the major. ## Exit telemetry → comapeo.app.exit count metric - Android: one count per record via Sentry.metrics().count with the former tags+extras as attributes; event level becomes the exit.severity attribute. Same high-water/anchor pipeline. - iOS: one count per window+bucket with the cumulative value as the metric value — the per-exit event duplication (and its window_id dedup machinery) is gone, since a count carries N natively. - Both SDKs ship metrics enabled by default; no init changes. ## Privacy re-tiering (metrics need fewer opt-ins) Metrics are aggregate and low-cardinality — pre-bucketed tags, no per-user timeline, no free text — so coarse fields moved to the always-on diagnostic tier: uptime_bucket / bg_duration_bucket / comapeo.fgs.killed_in_background on Android, and everything on iOS (nothing left gated there). Exact alive_for_ms / backgrounded_for_ms remain usage-tier. The plan is reshaped to match: Phase 5's per-RPC spans, backend memory checkpoint, and storage-size sample re-tier into Phase 11's diagnostic metrics inventory (RPC timing as distribution metrics at diagnostic; per-RPC traces move behind the debug toggle); Phase 11 is marked unblocked — every layer now has the metrics API, including the backend (@sentry/node-core 10.53). ## Validation - swift test: 99 tests, 0 failures (cocoa 9.15) - gradle testDebugUnitTest: green (sentry-android 8.43) - npm run build + lint green; example app typechecks against v8 - MetricKit half typechecked against the iOS 15.1 SDK - Runtime: example app on an API 34 emulator — SIGKILLed the FGS, relaunch reported the record through Sentry.metrics().count with no errors; backend boots to STARTED on the new SDK Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
…peer range Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bering Post-merge review fixes for the Phase 6/7a exit-reason telemetry: Android - Gate collection on Sentry.isEnabled() and advance the high-water mark only after capture: records were being consumed by no-op captures — the main process collected before the JS-triggered Sentry.init on every cold start, and the FGS collected with diagnostics off. - Main process now waits (up to 2 min) for JS-side Sentry init; anchors are snapshotted before the current run stamps its own, so collection can run late without reading this-session values. - Replace the backgrounded_at clear-on-foreground with a paired foregrounded_at stamp ordered against the exit timestamp: the relaunch used to zero the anchor before the FGS collected, making comapeo.fgs.killed_in_background systematically "false" in the kill-then-relaunch flow. - Per-process anchor prefs files with a single writer each (SharedPrefs is not multi-process safe; main and FGS were clobbering each other's keys in the shared file), written with commit=true so the backgrounded_at stamp survives an immediate SIGKILL. - Query exit records without a maxNum cap (the pid=0 window is package-wide, so a per-call cap let one process's churn evict the other's records) and cap to the newest 10 per process after filtering. - ExitReasonTags.levelFor returns SentryLevel directly, dropping the string round-trip re-parsed in capture(). - Derive the FGS process name at runtime instead of a literal copy of the manifest's android:process. iOS - Demote foreground memory_resource_limit/app_watchdog MetricKit buckets to warning: sentry-cocoa's watchdog-termination tracking (enabled by default) already captures those deaths as errors, so kill-rate dashboards double-counted them. - Cap app-usage-tier per-exit duplication at 10 events per window+bucket (counts are 24h cumulative totals; benign buckets reach the hundreds); window_count keeps the exact count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Carries the post-merge review fixes (PR #84) into the v8 metrics migration. Conflict resolution notes: - ExitReasonsCollector keeps the v8 metrics emission (comapeo.app.exit counts, exit.severity attribute, diagnostic-tier buckets) on top of the fix branch's structure: anchor snapshot, Sentry.isEnabled gate, high-water mark advanced only after capture, uncapped package-wide query with newest-10-per-process cap, and the backgrounded_at/foregrounded_at ordering logic. - ExitReasonTags keeps v8's severityFor (string attribute); the fix branch's typed-SentryLevel change is superseded — metrics have no event level. - iOS keeps v8's one-metric-per-bucket emission (the fix branch's per-event duplication cap is moot) and ports the cohort-aware severity demotion: foreground memory_resource_limit/app_watchdog are "warning" because sentry-cocoa's watchdog-termination tracking already reports those deaths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… add proc to iOS exit metrics Review follow-ups on the v8 metrics migration: - Sentry.flush(2s) before writeLastSeenMs: metrics sit in an in-memory 5s batch with no disk persistence, so a kill right after collection (an FGS start under an OEM killer — the population this measures) would lose the batch while the mark write consumed the records. - iOS comapeo.app.exit metrics now carry proc=main explicitly — scope tags don't propagate into metric attributes, so cross-platform slices by proc grouped all iOS rows under "(no value)". - Mark the plan's Phase 8 events→metrics migration as landed; update §7.5.1 for the flush; drop stale Sentry/HybridSDK comments (sentry- cocoa 9 dropped the subspec). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sentry-cocoa 9.15.0 (via @sentry/react-native v8) ships a swift-tools-version:6.0 manifest. Xcode 15.4 (Swift 5.10) can't parse it, so `swift test` fails at dependency resolution. Move the package-tests job to macos-15 / Xcode 26.3, matching the workflow's integration jobs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stacked on #72 (merge that first; this PR's base is
sentry-exit-telemetry).Sentry's Application Metrics (count/gauge/distribution with attributes; no trace context required; no issue lifecycle) is the right primitive for stats-gathering telemetry like the exit reasons landed in #72 — events there create perpetually-"unresolved" issues and regression-alert noise.
@sentry/react-nativev8 lock-steps native SDKs that have the metrics API (sentry-android 8.43, sentry-cocoa 9.15), so this PR does the migration and the flip together.SDK migration
@sentry/react-native^7.13.0→^8.13.0—Sentry/HybridSDK '8.58.0'→Sentry '9.15.0'in the podspec (cocoa 9 dropped theHybridSDKsubspec; RNSentry v8 depends on the plain pod), matchingexact:pin inPackage.swift.check-sentry-cocoa-pin.mjsupdated for RN v8's podspec shape (plainSentrydep, version via thesentry_cocoa_versionRuby variable).sentry-android-core8.32.0 → 8.43.0.TransactionContextgained nullablesampleRate/sampleRand). The@_spi(Private)surfaces the bridge relies on (SentryEventDecoder,PrivateSentrySDKOnly) survive the major unchanged.Exit telemetry →
comapeo.app.exitcount metricSentry.metrics().count, former tags+extras as attributes; event level becomes theexit.severityattribute (error/warning/info). Same high-water/anchor pipeline and tests.window_iddedup machinery are deleted; a count carries N natively.enableMetrics/Metrics.enabledefault true), so no init changes.Privacy re-tiering — metrics need fewer opt-ins
Metrics are aggregate and low-cardinality (pre-bucketed tags, no per-user timeline, no free text), so coarse fields moved to the always-on diagnostic tier:
uptime_bucket,bg_duration_bucket,comapeo.fgs.killed_in_backgroundnow flow at diagnostic; exactalive_for_ms/backgrounded_for_msremain behindcaptureApplicationData.The plan is reshaped on the same principle:
comapeo.rpc.*.duration_msdistribution metrics at diagnostic; per-RPC traces (the privacy-expensive shape) move behind thedebugtoggle.@sentry/node-core10.53 ≥ the 10.25 floor), andcomapeo.app.exitproves the native emission path.Validation
swift test: 99 tests, 0 failures against cocoa 9.15; MetricKit half typechecked against the iOS 15.1 SDK.npm run build+lintgreen; example app typechecks against v8 (appStartIntegrationsurvives).check-sentry-cocoa-pin.mjspasses with the v8 layout.Sentry.metrics().countwith no errors logged.Consumer note for the example/dev workflow: after pulling this branch, re-run
pod installinapps/example/ios(new pod major + new source file list).🤖 Generated with Claude Code