| name | mobile-observability-overview |
| description | 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. |
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.
class PerformanceInitializer : Initializer<Unit> {
override fun create(context: Context) {
FirebasePerformance.getInstance().isPerformanceCollectionEnabled = true
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
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.
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