Skip to main content

Flutter

scout_flutter is a single Dart package that ships zero-config OpenTelemetry RUM for Flutter on iOS, Android, macOS, and web. Auto- captures the full Real User Monitoring event set (except Session Replay and Profiling) and exports it as OTLP traces, metrics, and logs to a Scout collector.

await ScoutFlutter.initialize(
config: ScoutFlutterConfig(
serviceName: 'my-app',
endpoint: 'https://otel.example.com',
),
);
runApp(const MyApp());

That's all the code you write. Every tap, navigation, HTTP request, error, crash, scroll, and frame metric is gathered automatically — no manual Scout.track(...) calls anywhere in your app.

What You Get

CapabilitySignalMechanism
Tap trackinguser_interaction span (type=tap, target, name_source, permanent_id, x/y)Global GestureBinding.instance.pointerRouter interception
Screen / route navigationscreen_view span with view.id, view.loading_type, view.referrer, view.is_activeAutoNameNavigatorObserver attached to every Navigator
Screen load timescreen_load span with screen.load_timeFirst-frame measurement after route push
App startupapp_startup span with app_startup.type = cold | warm, app_startup.durationWidgetsBinding.addPostFrameCallback on first frame
FBC vital (First Build Complete)app_vital span with vital.name = fbcEmitted alongside cold-start; ready for dashboards as a first-class vital
INV vital (Interaction → Next View)app_vital span with vital.name = inv, vital.from_screen, vital.to_screenTap timestamp correlated with next screen_view within 5 s
Errors (Flutter framework)error span with error.id, error.fingerprint, error.handled, breadcrumbsFlutterError.onError + PlatformDispatcher.instance.onError
Manual error reportingerror spanScoutFlutter.reportError(e, stackTrace)
Native crashes (iOS)native_crash span with crash.reason, registers (FAR/ESR), mach_exception, callstack_tree, binary_imagesKSCrash 2.x all five monitors + MetricKit MXCrashDiagnostic / MXHangDiagnostic
Native crashes (Android)native_crash span with crash.reason, signal info, tombstone (≤ 128 KB, configurable), crash.os_reason_*, PSS/RSSCustom NDK signal handler + ApplicationExitInfo (API 30+; reflective subReason on API 31+). Each OS death is reported exactly once (persisted watermark); normal exits (swipe-away, Force Stop, exit()) are never reported as crashes
ANRanr span with anr.duration, anr.threshold, anr.thread_count, anr.threads_json, anr.main_thread_stack, and breadcrumbsiOS: AppHangWatchdog (5 s default). Android: native ping watchdog polling every 100 ms (deterministic detection, fires once per hang) + ApplicationExitInfo REASON_ANR post-mortem. Captures a full thread dump at detection time.
UI hang (iOS)ui_hang span with ui_hang.duration, ui_hang.thresholdiOS-only sub-ANR watchdog at 250 ms (configurable). Complements KSCrash mainThreadDeadlock and the 5 s ANR detector
Long taskslong_task span with long_task.duration, long_task.thresholdDart isolate event-loop polling
HTTP requestshttp.request span with method, URL, status, duration, headersHttpOverrides global wrap + Dio interceptor (optional)
Distributed tracingW3C traceparent header injected into outgoing requests to hosts in firstPartyHostsWrap on the HTTP client
Scroll depthdisplay.scroll.max_depth, display.scroll.max_depth_scroll_top, display.scroll.max_scroll_height, display.scroll.max_scroll_height_time_ms on screen_viewScoutScrollObserver widget wrapping NotificationListener<ScrollNotification>
Lifecycleapp_paused, app_resumed spans + force-flush on backgroundAppLifecycleListener
Frame metrics (opt-in)flutter.frame.build_time, flutter.frame.raster_time histogramsWidgetsBinding.instance.addTimingsCallback. Off by default (enableFrameMetrics) — records on every frame, one stream per screen
Memory + CPU (opt-in)flutter.memory.usage, flutter.cpu.usage gaugesPlatform channel poll every vitalsCollectionIntervalSeconds (60 s). Off by default (enableMemoryMetrics / enableCpuMetrics)
Network connectivitynetwork.connection.type resource attribute (wifi, cellular, none)connectivity_plus listener
Batterydevice.battery.level, device.battery.state, device.battery.discharge_rate resource attributesbattery_plus + platform channel
Device orientationdevice.orientation resource attribute (portrait / landscape)Orientation-change listener
Device integritydevice.is_jail_broken resource attributeJailbreak / root heuristic via platform channel
LogsOTLP logsScoutFlutter.log*() and (opt-in) print / debugPrint capture
Anonymous user iduser.anonymous_id on every spanUUID v4 minted on first launch, persisted to temp dir
WebView bridgeEmbedded web pages adopt the native session.id + user.anonymous_id; their spans flow back as span.source = "webview"ScoutWebViewBridge.attach() + injectShim() on every page finish

