Getting started with observability for .NET MAUI

This guide shows how to add LaunchDarkly observability to a .NET MAUI application targeting Android and iOS. Because MAUI is a single C# codebase that compiles to both platforms, one path serves both targets. Platform-specific differences are called out inline as you reach them. By the end, you’ll have the SDK and plugins installed, observability and session replay turned on, and confirmation that data is flowing.

This SDK is in early access

The MAUI observability plugin is in early access. Its APIs may change before the 1.x release.

The .NET MAUI SDK supports two complementary telemetry plugins:

  • Observability captures errors, crashes, logs, and traces. Use it to debug failures in production, watch error rates for a release, and trace network calls end-to-end. Observability automatically reports uncaught exceptions.
  • Session replay captures UI snapshots of what a user encountered. Use it when a stack trace alone doesn’t tell you why a user got stuck, and pair it with masking to keep sensitive fields out of the recording.

Both plugins install alongside the LaunchDarkly .NET MAUI SDK and initialize in the same place. To learn more about every configuration field, the full LDObserve method surface, and advanced distributed-tracing patterns, read the .NET MAUI SDK observability reference.

Prerequisites

To complete this guide, you need:

  • .NET 9.0 or .NET 10.0 with the MAUI workload installed.
  • A target device or emulator: Android API level 24+ (Android 7.0, Nougat), or iOS 13.2+.
  • A LaunchDarkly account and a mobile key for the environment you’re testing against. Find the mobile key on the SDK keys page under Settings.

Step 1: Install the SDK

Add the LaunchDarkly.SessionReplay package to your MAUI project:

Install the SDK
$dotnet add package LaunchDarkly.SessionReplay

Then run a clean build before anything else:

Clean build
$dotnet restore
$dotnet clean
$dotnet build
Don't skip the clean

Stale build artifacts cause hard-to-diagnose failures, especially on iOS targets. If you hit dependency-resolution errors, running dotnet restore first usually clears them.

Visual Studio on Windows (Android target)

If you build for Android from Visual Studio on Windows, the Android SDK may land in a system-protected directory the build can’t write to, causing build errors. The fix is to relocate the Android SDK to a user-writable location. For step-by-step instructions, read the Android SDK relocation guide for Windows. This only affects Android-on-Windows.

Import the namespaces where you’ll initialize the SDK:

Import namespaces
1using LaunchDarkly.SessionReplay;
2using LaunchDarkly.Sdk;
3using LaunchDarkly.Sdk.Client;
4using LaunchDarkly.Sdk.Client.Integrations;
5using LaunchDarkly.Observability;

Step 2: Initialize the SDK

Initialization belongs in MauiProgram.cs, alongside the rest of your MauiApp.CreateBuilder() configuration. This example references LDConfig.MobileKey. LDConfig is a small static class that holds the mobile key. Replace that reference with your own constant or with an inline string literal:

MauiProgram.cs
1using LaunchDarkly.Observability;
2using LaunchDarkly.Sdk;
3using LaunchDarkly.Sdk.Client;
4using LaunchDarkly.Sdk.Client.Integrations;
5using mood_tracker.Observability;
6
7namespace mood_tracker;
8
9public static class MauiProgram
10{
11 public static MauiApp CreateMauiApp()
12 {
13 var builder = MauiApp.CreateBuilder();
14 builder.UseMauiApp<App>();
15
16 // … fonts, services, view models, pages …
17
18 var ldConfig = Configuration.Builder(
19 LDConfig.MobileKey,
20 ConfigurationBuilder.AutoEnvAttributes.Enabled)
21 .Plugins(new PluginConfigurationBuilder()
22 .Add(new ObservabilityPlugin(new ObservabilityOptions(
23 isEnabled: true,
24 serviceName: "maui")))
25 ).Build();
26
27 var context = Context.New("demo-user");
28 LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));
29
30 return builder.Build();
31 }
32}

The serviceName is how this app appears in the LaunchDarkly UI, so make it recognizable.

Step 3: Turn on session replay

Add the SessionReplayPlugin to the plugin list alongside ObservabilityPlugin:

Add session replay plugin
1var ldConfig = Configuration.Builder(
2 LDConfig.MobileKey,
3 ConfigurationBuilder.AutoEnvAttributes.Enabled)
4 .Plugins(new PluginConfigurationBuilder()
5 .Add(new ObservabilityPlugin(new ObservabilityOptions(
6 isEnabled: true,
7 serviceName: "maui")))
8 .Add(new SessionReplayPlugin(new SessionReplayOptions(
9 isEnabled: true)))
10 ).Build();

Step 4: Decide what to mask

Masking is configured through PrivacyOptions on SessionReplayOptions. There are several configuration options depending on your use case.

They are:

  • Production: Mask everything, including labels, images, and web views.
  • Local debugging: Turn masking off on your own test data so you can see what’s going on.
  • Balanced: Mask inputs and web views (passwords, payment forms, and similar) but keep ordinary labels and images visible.
1var privacy = new SessionReplayOptions.PrivacyOptions(
2 maskTextInputs: true,
3 maskWebViews: true,
4 maskLabels: true,
5 maskImages: true
6);

The simple rule is to start fully masked, and only unmask what you’ve confirmed is safe to record.

A MAUI session replay showing a login screen where the password entry is rendered as a solid block while surrounding labels remain visible, demonstrating text-input masking.

A session replay with masking applied: the password field is captured as a blank block while non-sensitive labels remain readable.

Mask a specific element

