Mobile Observability Overview
Instructions
Mobile observability is not "backend observability, but smaller". Devices are constrained: battery, flaky networks, storage quotas, privacy boundaries, and the impossibility of hotfixing. Collect what you need, sample aggressively, and never ship a telemetry SDK that costs more than it informs.
1. The Four Signals, Mobile-Flavored
| Signal |
Why |
Constraints |
| Metrics |
Numeric trends (startup time, crash-free, conversion) |
Low volume, aggregate client-side |
| Traces |
Causal chain across calls (cold start, feature flow) |
Sample heavily; one trace per flow |
| Logs |
Event narrative |
Expensive to ship; breadcrumbs are preferable |
| Events (analytics) |
Product decisions |
Deduplicate and batch; respect consent |
2. What to Measure
Minimum set every mobile app should emit:
- Cold start time to first frame, to first interactive.
- Warm start time.
- Frame drops / jank on key screens.
- Network: p50/p95 latency per endpoint, error rate per endpoint, retry counts.
- Crash-free sessions / users (see
crash-reporting-strategy).
- ANRs (Android), hangs (iOS).
- Battery drain signals via OS tooling; not a per-event metric.
- Sync: outbox length, drain success rate, conflict rate.
- Funnel events: sign-in started/completed, checkout stages, key feature start/complete.
3. Cold Start Budget
- Target under 2s to first meaningful paint on mid-range devices.
- Measure with
MetricKit on iOS and Macrobenchmark / StartupTracing on Android.
- Budget: every new SDK adds 50-200ms; every synchronous disk read in Application startup adds 20-100ms.
// Android: AppStartup + Firebase Performance
class PerformanceInitializer : Initializer<Unit> {
override fun create(context: Context) {
FirebasePerformance.getInstance().isPerformanceCollectionEnabled = true
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
// iOS: MetricKit
class MetricsObserver: NSObject, MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXMetricPayload]) {
for p in payloads { Metrics.send("launch.time", p.applicationLaunchMetrics?.histogrammedTimeToFirstDraw) }
}
}
MXMetricManager.shared.add(MetricsObserver.shared)
4. Tracing
Use tracing for end-to-end flows that span UI, local storage, and network. Typical traces:
- Cold start: process start -> splash -> first screen render.
- Sign-in: tap -> auth API -> token exchange -> first authed screen.
- Checkout: cart -> address -> payment -> confirmation.
Keep per-trace spans shallow (under 20). Sample heavily (5-10% on production, 100% in dev/staging). Attach trace IDs to logs for correlation.
5. Logs vs Breadcrumbs
- Ship breadcrumbs to the crash reporter; they travel with the next crash.
- Do not stream every log to a backend. It burns battery and bandwidth.
- For debugging in the field, ship a log export feature that bundles recent logs with the user's consent and a support ticket.
6. Analytics Events
- Maintain a spec (JSON schema) for every event: name, required properties, versioning.
- Gate event emission on consent where legally required (GDPR, CCPA).
- Deduplicate: retry-safe events carry a client event ID; the pipeline dedupes.
- Batch and flush on:
- Network available + above threshold N events.
- App backgrounding (with a short budget).
- Foreground recovery after offline.
// React Native - simple batcher
const queue: AnalyticsEvent[] = [];
function track(event: AnalyticsEvent) {
queue.push({ ...event, id: uuid(), ts: Date.now() });
if (queue.length >= 20) flush();
}
AppState.addEventListener('change', (s) => { if (s === 'background') flush(); });
7. Privacy and Consent
- Never send PII in telemetry. Hash stable IDs; do not send email, name, phone.
- Respect App Tracking Transparency on iOS for anything that qualifies as tracking.
- Respect Play Data Safety declarations; the label must match what you actually collect.
- Provide in-app opt-out for analytics/crash reporting. Persist the choice across reinstalls if legally required (keychain on iOS).
- Do not store telemetry buffers in backups that might sync to iCloud/Drive with PII.
8. Sampling and Cost
- Client-side sampling: traces and non-critical metrics at 1-10%. Scale with user base.
- Server-side backstop: pipeline rejects over-quota events to keep costs sane.
- Aggregate on-device where possible (histograms over events) and send summaries.
9. Dashboards and Alerts
- One dashboard per release showing: crash-free sessions, key funnel, top endpoints, cold start.
- Alerts tied to SLOs, not raw metrics.
- Release-aware: the alert knows which version is rolling out and compares cohorts.
10. Tool Menu
- Firebase Performance + Crashlytics: free, baseline.
- Sentry: traces + release health + errors + logs.
- Datadog RUM, New Relic Mobile, Dynatrace: enterprise APM.
- PostHog, Amplitude, Mixpanel: product analytics.
- OpenTelemetry for mobile: growing; useful if your backend is OTel-native.
Do not stack five SDKs. Inventory every one against privacy labels and startup cost.
11. Anti-Patterns
- Logging every function call "just in case". Data volume drowns the signal.
- Streaming real-time logs from production clients. Use breadcrumbs and support bundles.
- Sending full URLs with query strings containing secrets.
- Collecting precise location for analytics. Truncate or avoid.
- Not versioning event schemas. Breaking downstream consumers silently.
- Letting the analytics backlog grow unbounded on disk.
Checklist
1---2name: mobile-observability-overview3description: Mobile observability - metrics, traces, logs, and events within the constraints of battery, network, and storage. Use when defining what to collect, how to sample, and how to pipe signals to analytics and APM tools.4---56# Mobile Observability Overview78## Instructions910Mobile observability is not "backend observability, but smaller". Devices are constrained: battery, flaky networks, storage quotas, privacy boundaries, and the impossibility of hotfixing. Collect what you need, sample aggressively, and never ship a telemetry SDK that costs more than it informs.1112### 1. The Four Signals, Mobile-Flavored1314| Signal | Why | Constraints |15| --- | --- | --- |16| Metrics | Numeric trends (startup time, crash-free, conversion) | Low volume, aggregate client-side |17| Traces | Causal chain across calls (cold start, feature flow) | Sample heavily; one trace per flow |18| Logs | Event narrative | Expensive to ship; breadcrumbs are preferable |19| Events (analytics) | Product decisions | Deduplicate and batch; respect consent |2021### 2. What to Measure2223Minimum set every mobile app should emit:2425- **Cold start** time to first frame, to first interactive.26- **Warm start** time.27- **Frame drops / jank** on key screens.28- **Network**: p50/p95 latency per endpoint, error rate per endpoint, retry counts.29- **Crash-free sessions / users** (see `crash-reporting-strategy`).30- **ANRs** (Android), **hangs** (iOS).31- **Battery drain** signals via OS tooling; not a per-event metric.32- **Sync**: outbox length, drain success rate, conflict rate.33- **Funnel events**: sign-in started/completed, checkout stages, key feature start/complete.3435### 3. Cold Start Budget3637- Target **under 2s** to first meaningful paint on mid-range devices.38- Measure with `MetricKit` on iOS and `Macrobenchmark` / `StartupTracing` on Android.39- Budget: every new SDK adds 50-200ms; every synchronous disk read in Application startup adds 20-100ms.4041```kotlin42// Android: AppStartup + Firebase Performance43class PerformanceInitializer : Initializer<Unit> {44 override fun create(context: Context) {45 FirebasePerformance.getInstance().isPerformanceCollectionEnabled = true46 }47 override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()48}49```5051```swift52// iOS: MetricKit53class MetricsObserver: NSObject, MXMetricManagerSubscriber {54 func didReceive(_ payloads: [MXMetricPayload]) {55 for p in payloads { Metrics.send("launch.time", p.applicationLaunchMetrics?.histogrammedTimeToFirstDraw) }56 }57}58MXMetricManager.shared.add(MetricsObserver.shared)59```6061### 4. Tracing6263Use tracing for end-to-end flows that span UI, local storage, and network. Typical traces:6465- Cold start: process start -> splash -> first screen render.66- Sign-in: tap -> auth API -> token exchange -> first authed screen.67- Checkout: cart -> address -> payment -> confirmation.6869Keep per-trace spans shallow (under 20). Sample heavily (5-10% on production, 100% in dev/staging). Attach trace IDs to logs for correlation.7071### 5. Logs vs Breadcrumbs7273- Ship **breadcrumbs** to the crash reporter; they travel with the next crash.74- Do not stream every log to a backend. It burns battery and bandwidth.75- For debugging in the field, ship a **log export** feature that bundles recent logs with the user's consent and a support ticket.7677### 6. Analytics Events7879- Maintain a **spec** (JSON schema) for every event: name, required properties, versioning.80- Gate event emission on **consent** where legally required (GDPR, CCPA).81- Deduplicate: retry-safe events carry a client event ID; the pipeline dedupes.82- Batch and flush on:83 - Network available + above threshold N events.84 - App backgrounding (with a short budget).85 - Foreground recovery after offline.8687```tsx88// React Native - simple batcher89const queue: AnalyticsEvent[] = [];90function track(event: AnalyticsEvent) {91 queue.push({ ...event, id: uuid(), ts: Date.now() });92 if (queue.length >= 20) flush();93}94AppState.addEventListener('change', (s) => { if (s === 'background') flush(); });95```9697### 7. Privacy and Consent9899- Never send PII in telemetry. Hash stable IDs; do not send email, name, phone.100- Respect **App Tracking Transparency** on iOS for anything that qualifies as tracking.101- Respect **Play Data Safety** declarations; the label must match what you actually collect.102- Provide in-app **opt-out** for analytics/crash reporting. Persist the choice across reinstalls if legally required (keychain on iOS).103- Do not store telemetry buffers in backups that might sync to iCloud/Drive with PII.104105### 8. Sampling and Cost106107- Client-side sampling: traces and non-critical metrics at 1-10%. Scale with user base.108- Server-side backstop: pipeline rejects over-quota events to keep costs sane.109- Aggregate on-device where possible (histograms over events) and send summaries.110111### 9. Dashboards and Alerts112113- One dashboard per release showing: crash-free sessions, key funnel, top endpoints, cold start.114- Alerts tied to SLOs, not raw metrics.115- Release-aware: the alert knows which version is rolling out and compares cohorts.116117### 10. Tool Menu118119- **Firebase Performance + Crashlytics**: free, baseline.120- **Sentry**: traces + release health + errors + logs.121- **Datadog RUM**, **New Relic Mobile**, **Dynatrace**: enterprise APM.122- **PostHog**, **Amplitude**, **Mixpanel**: product analytics.123- **OpenTelemetry** for mobile: growing; useful if your backend is OTel-native.124125Do not stack five SDKs. Inventory every one against privacy labels and startup cost.126127### 11. Anti-Patterns128129- Logging every function call "just in case". Data volume drowns the signal.130- Streaming real-time logs from production clients. Use breadcrumbs and support bundles.131- Sending full URLs with query strings containing secrets.132- Collecting precise location for analytics. Truncate or avoid.133- Not versioning event schemas. Breaking downstream consumers silently.134- Letting the analytics backlog grow unbounded on disk.135136## Checklist137138- [ ] Core metrics (cold start, jank, crash-free, network p95) are collected per release.139- [ ] Tracing is set up for critical flows with sane sampling.140- [ ] Analytics events follow a versioned schema with a single spec.141- [ ] Consent gates telemetry where legally required.142- [ ] No PII or secrets travel in telemetry.143- [ ] Breadcrumbs replace always-on log streaming.144- [ ] Event batches flush on backgrounding and on network recovery.145- [ ] Dashboards are release-aware with cohort comparison.146- [ ] Telemetry SDK count is audited against privacy labels.147- [ ] Per-endpoint error and latency signals feed rollout halt criteria.