Prerequisites

RequirementVersion
Flutter SDK≥ 3.7.0
Dart SDK≥ 3.7.0
iOS deployment target≥ 12.0
Android minSdkVersion≥ 21 (ApplicationExitInfo features activate from API 30+)
compileSdkVersion≥ 34 (recommended)
CocoaPods≥ 1.11
NDK (Android, for native crash)≥ 25 (matches Flutter default)

Installation

scout_flutter is published on pub.dev. Add it to your pubspec.yaml:

# pubspec.yaml
dependencies:
scout_flutter: ^0.2.0

Or:

flutter pub add scout_flutter
What changed in 0.2.0

The native engine now starts alongside the Flutter SDK on both platforms. This is additive for a pure-Flutter app: Flutter telemetry is unchanged, and you also get native crash capture and CPU/memory vitals under a native scope (base14.scout.android or base14.scout.ios) next to base14.scout.flutter.

The same mechanism lets a native host app share one session with embedded Flutter. See Hybrid (Native + Flutter).

iOS — CocoaPods install

The first build after adding scout_flutter triggers a pod install for the KSCrash 2.x and MetricKit dependencies. Make sure your iOS Podfile has platform :ios, '12.0' or higher:

# ios/Podfile
platform :ios, '12.0'

Then:

cd ios && pod install --repo-update && cd ..

Android — NDK setup

The native signal handler is built automatically as part of the plugin's Gradle build. No app-side configuration needed beyond ensuring your project has the NDK available (flutter doctor will warn if not).

Initialization

In your main.dart, before runApp():

import 'package:flutter/material.dart';
import 'package:scout_flutter/scout_flutter.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();

// Fire-and-forget — never block app startup on SDK init.
unawaited(
ScoutFlutter.initialize(
config: ScoutFlutterConfig(
serviceName: 'my-app',
serviceVersion: '1.0.0',
endpoint: 'https://otel.example.com',
headers: {'Authorization': 'Bearer …'},
),
),
);

// Wrap your root widget with the scroll observer so per-screen
// scroll metrics decorate the active screen_view span.
runApp(ScoutFlutter.observeScroll(child: const MyApp()));
}

Attach ScoutFlutter.navigatorObserver to every Navigator in your app — the root MaterialApp / CupertinoApp plus each nested Navigator (commonly inside CupertinoTabView, Navigator widgets used for bottom-sheet stacks, etc.):

MaterialApp(
navigatorObservers: [ScoutFlutter.navigatorObserver],
// …
);

// And per CupertinoTabView:
CupertinoTabView(
navigatorObservers: [ScoutFlutter.navigatorObserver],
builder: (context) => const SongsTab(),
);

navigatorObserver returns a fresh instance on every read, so attaching it to multiple Navigators does not trip Flutter's "observer already has a navigator" assertion. All instances funnel events into shared static state so dashboards see one coherent screen timeline regardless of which Navigator pushed a route.

Setting user identity

ScoutFlutter.setUser(
id: 'user-123',
attributes: {
'email': 'jane@example.com', // → user.email
'plan': 'pro', // → user.plan
'role': 'admin', // → user.role
},
);

// On logout:
ScoutFlutter.clearUser();

id is optional. user.id and every attribute ride on every span until cleared — bare attribute keys are auto-prefixed with user., and keys already starting with user. pass through unchanged. setUser replaces the whole user map, so pass everything you want each call.

Setting session attributes

Session attributes attach to every subsequent span, metric, and log for the rest of the session. Keys are stored verbatim (no auto-prefix).

// Replaces all existing session attributes.
ScoutFlutter.setSessionAttributes({'tenant': 'acme', 'ab_bucket': 'B'});

// Add one without clobbering the rest — merge with the current view:
ScoutFlutter.setSessionAttributes({
...ScoutFlutter.sessionAttributes,
'feature.new_checkout': 'true',
});

// Clear all.
ScoutFlutter.clearSessionAttributes();

They persist until you call clearSessionAttributes() — they are not cleared automatically on session rotation.

