.NET MAUI 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.

Overview

This topic documents how to get started with the LaunchDarkly observability and session replay SDK for .NET MAUI.

The .NET MAUI SDK supports observability for error monitoring and logging, and session replay for capturing UI snapshots. It provides cross-platform support for both Android and iOS through .NET MAUI’s native platform bindings.

SDK quick links

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

ResourceLocation
GitHub repository

LaunchDarkly.SessionReplay

Published packageNuGet

Prerequisites and dependencies

This SDK requires:

  • .NET 9.0 or .NET 10.0 with the MAUI workload installed
  • Android SDK version 24 or higher (Android 7.0, Nougat)
  • iOS 13.2 or higher

Get started

Follow these steps to get started:

Install the SDK

Add the LaunchDarkly.SessionReplay NuGet package to your MAUI project:

$dotnet add package LaunchDarkly.SessionReplay

After installing, run dotnet clean before building. This is especially important for iOS targets, as stale build artifacts can cause issues. If you encounter dependency resolution problems, run dotnet restore first:

.NET CLI
$dotnet restore
$dotnet clean
$dotnet build
Visual Studio with Android SDK on Windows

Visual Studio on Windows can have issues installing the SDK due to the Android SDK being in a system-protected directory. If you encounter build errors related to Android SDK access, you may need to move the Android SDK to a user-writable location.

For detailed instructions, read the Android SDK relocation guide for Windows.

Then, import the SDK namespaces in your code:

C#
1using LaunchDarkly.SessionReplay;
2using LaunchDarkly.Sdk.Client;
3using LaunchDarkly.Sdk.Client.Integrations;
4using LaunchDarkly.Observability;

Initialize the SDK

Initialize the SDK by registering the ObservabilityPlugin and SessionReplayPlugin as plugins on the LaunchDarkly client configuration. You should call this during your app’s startup, typically in MauiProgram.cs.

The observability and session replay features are registered as plugins using the PluginConfigurationBuilder. This integrates them directly with the LaunchDarkly client lifecycle.

.NET MAUI observability SDK credentials

The .NET MAUI observability SDK uses a mobile key. Keys are specific to each project and environment. They are available from Project settings, on the Environments list. 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.

Here’s how to initialize the SDK:

MauiProgram.cs
1using LaunchDarkly.Observability;
2using LaunchDarkly.Sdk;
3using LaunchDarkly.Sdk.Client;
4using LaunchDarkly.Sdk.Client.Integrations;
5
6public static class MauiProgram
7{
8 public static MauiApp CreateMauiApp()
9 {
10 var builder = MauiApp.CreateBuilder();
11
12 // ... other configuration ...
13
14 var mobileKey = "example-mobile-key";
15
16 var ldConfig = Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Enabled)
17 .Plugins(new PluginConfigurationBuilder()
18 .Add(new ObservabilityPlugin(new ObservabilityOptions(
19 isEnabled: true,
20 serviceName: "my-maui-app"
21 )))
22 ).Build();
23
24 var context = Context.New("example-user-key");
25 var client = LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));
26
27 return builder.Build();
28 }
29}

Configure observability options

You can customize the observability behavior by passing an ObservabilityOptions object to ObservabilityPlugin.Builder().

Here is an example:

Observability options
1var ldConfig = Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Enabled)
2 .Plugins(new PluginConfigurationBuilder()
3 .Add(new ObservabilityPlugin(new ObservabilityOptions(
4 isEnabled: true,
5 serviceName: "my-maui-app"
6 )))
7 .Add(new SessionReplayPlugin(new SessionReplayOptions(
8 isEnabled: true
9 )))
10 ).Build();

The available ObservabilityOptions configuration options are:

  • ServiceName: The service name for the application. Defaults to "observability-maui".
  • ServiceVersion: The version of the service. Defaults to "0.1.0".
  • OtlpEndpoint: The endpoint URL for the OTLP exporter. Defaults to LaunchDarkly’s endpoint.
  • BackendUrl: The backend URL for the observability service. Defaults to LaunchDarkly’s backend.
  • ContextFriendlyName: An optional friendly name for the user context, displayed in the LaunchDarkly UI.

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

