Flutter SDK observability reference

This LaunchDarkly observability plugin is available for early access

This LaunchDarkly observability plugin is currently available in Early Access, and APIs are subject to change until a 1.x version is released.

This topic documents how to get started with the LaunchDarkly observability plugin for the Flutter SDK.

The launchdarkly_flutter_observability package provides error monitoring, logging, tracing, and session replay through a single public facade named LDObserve.

SDK quick links

LaunchDarkly SDKs are open source. In addition to this reference guide, we provide source, API reference documentation, and a sample application:

ResourceLocation
GitHub repository@launchdarkly/flutter observability
Sample applicationFlutter example app

Prerequisites and dependencies

This reference guide assumes you are familiar with the LaunchDarkly Flutter SDK.

The observability plugin requires the LaunchDarkly Flutter SDK version 4.18.0 or later.

The Flutter observability plugin is compatible with iOS, Android, and web platforms. Session replay is supported only on iOS and Android. Web session replay is not yet available.

Get started

Follow these steps to get started:

Install the package

Add both the LaunchDarkly Flutter SDK and the observability package to your pubspec.yaml:

pubspec.yaml
1dependencies:
2 launchdarkly_flutter_client_sdk: ^4.18.0
3 launchdarkly_flutter_observability: ^0.13.0

Then run:

Install dependencies
$flutter pub get

For iOS, install the native pod dependencies from your app’s ios/ directory:

iOS pod install
$cd ios && pod install && cd ..

No extra setup is required for Android. Gradle resolves the plugin automatically.

After you install the dependencies, import the packages into your code:

Import
1import 'package:launchdarkly_flutter_client_sdk/launchdarkly_flutter_client_sdk.dart';
2import 'package:launchdarkly_flutter_observability/launchdarkly_flutter_observability.dart';

Initialize observability

To initialize, you need your LaunchDarkly environment’s mobile key. This authorizes your application to connect to a particular environment within LaunchDarkly. To learn more, read Initialize the client in the Flutter SDK reference guide.

Flutter observability mobile and web builds use different credential types

The Flutter observability SDK uses a mobile key for iOS and Android builds. For web builds, use a client-side ID instead. Keys are specific to each project and environment. They are available on the SDK keys page under Settings. To learn more about key types, read Keys.

Mobile keys are not secret and you can expose them in your client-side code without risk. However, never embed a server-side SDK key into a client-side application.

There are two initialization variants. Use LDObserve.init if you are using the LaunchDarkly client, or LDObserve.initStandalone to initialize without a client.

Initialize with a LaunchDarkly client

Pass your LDClient to LDObserve.init. This registers the observability plugin on the client so feature flag evaluations are correlated with your telemetry:

Initialize with LDClient
1import 'dart:async';
2import 'package:flutter/material.dart';
3import 'package:launchdarkly_flutter_client_sdk/launchdarkly_flutter_client_sdk.dart';
4import 'package:launchdarkly_flutter_observability/launchdarkly_flutter_observability.dart';
5
6void main() {
7 runZonedGuarded(
8 () {
9 WidgetsFlutterBinding.ensureInitialized();
10
11 final client = LDClient(
12 LDConfig(
13 'example-mobile-key',
14 AutoEnvAttributes.enabled,
15 ),
16 LDContextBuilder().kind('user', 'example-context-key').build(),
17 );
18 client.start();
19
20 LDObserve.init(
21 client,
22 observability: const ObservabilityOptions(
23 serviceName: 'my-flutter-app',
24 ),
25 );
26
27 // Report errors caught by the Flutter framework
28 FlutterError.onError = (FlutterErrorDetails details) {
29 LDObserve.recordException(details.exception, stackTrace: details.stack);
30 };
31
32 runApp(const MyApp());
33 },
34 (err, stack) {
35 // Report uncaught errors from the zone
36 LDObserve.recordException(err, stackTrace: stack);
37 },
38 );
39}

Initialize standalone

To initialize without creating a LaunchDarkly client, pass your mobile key directly to LDObserve.initStandalone:

Initialize standalone
1await LDObserve.initStandalone(
2 'example-mobile-key',
3 observability: const ObservabilityOptions(
4 serviceName: 'my-flutter-app',
5 ),
6);

Configure plugin options

Pass an ObservabilityOptions object to LDObserve.init or LDObserve.initStandalone to configure observability behavior:

ObservabilityOptions
1LDObserve.init(
2 client,
3 observability: ObservabilityOptions(
4 serviceName: 'my-flutter-app',
5 // Recommended: set to the latest deployed git SHA or semantic version
6 serviceVersion: 'example-sha',
7 instrumentation: InstrumentationOptions(
8 networkRequests: true,
9 launchTimes: true,
10 debugPrint: DebugPrintSetting.releaseOnly(),
11 ),
12 ),
13);

These ObservabilityOptions configuration options are available on all platforms:

  • isEnabled: Enables or disables observability. Defaults to true.
  • serviceName: The service name for telemetry. Defaults to "observability-flutter".
  • serviceVersion: The service version, commonly a Git SHA or semantic version string. Defaults to "0.1.0".
  • instrumentation: An InstrumentationOptions object that controls automatic instrumentation. To learn more, read Instrumentation options.
  • otlpEndpoint: The OTLP endpoint for reporting OpenTelemetry data. Defaults to https://otel.observability.app.launchdarkly.com:4318. You do not need to change this for most configurations.
  • backendUrl: The LaunchDarkly back-end URL. Defaults to https://pub.observability.app.launchdarkly.com. You do not need to change this for most configurations.
  • contextFriendlyName: A display name to identify the user’s session in the observability UI.
  • attributes: A map of additional resource attributes to include in telemetry.

The following ObservabilityOptions configuration options are available only on Android and iOS:

  • customHeaders: Extra HTTP headers added to OTLP exports, for example for proxies or authentication. Defaults to {}.
  • sessionBackgroundTimeout: How long the app can stay in the background before the session ends. Defaults to 15 minutes.
  • logsApiLevel: The minimum severity of logs forwarded to the logs pipeline. Use ObservabilityLogLevel.none to disable logs. Defaults to ObservabilityLogLevel.info.
  • traces: A TracesOptions object that controls automatic trace generation. Toggles includeErrors and includeSpans. Both default to true.
  • metricsEnabled: Whether metrics are exported. Defaults to true.
  • analytics: An AnalyticsOptions object that controls telemetry for product analytics events. Use AnalyticsOptions.enabled to enable all types or AnalyticsOptions.disabled to disable all:
    • taps (Android and iOS): Emits a click span for each user tap. Defaults to true. Setting this to false also stops tap detection, rather than detecting taps without publishing them.
    • views (Android only): Emits a screen_view span for each screen view. Defaults to true.
    • trackEvents: Emits a track span when a custom event is tracked with track(). Defaults to true.
    • appLifecycle (Android and iOS): Emits app_foreground and app_background spans as the app moves between states. Defaults to true.
    • appLaunch (Android and iOS): Emits an app_launch span once per process launch, including an app.start span event with the cold or warm startup dimension. Defaults to true. To learn more, read Product analytics events.

For more information on plugin options, read Configuration for client-side observability.

Instrumentation options

The InstrumentationOptions class controls which automatic instrumentation features are active. Pass it to the instrumentation parameter of ObservabilityOptions.

InstrumentationOptions
1ObservabilityOptions(
2 instrumentation: InstrumentationOptions(
3 networkRequests: true,
4 launchTimes: true,
5 debugPrint: DebugPrintSetting.always(),
6 ),
7)

These instrumentation options are available on all platforms:

  • networkRequests: When true, automatically instruments HTTP network requests. Defaults to true.
  • launchTimes: When true, measures and reports application launch time. Defaults to true.
  • debugPrint: Controls whether debugPrint calls are automatically captured as log events:
    • DebugPrintSetting.releaseOnly() (the default) captures debugPrint calls only in release builds.
    • DebugPrintSetting.always() captures debugPrint calls in all build configurations. When enabled, debugPrint output does not appear in the Flutter console.
    • DebugPrintSetting.disabled() does not instrument debugPrint.

The following InstrumentationOptions configuration option is available only on Android and iOS:

  • crashReporting: When true, reports uncaught exceptions as errors. Defaults to true.

Intercept print statements

To capture the output from print statements, pass LDObserve.zoneSpecification() to runZonedGuarded:

Intercept print
1void main() {
2 runZonedGuarded(
3 () {
4 // Initialize and run your app
5 },
6 (err, stack) {
7 LDObserve.recordException(err, stackTrace: stack);
8 },
9 zoneSpecification: LDObserve.zoneSpecification(),
10 );
11}