Configuration

ScoutFlutterConfig is the single config object. Required fields are flagged below; everything else has a sensible default and is opt-in.

Identity

FieldTypeDefaultDescription
serviceNameString(required)Logical app identifier. Used as service.name.
endpointString(required)OTLP-HTTP collector URL. /v1/traces, /v1/metrics, /v1/logs are appended automatically.
serviceVersionString?nullMaps to service.version. Set to your app build version.
securebooltrueWhen endpoint has no scheme, prefix https:// (true) or http:// (false).
headersMap<String, String>?nullExtra HTTP headers on every OTLP export. Use for auth.
resourceAttributesMap<String, String>?nullExtra attributes merged into every signal's Resource. Use for deployment.region, team, etc. Static — set once at init.

Network

FieldTypeDefaultDescription
enableNetworkTrackingbooltrueWraps HttpOverrides globally. Disable if you wire ScoutFlutter.dioInterceptor manually.
firstPartyHostsList<String>?nullHosts that receive a W3C traceparent header for distributed tracing. Supports exact match or *.host wildcards.
ignoreUrlPatternsList<RegExp>?nullURLs matching any pattern are not auto-instrumented.

Sessions

FieldTypeDefaultDescription
sessionTimeoutMinutesint30Inactivity timeout before a new session.id is minted.
maxSessionDurationMinutesint60Hard cap on session lifetime; rotates on the next ID read past this age regardless of activity. 0 disables.
sessionSampleRatedouble (0-100)1.0Percent of sessions sampled — default 1%. The decision is made once per session and applies uniformly to spans, metrics, and logs: a sampled session sends everything, an unsampled session sends nothing.
alwaysCaptureErrorsbooltrueError / crash / ANR-class spans and error-level logs bypass sessionSampleRate and are always exported. Set false to subject them to the same gate.

Thresholds

FieldTypeDefaultMinDescription
longTaskThresholdMsint10020Dart isolate task duration that qualifies as a long_task span.
anrThresholdMsint50001000Main-thread block duration that fires an anr span.
iosHangThresholdMsint25050 (or 0 to disable)iOS only — sub-ANR ui_hang watchdog. Complements ANR (5 s) and KSCrash mainThreadDeadlock (5 s+). Catches micro-stutter / jank.
maxTombstoneBytesint1310724096Max bytes of Android ApplicationExitInfo tombstone (ANR / native post-mortem) captured per report.

Batching & export (applies to spans, metrics, AND logs)

All three signals share one batching model. Each exporter also holds a single keep-alive HTTP connection for its lifetime (idle timeout sized to outlive the export interval), so TLS handshakes happen once per app session per signal — not once per export.

FieldTypeDefaultMinDescription
exportIntervalSecondsint301One export cadence for spans, metrics, and logs.
maxExportBatchSizeint5121Max items per export batch, per signal.
maxQueueSizeint20481Max items buffered awaiting export; overflow is dropped.
maxRetriesint00Delivery attempts after a failed export, for every signal. Default 0 = at-most-once: retrying an ambiguous failure (a timeout whose request the collector may already have ingested) delivers duplicate events.
metricExportIntervalSecondsint?null1Metrics-only override of exportIntervalSeconds. Unset means metrics follow the unified interval.
vitalsCollectionIntervalSecondsint601How often memory/CPU are polled natively (when the gauges are enabled).

Per-metric switches

The SDK ships no metrics by default — each gauge/histogram family is opt-in.

FieldDefaultDescription
enableFrameMetricsfalseflutter.frame.build_time / flutter.frame.raster_time histograms. They record on every rendered frame with one stream per screen — by far the highest-volume metrics the SDK can produce. Frozen-frame detection (the frozen_frame span) stays on regardless.
enableMemoryMetricsfalseThe flutter.memory.usage gauge. When off, the native poll is skipped entirely.
enableCpuMetricsfalseThe flutter.cpu.usage gauge, same behavior.

Offline buffer

Offline buffering is fully disabled by default — nothing is written to disk, and a batch that fails to export is dropped (strict at-most-once delivery). Opt in for durability at the cost of possible duplicate delivery on replay.

FieldTypeDefaultDescription
offlineBufferEnabledboolfalseMaster toggle. Set true to persist failed batches and replay them on next initialize() or connectivity change.
offlineMaxTraceItemsint0FIFO cap on persisted span items (0 = signal disabled in the queue). Oldest evicted first.
offlineMaxMetricItemsint0Same, for metric data points.
offlineMaxLogItemsint0Same, for log records.
maxOfflineStorageMbint5Coarse total-disk cap that runs alongside the per-signal offlineMax*Items caps when buffering is enabled — whichever limit is reached first wins.