Configure session replay

The .NET MAUI SDK supports session replay, which captures snapshots of your app’s UI at regular intervals. This allows you to visually review user sessions in LaunchDarkly to better understand user behavior and diagnose issues.

Session replay is configured through the SessionReplayOptions object passed to SessionReplayPlugin.Builder().

Here’s how to configure session replay:

Session replay options
1var ldConfig = Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Enabled)
2 .Plugins(new PluginConfigurationBuilder()
3 .Add(new ObservabilityPlugin(new ObservabilityOptions(
4 isEnabled: true,
5 serviceName: "my-maui-app"
6 )))
7 .Add(new SessionReplayPlugin(new SessionReplayOptions(
8 isEnabled: true,
9 privacy: new SessionReplayOptions.PrivacyOptions(
10 maskTextInputs: true,
11 maskWebViews: false,
12 maskLabels: false
13 )
14 )))
15 ).Build();

The available SessionReplayOptions configuration options are:

  • IsEnabled: Enables or disables session replay. Defaults to true.
  • ServiceName: The service name for session replay telemetry. Defaults to "sessionreplay-dotnet".
  • Privacy: Controls how UI elements are masked in the replay. To learn more, read Configure session replay privacy options.

Configure session replay privacy options

The PrivacyOptions class controls how UI elements are masked during session replay. By default, text inputs are masked to protect user privacy.

Here’s how to configure privacy settings:

Privacy options
1var replay = new SessionReplayOptions(
2 isEnabled: true,
3 privacy: new SessionReplayOptions.PrivacyOptions(
4 maskTextInputs: true,
5 maskWebViews: false,
6 maskLabels: false,
7 maskImages: false,
8 minimumAlpha: 0.02
9 )
10);

The available privacy options are:

  • MaskTextInputs: When true, masks all text input fields. Defaults to true.
  • MaskWebViews: When true, masks the contents of web views. Web views are rendered as blank rectangles in session replays. Defaults to false.
  • MaskLabels: When true, masks all text labels. Defaults to false.
  • MaskImages: When true, masks all images. Defaults to false.
  • MinimumAlpha: Minimum alpha value for view visibility in recordings. Views with alpha below this threshold are not captured. Defaults to 0.02.

Common privacy configurations

For maximum privacy (recommended for production):

Maximum privacy
1var privacy = new SessionReplayOptions.PrivacyOptions(
2 maskTextInputs: true,
3 maskWebViews: true,
4 maskLabels: true,
5 maskImages: true
6);

For debugging or development, you can turn masking off:

No masking
1var privacy = new SessionReplayOptions.PrivacyOptions(
2 maskTextInputs: false,
3 maskWebViews: false,
4 maskLabels: false,
5 maskImages: false
6);

For selective masking, you can mask inputs but show regular text and images:

Selective masking
1var privacy = new SessionReplayOptions.PrivacyOptions(
2 maskTextInputs: true,
3 maskWebViews: true,
4 maskLabels: false,
5 maskImages: false
6);

For more information on session replay configuration, read Configuration for session replay.

Use custom masking

In addition to the privacy profile settings, you can explicitly mask or unmask individual MAUI views using the LDMask() and LDUnmask() extension methods. This gives you fine-grained control over which UI elements are captured in session replay recordings.

Masking individual views
1using LaunchDarkly.SessionReplay;
2
3// Mask a sensitive field in session replay
4var passwordEntry = new Entry { Placeholder = "Password", IsPassword = true };
5passwordEntry.LDMask();
6
7// Unmask a field that would otherwise be masked by privacy settings
8var publicInfoLabel = new Label { Text = "Public information" };
9publicInfoLabel.LDUnmask();
Handler must be attached