Map contexts to friendly names

Use contextFriendlyName to set a human-readable display name for the user’s session when displayed in the observability User Interface (UI):

contextFriendlyName
1ObservabilityOptions(
2 contextFriendlyName: 'Bob Smith',
3)

Configure product analytics event collection

On Android and iOS, the observability SDK for Flutter can record the following product analytics events as OpenTelemetry spans:

  • Track events (manual, all platforms): A track span recorded when your code calls LDObserve.track(). The trackEvents flag in AnalyticsOptions controls whether the span is emitted. To learn more, read Recording product analytics events.
  • Taps (automatic, Android and iOS): A click span for each user tap, with details about the tap location. Enabled by default. To learn more, read Product analytics on mobile.
  • App lifecycle (automatic, Android and iOS): An app_foreground or app_background span as the app moves between the foreground and background states. Enabled by default.
  • App launches (automatic, Android and iOS): An app_launch span once per process launch, with the launch type and version information, plus an app.start span event that records the cold or warm startup dimension. Enabled by default. To learn more, read App launch events.
  • Screen views (automatic, Android only): A screen_view span when the app shows a screen, with the screen name and optional details such as the previous screen, screen class, screen identifier, and category. The views option is a no-op on iOS. To learn more, read Screen view events.

All product analytics span events also include information about the LaunchDarkly context that generated the event.

Use the generated span events to create custom product analytics charts, such as time series and funnels. To learn more, read Product analytics events.

To enable all compatible product analytics events, set analytics to AnalyticsOptions.enabled in ObservabilityOptions:

Enable all product analytics events
1ObservabilityOptions(
2 analytics: AnalyticsOptions.enabled,
3)

To enable or disable individual event types, use AnalyticsOptions instead:

Enable individual event types
1ObservabilityOptions(
2 analytics: AnalyticsOptions(
3 trackEvents: true, // all platforms
4 taps: true, // Android and iOS
5 views: true, // Android only
6 appLifecycle: true, // Android and iOS
7 appLaunch: true, // Android and iOS
8 ),
9)
Default values for product analytics

Track event, tap, screen view, app lifecycle, and app launch collection are all enabled by default. Use AnalyticsOptions.disabled to disable all product analytics spans at once.

Configure session replay

Session replay is in Early Access

Session replay for Flutter is available in Early Access. APIs are subject to change until a 1.x version is released.

Session replay captures screen recordings of user actions to help you understand how users interact with your application. It is included in the launchdarkly_flutter_observability package and uses native iOS and Android libraries to capture and upload recordings.

Session replay for Flutter is supported only on iOS and Android. Web session replay is not yet available.

Initialize session replay

To enable session replay, pass a SessionReplayOptions object to the replay parameter of LDObserve.init or LDObserve.initStandalone, and wrap your app in SessionReplayCapture:

Initialize with session replay
1import 'dart:async';
2import 'package:flutter/material.dart';
3import 'package:launchdarkly_flutter_client_sdk/launchdarkly_flutter_client_sdk.dart';
4import 'package:launchdarkly_flutter_observability/launchdarkly_flutter_observability.dart';
5
6void main() {
7 runZonedGuarded(
8 () {
9 WidgetsFlutterBinding.ensureInitialized();
10
11 final client = LDClient(
12 LDConfig(
13 'example-mobile-key',
14 AutoEnvAttributes.enabled,
15 ),
16 LDContextBuilder().kind('user', 'example-context-key').build(),
17 );
18 client.start();
19
20 LDObserve.init(
21 client,
22 observability: const ObservabilityOptions(
23 serviceName: 'my-flutter-app',
24 ),
25 replay: const SessionReplayOptions(
26 isEnabled: true,
27 privacy: PrivacyOptions(maskTextInputs: true),
28 ),
29 );
30
31 FlutterError.onError = (FlutterErrorDetails details) {
32 LDObserve.recordException(details.exception, stackTrace: details.stack);
33 };
34
35 // Wrap your app in SessionReplayCapture to enable screen recording
36 runApp(const SessionReplayCapture(child: MyApp()));
37 },
38 (err, stack) {
39 LDObserve.recordException(err, stackTrace: stack);
40 },
41 );
42}

SessionReplayCapture widget

Wrap your top-level widget in SessionReplayCapture to allow the native session replay library to capture screen content. On web, SessionReplayCapture is a no-op pass-through, so it is safe to use on every platform:

SessionReplayCapture
1runApp(const SessionReplayCapture(child: MyApp()));

Session replay configuration options

Pass a SessionReplayOptions object to LDObserve.init or LDObserve.initStandalone to control session replay behavior:

SessionReplayOptions
1const SessionReplayOptions(
2 isEnabled: true,
3 privacy: PrivacyOptions(
4 maskTextInputs: true,
5 maskWebViews: false,
6 maskLabels: false,
7 maskImages: false,
8 ),
9)

If you omit the replay parameter, session replay does not start. The isEnabled default applies only to the SessionReplayOptions object you pass in.

These SessionReplayOptions configuration options are available on all platforms:

  • isEnabled: Controls whether session recording starts. Defaults to true.
  • privacy: A PrivacyOptions object that controls which UI elements are masked. To learn more, read Privacy options.
  • serviceName: The service name reported for session replay telemetry. Defaults to "sessionreplay-flutter".

The following SessionReplayOptions configuration options are available only on Android and iOS:

  • frameRate: Target capture rate in frames per second. Defaults to 1.0.
  • scale: The resolution multiplier for captured frames, where 1.0 is 1x (160 DPI) and 2.0 is 2x. Higher values capture more detail but produce larger frames. A null value is treated as 1.0. Defaults to 1.0.

Privacy options

Use PrivacyOptions to control which elements are masked in session replay recordings. By default, text inputs are masked to protect user data.

PrivacyOptions
1const PrivacyOptions(
2 maskTextInputs: true, // default — masks all text inputs
3 maskWebViews: false, // when true, masks WebView content
4 maskLabels: false, // when true, masks all text labels
5 maskImages: false, // when true, masks all images
6 minimumAlpha: 0.02, // views below this alpha are not captured (iOS only)
7)

The available privacy options are:

  • maskTextInputs: Masks all text input fields. Defaults to true.
  • maskWebViews: Masks the contents of web views. When enabled, web views appear as blank rectangles in recordings. Defaults to false.
  • maskLabels: Masks all text labels. Defaults to false.
  • maskImages: Masks all images. Defaults to false.
  • minimumAlpha: (iOS only) Minimum alpha value for a view to be captured. Views with a lower alpha are not recorded. Defaults to 0.02.

LaunchDarkly applies masks to every captured frame. Masks follow their widgets through scrolling, transforms, and animations. If a mask cannot be placed reliably in a frame, LaunchDarkly drops that frame rather than risk exposing unmasked content.

Per-widget masking

In addition to screen-wide PrivacyOptions, you can redact individual widgets using LDMask, LDIgnore, and LDUnmask.

Use LDMask to redact a widget’s subtree in all captured frames:

LDMask
1LDMask(
2 child: Text(creditCardNumber),
3)

Use LDIgnore to exclude a subtree from session replay entirely. In Flutter, LDIgnore behaves like LDMask and paints over the region in every captured frame, so its contents never appear in a recording:

LDIgnore
1LDIgnore(
2 child: VideoPlayer(controller),
3)

Use LDUnmask to exempt a subtree from global masking rules such as maskTextInputs. For example, to reveal one non-sensitive field on a page where every input is masked:

LDUnmask
1// maskTextInputs masks every field; reveal just this one
2LDUnmask(
3 child: TextField(controller: searchController),
4)

LDUnmask only overrides global masking. It does not override an explicit LDMask or LDIgnore. An LDUnmask nested inside either one stays masked, because an explicit per-widget mask always takes precedence.

LDMask, LDIgnore, and LDUnmask are active on iOS and Android. On web they render their child unchanged.

Mask by widget type or key

When wrapping widgets is not convenient, name them once in PrivacyOptions by their Type or Key. LaunchDarkly resolves these rules on the Flutter side and does not send them to the native SDKs:

Mask by widget type or key
1PrivacyOptions(
2 maskWidgetTypes: {CreditCardField},
3 maskWidgetKeys: {const ValueKey('ssn-field')},
4 unmaskWidgetTypes: {SearchBox},
5 ignoreWidgetTypes: {LiveCameraPreview},
6)