Auto-instrumentation toggles

Every auto-instrumentation can be turned off independently. Span and log instrumentation defaults to on; metric collection defaults to off (see Per-metric switches).

ToggleDefaultWhat you lose when set to false
enableAutoTapTrackingtrueAll user_interaction spans.
enableErrorTrackingtrueerror spans from FlutterError.onError and PlatformDispatcher.onError. Manual reportError() still works.
enableLifecycleTrackingtrueapp_paused / app_resumed spans and the background-flush hook. Heavy loss — recommend leaving on.
enableStartupTrackingtrueapp_startup cold/warm spans and the FBC vital.
enableConnectivityTrackingtruenetwork.connection.type resource attr updates on network transitions.
enablePerformanceMetricstrueMaster switch for the whole metrics pipeline (exporter + reader). With it on, individual gauges still need their per-metric switches.
enableLongTaskDetectiontruelong_task spans. Tune with longTaskThresholdMs instead of disabling.
enableAnrDetectiontrueanr spans and the iOS ui_hang watchdog.
enableNetworkTrackingtruehttp.request spans + traceparent injection.
enableLoggingtrueScoutFlutter.log*() calls become no-ops.
capturePrintStatementsfalse(Off by default) When true, mirrors print / debugPrint calls to OTLP logs. Original console output is preserved.

Filtering — beforeSend

ScoutFlutterConfig(
// …
beforeSend: (event) {
// event keys: 'type' ('span'|'metric'|'log'), 'name', plus
// per-span attributes. Return null to drop the event.
if ((event['http.url'] as String?)?.contains('/health') == true) {
return null;
}
event.remove('user.email');
return event;
},
)

Sees per-span attributes only. Resource attributes set on the OTel Resource (e.g. service.name, os.name, device.*) are not in the event payload.

Native crash setup

iOS — KSCrash + MetricKit

The plugin auto-installs KSCrash 2.5+ with all five monitors:

  • Mach exceptions
  • POSIX signals
  • C++ exceptions
  • NSException
  • Main-thread deadlock (5 s+ — complementary to the 250 ms iosHangThresholdMs watchdog)

On every launch, scout_flutter drains any persisted KSCrash reports from the previous run and emits them as native_crash spans carrying:

  • crash.reason, crash.type, crash.signal, crash.os_name, crash.os_version, crash.kernel
  • crash.registers_json — full CPU register dump including FAR and ESR
  • crash.mach_exception, crash.mach_code, crash.mach_subcode
  • crash.nsexception_name (when applicable)
  • crash.callstack_tree_json — symbolicated stack tree of every thread
  • crash.binary_images_json — loaded image list for offline symbolication
  • The prior session's last 20 breadcrumbs

In parallel, an MXMetricManagerSubscriber collects asynchronous MXCrashDiagnostic and MXHangDiagnostic payloads that Apple delivers the morning after a crash — useful for catching kernel-killed crashes that KSCrash couldn't intercept.

Triggering a crash (testing)

scout_flutter does not ship a "simulate crash" API. To validate end-to-end capture, trigger a real fault — and never use exit(), which is a graceful shutdown that no crash reporter intercepts:

// Uncaught Dart error → `error` span (and, if fatal, `app_crash` on relaunch).
throw StateError('test crash');

For a true native signal (SIGSEGV on Android, fatalError() on iOS), add a small platform-channel method on the app side. The repo's example/ app and the flutter/samples/platform_design sample ship ready-made crash / ANR / deadlock buttons for exactly this.

Android — NDK signal handler + ApplicationExitInfo

The plugin auto-installs a custom NDK signal handler that catches SIGSEGV / SIGABRT / SIGBUS / SIGILL / SIGFPE before they kill the process. A compact crash report (signal info, registers, stack, memory map, binary images with ELF build-ids) is persisted to disk and emitted on next launch as a native_crash span.

