Zero-config OpenTelemetry RUM (Real User Monitoring) for Flutter. One package, one initialize() call — and the SDK captures spans, metrics, and logs for taps, navigation, errors, lifecycle, crashes, performance, and network out of the box.
- Package — https://pub.dev/packages/scout_flutter
- Publisher — base14.io
| Signal | Span/Metric | Details |
|---|---|---|
| Taps | user_interaction |
Buttons, GestureDetectors, InkWells, Switches, Tabs |
| Lifecycle | app_paused, app_resumed |
Background/foreground transitions |
| Errors | error.count metric |
FlutterError + uncaught async exceptions |
| Device info | Resource attributes | Model, manufacturer, battery level, battery discharge rate, orientation, connectivity |
| App startup | app_startup |
Cold start and warm start duration |
| Long tasks | long_task |
Main isolate jank detection (configurable threshold) |
| ANR | anr |
Native watchdog detects unresponsive main thread; captures full thread dump and breadcrumbs |
| Frame metrics | flutter.frame.build_time, flutter.frame.raster_time |
Per-frame build and raster histograms — opt-in via enableFrameMetrics (default off; records every frame, one stream per screen) |
| Frozen frames | frozen_frame |
Frames exceeding 700ms |
| Memory | flutter.memory.usage |
Native memory gauge — opt-in via enableMemoryMetrics (default off; polled every vitalsCollectionIntervalSeconds, default 60s) |
| CPU | flutter.cpu.usage |
CPU percentage gauge — opt-in via enableCpuMetrics (default off) |
| Crash detection | app_crash |
Detects OOM/SIGKILL/exit crashes via session marker |
| Native crashes | native_crash |
JVM exceptions, NDK signals (SIGSEGV, SIGABRT, etc.) with full stack trace, registers, memory map |
| Signal | Span | Details |
|---|---|---|
| Screen views | screen_view |
Auto-named from route settings or widget type |
| Screen load time | screen_load |
Time from push to first frame rendered |
| View sessions | view_session |
Time spent on each screen |
| Signal | Span | Details |
|---|---|---|
| HTTP requests | http.request |
Method, URL, status, duration, response size |
| Distributed tracing | W3C traceparent |
Injected for first-party hosts |
| Signal | Export | Details |
|---|---|---|
| Logs | OTLP logs | Debug, info, warning, error severity levels |
| Print capture | OTLP logs | Optional debugPrint() capture as info-level logs |
Three categories of crashes:
- Session marker (
app_crash) — OOM kills,exit()calls, and SIGKILL via persistent marker file. Reported on the next launch with the crashed session's breadcrumbs. - JVM / NSException (
native_crash) — uncaught Java/Kotlin exceptions on Android, NSExceptions on iOS. Written to disk before the process dies. - Native signals (
native_crash) — SIGSEGV, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGTRAP. On Android, an in-process C signal handler captures stack trace via frame-pointer walk, register dump, signal code, pid/tid/uid, memory map, ABI, build fingerprint, kernel version, process uptime. On iOS, the native crash reporter captures POSIX signals, Mach exceptions, C++ exceptions, and main-thread deadlock, with MetricKit supplying OS-delivered crash/hang diagnostics.
Breadcrumbs are persisted to disk on every record, so they survive crashes and ship with both app_crash and native_crash spans.
Android ApplicationExitInfo post-mortems are filtered to crash-class reasons only (anr, jvm_crash, native_crash, low_memory) — normal exits like the user swiping the app away are never reported as crashes — and each record is reported exactly once via a persisted drain watermark. A JVM death produces two spans: jvm_exception (in-process, full stack trace) and jvm_crash (OS post-mortem, process facts, no stack — Android retains trace blobs only for ANR/native-crash exits).
Every telemetry callback, error handler, and export path is wrapped in try/catch. If any telemetry operation fails, it silently degrades — your app continues running normally.
By default Scout samples 1% of sessions (sessionSampleRate: 1.0). The decision is made once per session and applies uniformly to all three signals — spans, metrics, and logs: a sampled session sends everything (coherent traces, matching metrics and logs); an unsampled session sends nothing.
Error- and crash-class spans (error, native_crash, app_crash, anr, ui_hang) and error-level logs bypass the session sample rate by default. Set alwaysCaptureErrors: false to subject them to the same gate. Sampling is enforced both at the OpenTelemetry layer (so it also covers direct tracer.startSpan calls) and in a single fail-closed gate shared by every emit path — telemetry produced before the session exists is dropped, never leaked.
All three signals share one batching model, governed by four knobs:
exportIntervalSeconds(default 30) — export cadence for spans, metrics, and logs. (metricExportIntervalSecondsremains as an optional metrics-only override.)maxExportBatchSize(default 512) — max items per batch.maxQueueSize(default 2048) — max items buffered; overflow is dropped.maxRetries(default 0) — delivery is at-most-once for every signal, so a batch is never duplicated on the backend. Failed exports are dropped unless offline buffering is enabled.
Offline buffering is off by default (offlineBufferEnabled: false, per-signal caps 0) — nothing is stored on disk. Opt in for durability at the cost of possible duplicate delivery on replay.
Each signal's exporter holds a single keep-alive HTTP connection (idle timeout sized to outlive the export interval), so TLS handshakes happen once per app session per signal — not once per export.
Set debugLogging: true to print a [scout] line for every init, session rotation, sampling decision, export batch, and log entry. Useful while integrating; noisy in production.
[scout] init ok (service=my-app endpoint=http://localhost:4318 v=1.0.0 sampleRate=1.0 alwaysCaptureErrors=true)
[scout] session a1b2c3 sampled=true
[scout] span screen_view → recordAndSample
[scout] span http.request → drop
[scout] export batch: 8 spans (212ms) ok
[scout] log [warn] Retry attempt 2
ScoutFlutter.initialize(config: ...)— boot the SDKScoutFlutter.navigatorObserver— navigation/screen trackingScoutFlutter.dioInterceptor— Dio HTTP interceptor (apps usingdart:ioHttpClient are tracked automatically)ScoutFlutter.observeScroll(child: ...)— scroll-depth instrumentationScoutFlutter.logEvent(name, attributes: ...)— custom business eventsScoutFlutter.logInfo/logWarning/logError/logDebug(...)— structured loggingScoutFlutter.addBreadcrumb(type, message)— error contextScoutFlutter.reportError(error, stackTrace)— manual error reportingScoutFlutter.setUser(id: ..., attributes: ...)/clearUser()— user identityScoutFlutter.setSessionAttributes({...})/clearSessionAttributes()— session-scoped attributesRumUserActionAnnotation— custom tap labels for non-standard widgetsheadersconfig — OTLP auth headers sent with every export (e.g.Authorization: Bearer …)beforeSendconfig — filter or modify events before exportmaxTombstoneBytesconfig — cap on Android exit-info tombstone bytes captured for ANR/native post-mortemsenableFrameMetricsconfig — opt into per-frame build/raster histograms (default off; highest-volume metrics)enableMemoryMetrics/enableCpuMetricsconfig — opt into the periodic vitals gauges (default off)exportIntervalSeconds/maxExportBatchSize/maxQueueSize/maxRetriesconfig — unified batch/export tuning for spans, metrics, and logs (defaults 30 / 512 / 2048 / 0)metricExportIntervalSecondsconfig — optional metrics-only override of the unified export intervalvitalsCollectionIntervalSecondsconfig — memory/CPU poll interval (default 60)
Telemetry is exported via OpenTelemetry Protocol (OTLP) over HTTP:
- Traces — spans for user interactions, navigation, crashes, HTTP requests
- Metrics — histograms and gauges for frame times, memory, CPU
- Logs — structured log records with severity levels
Data flows through a beforeSend filter, then to the OTLP collector. Failed exports are queued offline and retried when connectivity returns.
| Platform | Taps | Lifecycle | Errors | Navigation | Crashes | ANR | Native Vitals |
|---|---|---|---|---|---|---|---|
| Android | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| iOS | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
Both platforms capture native crashes in-process. iOS covers POSIX signals (SIGSEGV/SIGABRT/…), Mach exceptions, C++ exceptions, NSException, and main-thread deadlock, complemented by MetricKit for OS-delivered crash and hang diagnostics. Android pairs an in-process C signal handler with ApplicationExitInfo post-mortems.
MIT