The available widget matching options are:

  • maskWidgetTypes: A set of widget Types to mask wherever they appear. Defaults to an empty set.
  • maskWidgetKeys: A set of widget Keys to mask wherever they appear. Defaults to an empty set.
  • unmaskWidgetTypes: A set of widget Types to reveal from global masking. Defaults to an empty set.
  • unmaskWidgetKeys: A set of widget Keys to reveal from global masking. Defaults to an empty set.
  • ignoreWidgetTypes: A set of widget Types to ignore. Defaults to an empty set.
  • ignoreWidgetKeys: A set of widget Keys to ignore. Defaults to an empty set.

These rules follow the same precedence as the wrapper widgets. A mask or ignore match takes precedence over an unmask match.

To learn more about session replay configuration, read Configuration for session replay.

Manual instrumentation

After initializing the observability plugin, use LDObserve to manually instrument your Flutter application with custom logs, traces, and product analytics events.

Recording custom logs

Use LDObserve.recordLog to emit a structured log. severity is a plain string. Common levels are trace, debug, info, warn, error, and fatal. properties is a plain Dart map with no LaunchDarkly or OpenTelemetry types required:

Record logs
1// Record a basic log message
2LDObserve.recordLog(
3 'User login successful',
4 severity: 'info',
5);
6
7// Record a log with custom properties
8LDObserve.recordLog(
9 'Authentication completed',
10 severity: 'info',
11 properties: <String, Object?>{
12 'user_id': '12345',
13 'action': 'login',
14 },
15);

Recording custom traces

Use LDObserve.startSpan to create a span for tracing an operation. Spans nest automatically under the currently active span. Always end spans when the operation completes:

Record traces
1// Start a span with custom properties
2final span = LDObserve.startSpan(
3 'database_query',
4 properties: <String, Object?>{
5 'table': 'users',
6 'operation': 'select',
7 },
8);
9
10// Perform your operation
11await performDatabaseQuery();
12
13// Always end the span
14span.end();

To set the span’s kind, pass the kind parameter to startSpan. SpanKind supports internal, which is the default, along with client, server, producer, and consumer.

Each span supports these methods:

  • setAttribute(name, value): Sets a single attribute on the span.
  • setAttributes(map): Sets multiple attributes on the span.
  • addEvent(name, {attributes}): Records a named event on the span.
  • setStatus(SpanStatusCode): Sets the span status to ok, error, or unset.
  • recordException(exception, {stackTrace, attributes}): Records an exception on the span.
  • end(): Ends the span.

To record spans independently instead of nesting them, end each span before you start the next:

Sequential spans
1final span1 = LDObserve.startSpan('SequentialOperation1');
2span1.setAttribute('sequence', '1');
3span1.end();
4
5final span2 = LDObserve.startSpan('SequentialOperation2');
6span2.setAttribute('sequence', '2');
7span2.end();

Attribute and property values

Attributes and properties are plain Dart values, so no LaunchDarkly or OpenTelemetry types are required. A value can be a String, int, double, bool, or a homogeneous list of any of those types. The SDK ignores values it cannot represent as an attribute, such as nested maps or mixed-type lists.

The recordLog, recordException, startSpan, and track methods all accept a properties map of these same plain values.

Recording product analytics events

Use LDObserve.track to record a custom event as a product analytics span:

Track a custom event
1// Track an event with properties and an optional metric value
2LDObserve.track(
3 'purchase_completed',
4 properties: <String, Object?>{
5 'product_id': 'SKU-123',
6 'price': 29.99,
7 },
8 metricValue: 29.99,
9);
10
11// Track an event with no properties
12LDObserve.track('button_tapped');

Shut down observability

Call LDObserve.shutdown() to shut down observability. You cannot restart observability after you shut it down:

Shut down observability
1LDObserve.shutdown();

Identify contexts

To tie observability data to the correct context, use the LaunchDarkly client to identify or switch contexts:

Identify a context
1final userContext = LDContextBuilder()
2 .kind('user', 'user-key')
3 .name('Bob Smith')
4 .build();
5await client.identify(userContext);

You do not need to call any LDObserve method. The observability plugin hooks into the LaunchDarkly client and, on Android and iOS, forwards each completed identify to the native observability SDK and to session replay. This attributes subsequent LDObserve.track events to the active context and records the context on the active session replay recording.

Explore supported features

The observability plugin supports the following features. After the SDK and plugins are initialized, you can access these from within your application:

Review observability data in LaunchDarkly

After you initialize the SDK and observability plugin, your application automatically starts sending observability data back to LaunchDarkly, including errors and logs. You can review this information in the LaunchDarkly user interface. To learn how, read Observability.