The LDMask() and LDUnmask() methods work by accessing the underlying native platform view. The MAUI view’s handler must be attached before calling these methods. Call them after the view has been added to the visual tree, such as in a page’s Appearing event or after Handler is set.

The .LDMask() and .LDUnmask() extension methods work on any MAUI View, and they apply the masking to the underlying native view on both Android and iOS.

Explore supported features

The .NET MAUI observability SDK supports the following features. After the SDK is initialized, you can access these from within your application:

Using .NET System.Diagnostics

As with other LaunchDarkly observability SDK plugins, the .NET MAUI observability plugin supports OpenTelemetry operations using the following span methods.

MethodDescriptionReturn value
LDObserve.StartActiveSpan(name)Uses the current active span as its parent.TelemetrySpan
LDObserve.StartActiveSpan(name, parentContext)Uses the provided SpanContext as its parent.TelemetrySpan
LDObserve.StartActiveSpan(name, kind, parentContext)Uses the provided SpanContext with a custom kind as its parent.TelemetrySpan
LDObserve.StartRootSpan(name)Creates a new trace.TelemetrySpan
LDObserve.GetTracer()Returns an OpenTelemetry tracer.Tracer

If your .NET application already uses Microsoft’s System.Diagnostics API, LaunchDarkly’s observability plugin provides the following methods help you continue using your existing trace code.

MethodDescriptionReturn value
LDObserve.StartActivity(name)Uses the current activity as its parent.Activity?
LDObserve.StartRootActivity(name)Creates a new trace.Activity?
LDObserve.GetActivitySource()Returns an ActivitySource to create and start Activity objects.ActivitySource

Here’s an example of using the LaunchDarkly observability plugin with the System.Diagnostics API:

Using observability with the .NET System.Diagnostics API
1using System.Diagnostics;
2
3using var activity = LDObserve.StartActivity("api_request");
4activity?.SetTag("endpoint", "/api/users");
5activity?.SetTag("method", "GET");
6activity?.AddEvent(new ActivityEvent("cache.miss"));
7
8// Create a root activity (no parent), equivalent to StartRootSpan in OpenTelemetry
9using var root = LDObserve.StartRootActivity("independent-operation");
10root?.SetTag("sequence", "1");
11
12// Or, get the ActivitySource directly for full control
13var source = LDObserve.GetActivitySource();
14using var custom = source.StartActivity("custom", ActivityKind.Client);

Distributed tracing

To implement distributed tracing, all spans generated across different function calls and services for the same user request must share the same context. This section shows techniques for nesting spans and propagating span contexts across services when using the .NET MAUI observability SDK.

Creating a root span

Creating a root span creates a new trace regardless of any current span context that might exist. Use StartRootSpan to create your initial trace and span context.

Creating a root span
1using var span = LDObserve.StartRootSpan("app-cold-start");
2span.SetAttribute("launch_type", "cold");
3span.SetAttribute("device_model", DeviceInfo.Model);
4span.AddEvent("splash_rendered");
5
6// ... initialization work ...
7
8span.AddEvent("home_screen_ready");

Nesting spans

Use StartActiveSpan to create new spans using the parent span context. This pattern enables you to trace multi-step operations in the same application.

The following example nests all newly-created spans under LoadProducts. The DeserializeJson span is further nested under FetchFromApi.

Creating nested spans
1private async Task LoadProductsAsync()
2{
3 using var loadSpan = LDObserve.StartActiveSpan("LoadProducts");
4
5 List<Product> products;
6 using (var fetchSpan = LDObserve.StartActiveSpan("FetchFromApi"))
7 {
8 var response = await _httpClient.GetAsync("https://api.example.com/products");
9 fetchSpan.SetAttribute("http.status_code", (int)response.StatusCode);
10 var json = await response.Content.ReadAsStringAsync();
11
12 using var parseSpan = LDObserve.StartActiveSpan("DeserializeJson");
13 products = JsonSerializer.Deserialize<List<Product>>(json)!;
14 parseSpan.SetAttribute("product_count", products.Count);
15 }
16
17 using (var renderSpan = LDObserve.StartActiveSpan("RenderUI"))
18 {
19 Products.Clear();
20 foreach (var p in products)
21 Products.Add(p);
22 }
23}