In parallel, on API 30+ (Android 11+), scout_flutter polls ActivityManager.getHistoricalProcessExitReasons and emits a native_crash span for any OS-recorded death newer than the persisted watermark — each death is reported exactly once across launches. Only crash-class exit reasons are reported (anr, jvm_crash, native_crash, low_memory); benign exits such as the user swiping the app away (user_requested), Force Stop (user_stopped), or a normal exit() (exit_self) are filtered out and never counted as crashes. Captured attributes:

  • crash.type / crash.reason
  • crash.subreason (API 31+ via reflection)
  • crash.exit_status, crash.importance, crash.death_timestamp_ms, crash.process_name, crash.pid, crash.pss_kb, crash.rss_kb
  • crash.tombstone — the OS's full thread dump with native frames (capped at 128 KB by default; tune with maxTombstoneBytes)

The two pipelines complement each other: NDK fires in-process at crash time, ApplicationExitInfo catches deaths that the OS killed before in-process handlers could write to disk (OOM, hard watchdog, etc.).

Background flush

scout_flutter calls forceFlush() on every signal provider when the app transitions to AppLifecycleState.paused / inactive / hidden. This drains the BatchSpanProcessor, metric reader, and log processor before the OS suspends the process — without it, events emitted in the last few seconds (the ones leading up to a crash) would die with the in-memory batch queue.

If the in-memory exporter still doesn't deliver in time (OS kills us mid-POST), the batch is dropped under the default at-most-once delivery. Enable the offline buffer (offlineBufferEnabled: true plus per-signal caps) to persist such batches to disk and replay them on next initialize().

WebView bridge

Embed a WebView showing a page instrumented with @base14/scout-react (web entry) v0.1.5+, and scout_flutter will flatten the WebView's RUM session into the native session — both runtimes share one session.id and user.anonymous_id, and the embedded page's spans flow back into the native pipeline tagged with span.source = "webview".

import 'package:scout_flutter/scout_flutter.dart';
import 'package:webview_flutter/webview_flutter.dart';

final controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (_) {
// Re-inject the shim on every navigation. The shim has a
// sentinel so re-injecting in the same page is a no-op.
ScoutWebViewBridge.injectShim(
runJavaScript: controller.runJavaScript,
);
},
),
);

ScoutWebViewBridge.attach(
addJavaScriptChannel: (name, onMessage) {
controller.addJavaScriptChannel(
name,
onMessageReceived: (m) => onMessage(m.message),
);
},
);

await controller.loadRequest(Uri.parse('https://app.example.com'));

The bridge:

  1. Registers a JavaScript channel (default name ScoutBridge) the page can postMessage into.
  2. Injects a JS shim that polls window.Scout and calls setWebViewBridge({sessionId, anonymousId, send}) once the page's web SDK appears.
  3. Receives bridged span payloads via the channel and re-emits them as native spans with span.source = "webview", session.id, user.anonymous_id, and the rest of the native common attributes.

The bridge is currently a parallel transport — both the web SDK's own OTLP exporter and the native bridge ship a copy of each span. Configure the web SDK with an unreachable endpoint, or filter everything with beforeSend returning null, to make the bridge the sole transport.

The bridge is generic — attach() accepts bare callbacks rather than a typed WebViewController, so it works with webview_flutter, flutter_inappwebview, or any future plugin. Adapt the integration glue (~6 lines) per your plugin of choice.

What happens when export fails

Delivery is at-most-once by default (maxRetries: 0): a batch gets one attempt, and a failed batch is dropped rather than risking a duplicate delivery (a retried timeout whose first request the collector already ingested would store the same events twice).

FailureWhat Scout does (defaults)
Any export failure, maxRetries: 0 (default)One attempt; batch dropped on failure. No duplicates, ever.
maxRetries > 0 configuredFailed batches are retried up to that many times — with the corresponding duplicate risk on ambiguous failures.
Failure with offlineBufferEnabled: true configuredBatch persisted to disk and replayed on next initialize() or connectivity change (single replay attempt).
Disk write fails (quota, permissions)Caught and swallowed; batch dropped.
App crash mid-batchThe background-flush hook drains buffers on paused; anything emitted after the last flush is lost with the process.

Running the example app

The repo ships a runnable example at example/. For physical-device testing of the full diagnostic suite (UI hang, ANR, real SIGSEGV crash, WebView bridge) the flutter/samples/platform_design sample is a good starting point.

# iOS simulator
cd example
flutter run -d "iPhone 17"

# Android emulator / physical device
flutter run -d <device-id>

# For physical Android: reverse-forward your local collector
adb reverse tcp:34318 tcp:34318

Troubleshooting

