Skip to content

feat(sentry): migrate to @sentry/react-native v8; exit telemetry as Application Metrics#73

Merged
gmaclennan merged 8 commits into
mainfrom
sentry-sdk-v8-metrics
Jun 11, 2026
Merged

feat(sentry): migrate to @sentry/react-native v8; exit telemetry as Application Metrics#73
gmaclennan merged 8 commits into
mainfrom
sentry-sdk-v8-metrics

Conversation

@gmaclennan

Copy link
Copy Markdown
Member

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-native v8 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

  • peer + dev dep @sentry/react-native ^7.13.0^8.13.0⚠️ breaking for consumers: the host app must upgrade its Sentry SDK in the same release (v8 floors: iOS 15+, Xcode 16.4+, AGP 7.4+, Kotlin 1.8+ — all already satisfied by this module/Expo 55).
  • iOS: Sentry/HybridSDK '8.58.0'Sentry '9.15.0' in the podspec (cocoa 9 dropped the HybridSDK subspec; RNSentry v8 depends on the plain pod), matching exact: pin in Package.swift. check-sentry-cocoa-pin.mjs updated for RN v8's podspec shape (plain Sentry dep, version via the sentry_cocoa_version Ruby variable).
  • Android: sentry-android-core 8.32.0 → 8.43.0.
  • cocoa 9 API drift: exactly one call site (TransactionContext gained nullable sampleRate/sampleRand). The @_spi(Private) surfaces the bridge relies on (SentryEventDecoder, PrivateSentrySDKOnly) survive the major unchanged.

Exit telemetry → comapeo.app.exit count metric

  • Android: one count per record via Sentry.metrics().count, former tags+extras as attributes; event level becomes the exit.severity attribute (error/warning/info). Same high-water/anchor pipeline and tests.
  • 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 are deleted; a count carries N natively.
  • Both SDKs ship metrics enabled by default (enableMetrics/Metrics.enable default 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:

  • Android: uptime_bucket, bg_duration_bucket, comapeo.fgs.killed_in_background now flow at diagnostic; exact alive_for_ms / backgrounded_for_ms remain behind captureApplicationData.
  • iOS: nothing left gated — the count-per-window shape has no usage-tier residue.

The plan is reshaped on the same principle:

  • Phase 5's per-RPC spans, backend memory checkpoint, and storage-size sample re-tier into Phase 11's diagnostic metrics inventory — RPC timing becomes comapeo.rpc.*.duration_ms distribution metrics at diagnostic; per-RPC traces (the privacy-expensive shape) move behind the debug toggle.
  • Phase 11 marked unblocked: every layer now has the metrics API, including the backend (@sentry/node-core 10.53 ≥ the 10.25 floor), and comapeo.app.exit proves the native emission path.

Validation

  • swift test: 99 tests, 0 failures against cocoa 9.15; MetricKit half typechecked against the iOS 15.1 SDK.
  • Gradle JVM unit tests green against sentry-android 8.43.
  • npm run build + lint green; example app typechecks against v8 (appStartIntegration survives).
  • check-sentry-cocoa-pin.mjs passes with the v8 layout.
  • Runtime (API 34 emulator, example app rebuilt on the new SDKs): backend boots to STARTED; SIGKILLed the FGS while foreground-service and the relaunch reported the record through Sentry.metrics().count with no errors logged.

Consumer note for the example/dev workflow: after pulling this branch, re-run pod install in apps/example/ios (new pod major + new source file list).

🤖 Generated with Claude Code

gmaclennan and others added 2 commits June 10, 2026 06:47
…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>
@socket-security

socket-security Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedexpo-module-scripts@​55.0.273100100100100
Updated@​types/​node@​22.19.15 ⏵ 24.12.2100 +110081 +196100
Addedexeca@​9.6.18810010083100
Updated@​sentry/​react-native@​7.13.0 ⏵ 8.13.085 -13100100 +196 +1100
Addedglobals@​17.5.01001008691100
Added@​eslint/​js@​9.39.410010010088100
Added@​eslint/​compat@​2.0.510010010092100
Addedeslint@​9.39.49710010095100

View full report

@socket-security

socket-security Bot commented Jun 10, 2026

Copy link
Copy Markdown

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.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @sentry/react-native is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/@sentry/react-native@8.13.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@sentry/react-native@8.13.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @typescript-eslint/eslint-plugin is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/expo-module-scripts@55.0.2npm/@typescript-eslint/eslint-plugin@8.59.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@typescript-eslint/eslint-plugin@8.59.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm cssom is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/cssom@0.3.8

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/cssom@0.3.8. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm cssom is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/cssom@0.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/cssom@0.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: package-lock.jsonnpm/entities@4.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/entities@4.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: package-lock.jsonnpm/entities@6.0.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/entities@6.0.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm es-abstract is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/expo-module-scripts@55.0.2npm/es-abstract@1.24.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/es-abstract@1.24.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm eslint-plugin-react is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/expo-module-scripts@55.0.2npm/eslint-plugin-react@7.37.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/eslint-plugin-react@7.37.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm execa is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/execa@9.6.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/execa@9.6.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

…peer range

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from sentry-exit-telemetry to main June 10, 2026 10:38
gmaclennan and others added 5 commits June 10, 2026 12:07
…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>
* main:
  chore(e2e): add e2e tests on browserstack via Maestro (#56)
  fix(sentry): make exit telemetry lossless and stop cross-process clobbering (#84)
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>
@gmaclennan gmaclennan merged commit 63f2b23 into main Jun 11, 2026
12 checks passed
@gmaclennan gmaclennan deleted the sentry-sdk-v8-metrics branch June 11, 2026 17:04
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