When a preset is almost right but one field is the exception, override it at the element level. Call .LDMask() on the underlying view after you have a reference to it, or call .LDUnmask() to reveal something the preset would hide:

Per-view masking
1var passwordEntry = new Entry { Placeholder = "Password", IsPassword = true };
2passwordEntry.LDMask();
3
4var publicInfoLabel = new Label { Text = "Public information" };
5publicInfoLabel.LDUnmask();
Timing matters

LDMask() and LDUnmask() reach the underlying view, so the view’s handler must be attached first. Call them after the view is in the visual tree, for example in a page’s Appearing event, not in a constructor.

Step 5: Run it and confirm data is arriving

Build and run on a device or emulator:

Build and run
$dotnet build -t:Run -f net9.0-android # or your iOS target framework

Test out the app by navigating between a couple of screens. Trigger an error deliberately if you want to verify that Observability captures it correctly.

Then open the Observability page in the LaunchDarkly UI. Logs and errors attributed to your serviceName appear there, as well as a session replay recording you can scrub through with your masking choices applied. When your service name appears in Observability, that is a confirmation that the SDK initialized successfully and is recording data.

The LaunchDarkly observability view listing logs and errors from a MAUI app, attributed to the service name set during initialization.

Logs and errors arriving in LaunchDarkly, attributed to the serviceName you set in Step 2.

The LaunchDarkly session replay player showing a recorded MAUI session with a scrub bar and timeline.

A recorded session in the replay player. You can scrub the timeline to watch what the user did leading up to an error, with your masking choices applied throughout.

After data shows up on one target, the other target reports the same way after you build and run it there.

Complete code sample

Here is a MauiProgram.cs showing both plugins wired into MAUI’s startup. LDConfig is a small static class that holds the MobileKey constant. The privacy block uses the SDK default (mask text inputs only). Swap in any preset from Step 4 to change what session replay records:

MauiProgram.cs (complete)
1using LaunchDarkly.Observability;
2using LaunchDarkly.Sdk;
3using LaunchDarkly.Sdk.Client;
4using LaunchDarkly.Sdk.Client.Integrations;
5using LaunchDarkly.SessionReplay;
6using mood_tracker.Observability;
7
8namespace mood_tracker;
9
10public static class MauiProgram
11{
12 public static MauiApp CreateMauiApp()
13 {
14 var builder = MauiApp.CreateBuilder();
15 builder.UseMauiApp<App>();
16
17 // … fonts and other MAUI configuration …
18
19 var ldConfig = Configuration.Builder(
20 LDConfig.MobileKey,
21 ConfigurationBuilder.AutoEnvAttributes.Enabled)
22 .Plugins(new PluginConfigurationBuilder()
23 .Add(new ObservabilityPlugin(new ObservabilityOptions(
24 isEnabled: true,
25 serviceName: "maui")))
26 .Add(new SessionReplayPlugin(new SessionReplayOptions(
27 isEnabled: true,
28 privacy: new SessionReplayOptions.PrivacyOptions(
29 maskTextInputs: true,
30 maskWebViews: false,
31 maskLabels: false,
32 maskImages: false))))
33 ).Build();
34
35 var context = Context.New("demo-user");
36 LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));
37
38 // … register your own services, view models, and pages …
39
40 return builder.Build();
41 }
42}

What to explore next

  • Every configuration field: The complete ObservabilityOptions and SessionReplayOptions tables are in the SDK reference documentation and in Configuration for client-side observability.
  • Custom tracing: Time your own operations with LDObserve spans. The reference’s distributed-tracing section covers root spans, nesting, cross-thread context, and automatic HttpClient instrumentation.
  • How this relates to OpenTelemetry: The plugin is an OpenTelemetry implementation under the hood. If you already use System.Diagnostics.Activity, LDObserve has StartActivity, StartRootActivity, and GetActivitySource equivalents so your existing trace code flows in.
  • Working with your data: After telemetry is flowing, set up alerts and dashboards, then investigate issues with Vega.

Troubleshooting

Use this section to investigate issues.

No data appearing

  • Confirm you pasted the mobile key for the environment you’re viewing in the UI, and not for a different environment.
  • Make sure plugin registration runs in MauiProgram.cs and that the configured builder is actually returned from CreateMauiApp().
  • Test the app a bit more and refresh. Data isn’t always instantaneous, so it may take a few minutes to display correctly.

Session replay isn’t recording, but observability works

  • Confirm SessionReplayPlugin is added to the PluginConfigurationBuilder, not just ObservabilityPlugin.
  • If you set isEnabled to false on SessionReplayOptions for consent-gated rollout, confirm the call that enables it is actually running.

Build failures

  • Run dotnet clean and rebuild, especially on iOS.
  • On Windows building for Android, check the Android SDK relocation note in Step 1.

Masking not applied to a specific view

  • Confirm .LDMask() or .LDUnmask() is called after the view’s handler is attached, for example in Appearing, not in a constructor.

Conclusion

In this guide, you instrumented a .NET MAUI application for LaunchDarkly observability. You can now:

  • Capture errors, logs, and session replays from both Android and iOS with one C# codebase
  • Control what session replay records through PrivacyOptions presets and per-view masking
  • Confirm telemetry is flowing in the LaunchDarkly UI, ready for alerts, dashboards, and Vega
Want to know more? Start a trial.

Your 14-day trial begins as soon as you sign up. Get started in minutes using the in-app Quickstart. You’ll discover how easy it is to release, monitor, and optimize your software.

Want to try it out? Start a trial.