Mobile crash and vitals ingest (server half)

Embed
Preview of the card
<a href="https://watchtowerops.net/docs/ingest-mobile"><img src="https://watchtowerops.net/docs/_api/public/orgs/aa5959cb-1a0b-4a9a-bc9a-913e7ac31cd9/spaces/b7aac181-9916-4805-b253-becc5fd2ca62/pages/0b1446a4-33e4-4c87-8f99-2ceab3ace406/social-card.svg?v=3e7ae33eec8b" alt="Mobile crash and vitals ingest (server half)" width="600" height="315"></a>

Watchtower ships native SDKs for iOS (sdks/ios), Android (sdks/android), React Native (sdks/react-native), Flutter/Dart (sdks/dart) and Kotlin Multiplatform (sdks/kmp). They send crashes, ANRs/hangs, app-start timing, slow/frozen frames, RUM batches and masked screenshot replay, with an offline buffer. The endpoints below are what any mobile SDK — ours or a third party's that emits the same shapes — sends to.

Crashes (Sentry envelopes)

Send events to the Sentry-compatible endpoint. Events whose mechanism.type or exception type is an ANR, watchdog termination, out-of-memory, signal or Mach exception are stored as exceptions with tags crash.class (anr, watchdog_termination, oom, signal, mach), crash.handled=false and app.in_foreground. Device, OS and app contexts are kept; the user IP is never stored. Native frames need instruction_addr and debug_id; upload dSYM, ELF or ProGuard files for symbolication.

Sessions and vitals (RUM batch or OTLP)

Events with an os.name of iOS, iPadOS, Android, tvOS or watchOS are stored with device_class=mobile. These vitals are accepted (stored in the RUM events by name):

  • app_launch_cold, app_launch_warm — launch time in ms. They get wt.rating: cold good at or below 2000, poor above 4000; warm good at or below 1000, poor above 2000. Per-phase timing lives in launch.phase.{process,application,activity,ui,first_frame}_ms and feeds the App launches view.

  • app_hang — main-thread hang duration in ms.

  • frozen_frames, slow_frames — frame counts.

  • cpu_ticks_per_s, memory_mb — runtime resource usage.

Query them on GET /v1/projects/{prj}/rum/vitals?metric=<name>&device_class=mobile, which returns the same p50/p75/p95 time series as the web vitals. Only launch time is rated; the other mobile metrics have no good/needs-improvement/poor thresholds, so those shares are 0. GET /v1/projects/{prj}/rum/vitals/by_screen breaks a metric down per screen (the view route). Both accept a flag=key:value filter to compare variants.

Each resumed screen is a RUM view; errors, network requests and feature-flag evaluations attach to the session like on web. Masked screenshot replay chunks upload to the replay endpoint.

Updated