SymptomLikely cause + fix
ui_hang never fires on iOSiosHangThresholdMs: 0 disables it; check your config. Or the main thread genuinely isn't hanging — try a 300 ms while loop to confirm the watchdog is armed.
Two screen_view per navYou attached ScoutFlutter.navigatorObserver to both the root MaterialApp Navigator and a nested CupertinoTabView Navigator. That's correct — each Navigator emits its own screen_view. Filter dashboards by view.id to dedupe.
native_crash not appearing after iOS crashKSCrash writes asynchronously; the report drains on the next launch. Force-quit and relaunch the app, then check the collector log.
Android native_crash empty on API < 30ApplicationExitInfo requires API 30+. Older devices only get whatever the in-process NDK handler caught.
WebView spans not tagged span.source = webviewEither the embedded page isn't @base14/scout-react v0.1.5+ (no window.Scout), or your NavigationDelegate.onPageFinished is missing the injectShim(...) call.
Observer already has a Navigator assertionnavigatorObserver returns a fresh instance on every read, so attaching it to multiple Navigators is fine. If you hit this, you're caching one instance and reusing it — read ScoutFlutter.navigatorObserver afresh per Navigator.
HTTP requests not getting traceparentThe host isn't in firstPartyHosts. Add it explicitly (e.g. 'api.example.com') or use a wildcard ('*.example.com').
Crash button gives a graceful shutdown instead of SIGSEGVYou're calling exit(), which is graceful — no crash reporter intercepts it. Trigger a real fault instead (an uncaught error, or a native null-deref via your own platform-channel method).

Performance considerations

  • Unified 30 s batching. Spans, metrics, and logs each flush once per exportIntervalSeconds (default 30 s) — including logs, which are buffered and batched rather than posted per line.
  • Connection reuse. Each signal holds one keep-alive HTTP connection for the app session — TLS handshakes happen ~3 times per session, not once per export.
  • No metrics unless enabled. The default configuration ships zero metric data points; the vitals gauges and frame histograms are per-app opt-ins.
  • Zero disk usage by default. Offline buffering is off; the only disk writes are crash evidence (crash reports, breadcrumbs, session marker).
  • Sampling. sessionSampleRate drops full sessions — spans, metrics, and logs together — never individual events, so session traces stay coherent.
  • Async init. ScoutFlutter.initialize() is fire-and-forget. The app boot does not wait on it.

Security considerations

  • PII scrubbing. Use beforeSend to redact attributes (event.remove('user.email')) or drop entire events (return null). It runs synchronously on every span / metric / log before export.
  • Custom headers for auth. Pass headers: {'Authorization': 'Bearer …'} to authenticate the OTLP export. Headers are sent on every request including offline-replay POSTs.
  • No telemetry-to-disk PII by default. The offline buffer writes the same OTLP JSON your live exporter would have sent — it doesn't add anything extra. If you don't want sensitive attrs on disk, scrub them in beforeSend before the batch hits the buffer.
  • TLS. Set secure: true (default) or pass an explicit https:// endpoint. No CA pinning by default; if you need it, wrap the outbound HTTP client yourself.

FAQ

Does scout_flutter work on macOS / web / Linux / Windows desktop?

iOS and Android are fully supported, including native crash capture. macOS works for the Dart-side instrumentation (taps, navigation, HTTP, errors, lifecycle, logs) but the KSCrash / MetricKit / ApplicationExitInfo pipelines are mobile-only. Web works for Dart-side instrumentation — for richer web RUM, use @base14/scout-react directly in a web app and (for hybrid apps) bridge with the WebView bridge.

Will the SDK ever block my app's boot?

No. ScoutFlutter.initialize() is async and fire-and-forget — wrap it in unawaited(...) as shown above. If init fails (network down, disk full, etc.) the error is swallowed; your app keeps running.

How big are crash reports on the wire?

A KSCrash report with full register dump + callstack tree typically serializes to 30–80 KB. ApplicationExitInfo tombstones are capped at 128 KB by default (maxTombstoneBytes). They're sent as part of the next launch's first batch — each death exactly once.

Can I add custom spans?

Yes — the underlying OTel Tracer is accessible. Custom spans go through the same beforeSend / sampling / export pipeline as auto-instrumented ones.

Can I emit metrics or logs manually?

Yes:

ScoutFlutter.logInfo('checkout started', attributes: {'cart.size': 3});
ScoutFlutter.logError('payment failed', attributes: {'order.id': 'ord-1'});

// For an error with a stack trace, use reportError (emits an `error` span):
ScoutFlutter.reportError(e, st);

What's next

References

Was this page helpful?