Propagating span context across threads

When work runs on a detached thread, for example, using Task.Run, the span context is lost. Use span.Context to capture the context and pass it to other threads.

Creating nested spans
1private async void OnUploadClicked(object? sender, EventArgs e)
2{
3 var span = LDObserve.StartActiveSpan("UploadReport");
4 span.SetAttribute("report.type", "daily");
5 var capturedContext = span.Context;
6 span.End();
7
8 await Task.Run(() =>
9 {
10 // Activity.Current is null here -- pass capturedContext explicitly
11 LDObserve.RecordLog(
12 "Upload processing on background thread",
13 Severity.Info,
14 new Dictionary<string, object?> { { "thread", Environment.CurrentManagedThreadId } },
15 spanContext: capturedContext);
16
17 // ... heavy processing ...
18
19 LDObserve.RecordLog(
20 "Upload complete",
21 Severity.Info,
22 spanContext: capturedContext);
23 });
24}

Using spans with HttpClient

When calling a remote service you must preserve the current span context when creating new spans, or capture details of the service call in the current span.

When InstrumentationOptions.NetworkRequests is enabled, every HttpClient request generates a System.Net.Http span automatically. This is the default behavior. If a custom span is active at call time, the HTTP span becomes its child. The following example automatically generates a new span for the HTTP GET call and nests it under the active SyncOrders span. You do not need to create a span for the HTTP call itself:

Creating nested spans
1private async Task SyncOrdersAsync()
2{
3 using var span = LDObserve.StartActiveSpan("SyncOrders");
4 span.SetAttribute("sync.direction", "pull");
5
6 // The HTTP span for this GET is auto-created as a child of "SyncOrders"
7 var response = await _httpClient.GetAsync("https://api.example.com/orders?since=yesterday");
8 span.SetAttribute("http.status_code", (int)response.StatusCode);
9
10 var orders = await response.Content.ReadFromJsonAsync<List<Order>>();
11 span.SetAttribute("order_count", orders?.Count ?? 0);
12}

As an alternative, you can wrap an HTTP call in a span to capture timing, status codes, and errors in a single trace unit.

Creating nested spans
1private async Task<UserProfile?> FetchUserProfileAsync(string userId)
2{
3 using var span = LDObserve.StartActiveSpan("FetchUserProfile");
4 span.SetAttribute("user.id", userId);
5 span.SetAttribute("http.method", "GET");
6
7 try
8 {
9 var url = $"https://api.example.com/users/{userId}";
10 span.SetAttribute("http.url", url);
11
12 var response = await _httpClient.GetAsync(url);
13 span.SetAttribute("http.status_code", (int)response.StatusCode);
14
15 if (!response.IsSuccessStatusCode)
16 {
17 span.SetStatus(Status.Error);
18 span.SetAttribute("error.type", $"HTTP {response.StatusCode}");
19 return null;
20 }
21
22 var profile = await response.Content.ReadFromJsonAsync<UserProfile>();
23 span.SetStatus(Status.Ok);
24 return profile;
25 }
26 catch (Exception ex)
27 {
28 span.RecordException(ex);
29 span.SetStatus(Status.Error);
30 throw;
31 }
32}

Additional distributed tracing techniques

Your application may also need to perform other, more advanced distributed tracing activities such as:

  • Correlating log entries within a span
  • Correlating log entries or across different threads
  • Recording span exceptions
  • Using the Activity API in place of OpenTelemetry methods

To learn more about these techniques, read the .NET MAUI Distributed Tracing Guide.

Review observability data in LaunchDarkly

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