Advanced Configuration

This guide covers advanced configuration options for fine-tuning Datadog RUM in your .NET MAUI application.

Table of Contents

Configuration Methods

Datadog MAUI SDK supports three configuration approaches:

Use appsettings.json for declarative configuration:

{
  "Datadog": {
    "Environment": "production",
    "ServiceName": "my-app"
  }
}
builder.UseDatadogFromConfiguration();

Pros: Clean separation, easy to manage per environment, no code changes Use when: Standard configuration needs, multiple environments

2. Programmatic Configuration

Configure entirely in code:

builder.UseDatadog(datadog =>
{
    datadog.SetClientToken(
        android: "pub_android_token",
        ios: "pub_ios_token"
    );

    datadog.Environment = "production";
    datadog.ServiceName = "my-app";

    datadog.EnableRum(rum =>
    {
        rum.SetApplicationId(
            android: "android-app-id",
            ios: "ios-app-id"
        );
    });
});

Pros: Type-safe, compile-time validation, dynamic configuration Use when: Configuration needs to be determined at runtime

3. Hybrid Approach

Combine configuration file with programmatic overrides:

builder.UseDatadogFromConfiguration(configure: datadog =>
{
    // Base config from appsettings.json, override specific values
    datadog.EnableRum(rum =>
    {
        rum.Variant = BuildInfo.Variant;
        rum.BuildId = BuildInfo.BuildId;
    });
});

Pros: Best of both worlds, flexible for build-specific values Use when: Most production scenarios (recommended)

RUM Configuration

Basic RUM Setup

datadog.EnableRum(rum =>
{
    rum.SetApplicationId(
        android: "YOUR_ANDROID_APP_ID",
        ios: "YOUR_IOS_APP_ID"
    );

    rum.SetSessionSampleRate(100); // Track 100% of sessions
});

Session Sampling

Control what percentage of user sessions are tracked:

rum.SetSessionSampleRate(20); // Track 20% of sessions

Sampling Strategy:

  • Development: 100% - Track all sessions for debugging
  • Staging: 50-100% - High visibility for testing
  • Production: 10-30% - Balance cost with visibility
  • High Traffic: 1-10% - Cost-effective for large scale

Crash Reporting with Symbolication

Enable full crash symbolication:

datadog.EnableRum(rum =>
{
    // These values are auto-generated by Datadog.MAUI.Symbols package
    rum.Variant = Datadog.MAUI.Symbols.DatadogBuildInfo.Variant;
    rum.BuildId = Datadog.MAUI.Symbols.DatadogBuildInfo.BuildId;
});

See Crash Reporting Guide for complete symbolication setup.

Custom View Names

Control how views appear in Datadog:

// Manual view tracking
Rum.StartView("checkout", "Checkout Flow", new Dictionary<string, object>
{
    { "step", "payment" },
    { "cart_value", 99.99 }
});

// Stop view when done
Rum.StopView("checkout");

Global Attributes

Add attributes to all RUM events:

// Add global attribute
Rum.AddAttribute("app_version", "2.1.0");
Rum.AddAttribute("build_number", 142);

// Remove when no longer needed
Rum.RemoveAttribute("build_number");

Logs Configuration

Enable Logging

{
  "Datadog": {
    "Logs": {
      "Enabled": true
    }
  }
}

Or programmatically:

datadog.EnableLogs();

Using Loggers

Create feature-specific loggers:

using Datadog.Maui.Logs;

public class CheckoutService
{
    private readonly ILogger _logger;

    public CheckoutService()
    {
        _logger = Logs.CreateLogger("Checkout");
    }

    public void ProcessOrder(string orderId)
    {
        _logger.Info("Processing order", attributes: new Dictionary<string, object>
        {
            { "order_id", orderId },
            { "timestamp", DateTime.UtcNow }
        });

        try
        {
            // Process order
        }
        catch (Exception ex)
        {
            _logger.Error("Order processing failed", ex, attributes: new Dictionary<string, object>
            {
                { "order_id", orderId }
            });
        }
    }
}

Log Levels

All standard log levels are supported:

_logger.Debug("Debug information");
_logger.Info("Informational message");
_logger.Notice("Notable event");
_logger.Warn("Warning condition");
_logger.Error("Error occurred", exception);
_logger.Critical("Critical failure", exception);

Logger Attributes

Add persistent attributes to a specific logger:

_logger.AddAttribute("feature", "checkout");
_logger.AddAttribute("version", "2.0");

// These attributes will be included in all logs from this logger
_logger.Info("Order placed"); // Will include feature and version attributes

Tracing Configuration

Enable Distributed Tracing

{
  "Datadog": {
    "Tracing": {
      "Enabled": true,
      "SampleRate": 100
    },
    "FirstPartyHosts": [
      "api.example.com",
      "cdn.example.com"
    ]
  }
}

Or programmatically:

datadog.EnableTracing(tracing =>
{
    tracing.SetFirstPartyHosts(new[]
    {
        "api.example.com",
        "cdn.example.com"
    });

    tracing.SetSampleRate(100); // Trace 100% of requests
});

Creating Spans

Track custom operations:

using Datadog.Maui.Tracing;

public async Task<User> GetUserAsync(string userId)
{
    using (var span = Tracer.StartSpan("database.query"))
    {
        span.SetTag("db.operation", "SELECT");
        span.SetTag("db.table", "users");
        span.SetTag("user.id", userId);

        try
        {
            var user = await _database.GetUserAsync(userId);
            span.SetTag("result.found", user != null);
            return user;
        }
        catch (Exception ex)
        {
            span.SetError(ex);
            throw;
        }
    }
}

Nested Spans

Track parent-child relationships:

using (var parentSpan = Tracer.StartSpan("checkout.process"))
{
    parentSpan.SetTag("order.id", orderId);

    // Child operation 1
    using (var childSpan = Tracer.StartSpan("payment.validate", parent: parentSpan))
    {
        childSpan.SetTag("payment.method", "credit_card");
        await ValidatePayment();
    }

    // Child operation 2
    using (var childSpan = Tracer.StartSpan("inventory.reserve", parent: parentSpan))
    {
        childSpan.SetTag("items.count", 3);
        await ReserveInventory();
    }
}

HTTP Tracing

HTTP requests to first-party hosts are automatically traced. Use DatadogHttpMessageHandler for explicit control:

using Datadog.Maui.Tracing;

var httpClient = new HttpClient(new DatadogHttpMessageHandler(
    firstPartyHosts: new[] { "api.example.com" },
    innerHandler: new HttpClientHandler()
));

// Requests will automatically create spans
var response = await httpClient.GetAsync("https://api.example.com/users");

Session Replay Configuration

Enable Session Replay

{
  "Datadog": {
    "SessionReplay": {
      "Enabled": true,
      "SampleRate": 20,
      "TextAndInputPrivacy": "MaskSensitiveInputs",
      "ImagePrivacy": "MaskNonBundledOnly",
      "TouchPrivacy": "Show"
    }
  }
}

Privacy Levels

TextAndInputPrivacy Options:

sessionReplay.SetTextAndInputPrivacy(TextAndInputPrivacy.Allow);
// Records all text exactly as displayed

sessionReplay.SetTextAndInputPrivacy(TextAndInputPrivacy.Mask);
// Masks all text with "X" characters

sessionReplay.SetTextAndInputPrivacy(TextAndInputPrivacy.MaskSensitiveInputs);
// Masks only password, email, phone fields (RECOMMENDED)

ImagePrivacy Options:

sessionReplay.SetImagePrivacy(ImagePrivacy.MaskAll);
// All images shown as placeholders

sessionReplay.SetImagePrivacy(ImagePrivacy.MaskNonBundledOnly);
// Only user-uploaded images masked (RECOMMENDED)

sessionReplay.SetImagePrivacy(ImagePrivacy.MaskNone);
// All images shown

TouchPrivacy Options:

sessionReplay.SetTouchPrivacy(TouchPrivacy.Show);
// Show all touch interactions (RECOMMENDED)

sessionReplay.SetTouchPrivacy(TouchPrivacy.Hide);
// Hide all touch interactions

Conditional Session Replay

Enable Session Replay only for specific conditions:

datadog.EnableSessionReplay(sessionReplay =>
{
    sessionReplay.SetSampleRate(20);

    // Only on physical devices (not simulators)
    if (DeviceInfo.DeviceType == DeviceType.Physical)
    {
        sessionReplay.SetTextAndInputPrivacy(TextAndInputPrivacy.MaskSensitiveInputs);
    }
});

Privacy and Data Scrubbing

User Information

Set user information while respecting privacy:

Datadog.SetUser(new UserInfo
{
    Id = "user-12345",              // Use internal ID, not email
    Name = "John D.",               // Optionally mask surname
    Email = "j***@example.com",     // Mask email if needed
    ExtraInfo = new Dictionary<string, object>
    {
        { "plan", "premium" },
        { "signup_date", "2024-01-15" }
        // Don't include sensitive data (SSN, credit card, etc.)
    }
});

Clear User Data

Clear user information on logout:

Datadog.ClearUser();

Scrubbing Sensitive Data

Remove sensitive information from logs and RUM:

// Bad - Logs credit card
_logger.Info("Payment processed", attributes: new Dictionary<string, object>
{
    { "card_number", "4111111111111111" } // ❌ Don't do this
});

// Good - Logs only last 4 digits
_logger.Info("Payment processed", attributes: new Dictionary<string, object>
{
    { "card_last4", "1111" } // ✅ Safe to log
});
{
  "Datadog": {
    "TrackingConsent": "Granted"
  }
}

Options:

  • Granted: Start tracking immediately (default)
  • NotGranted: Wait for user consent
  • Pending: Buffer events until consent is granted
using Datadog.Maui;

// User accepts tracking
Datadog.SetTrackingConsent(TrackingConsent.Granted);

// User declines tracking
Datadog.SetTrackingConsent(TrackingConsent.NotGranted);

GDPR Compliance Pattern

public class ConsentManager
{
    public async Task InitializeAsync()
    {
        // Start with pending consent
        Datadog.SetTrackingConsent(TrackingConsent.Pending);

        // Show consent dialog
        var consent = await ShowConsentDialog();

        if (consent)
        {
            // Grant tracking - buffered events will be sent
            Datadog.SetTrackingConsent(TrackingConsent.Granted);
        }
        else
        {
            // Deny tracking - buffered events will be discarded
            Datadog.SetTrackingConsent(TrackingConsent.NotGranted);
        }
    }
}

Performance Tuning

Optimize Sampling Rates

Balance visibility with performance and cost:

builder.UseDatadogFromConfiguration(configure: datadog =>
{
#if DEBUG
    // Development: Maximum visibility
    datadog.EnableRum(rum => rum.SetSessionSampleRate(100));
    datadog.EnableTracing(tracing => tracing.SetSampleRate(100));
    datadog.EnableSessionReplay(sr => sr.SetSampleRate(100));
#else
    // Production: Cost-effective sampling
    datadog.EnableRum(rum => rum.SetSessionSampleRate(20));
    datadog.EnableTracing(tracing => tracing.SetSampleRate(30));
    datadog.EnableSessionReplay(sr => sr.SetSampleRate(5));
#endif
});

Batching Configuration

The SDK automatically batches events for network efficiency. No configuration needed.

Memory Management

The SDK uses bounded buffers to prevent memory issues. Events are automatically dropped if limits are reached.

Multi-Environment Setup

Using Multiple Configuration Files

appsettings.json (base):

{
  "Datadog": {
    "ServiceName": "my-app",
    "Logs": { "Enabled": true }
  }
}

appsettings.Development.json:

{
  "Datadog": {
    "Environment": "development",
    "Android": {
      "ClientToken": "dev_android_token",
      "RumApplicationId": "dev_android_app_id"
    },
    "iOS": {
      "ClientToken": "dev_ios_token",
      "RumApplicationId": "dev_ios_app_id"
    },
    "Rum": { "SessionSampleRate": 100 }
  }
}

appsettings.Production.json:

{
  "Datadog": {
    "Environment": "production",
    "Android": {
      "ClientToken": "prod_android_token",
      "RumApplicationId": "prod_android_app_id"
    },
    "iOS": {
      "ClientToken": "prod_ios_token",
      "RumApplicationId": "prod_ios_app_id"
    },
    "Rum": { "SessionSampleRate": 20 }
  }
}

Load environment-specific config:

private static void LoadAppSettings(MauiAppBuilder builder)
{
    var configBuilder = new ConfigurationBuilder();

    // Load base config
    using var baseStream = FileSystem.OpenAppPackageFileAsync("appsettings.json")
        .GetAwaiter().GetResult();
    var baseMemoryStream = new MemoryStream();
    baseStream.CopyTo(baseMemoryStream);
    baseMemoryStream.Position = 0;
    configBuilder.AddJsonStream(baseMemoryStream);

    // Load environment-specific override
#if DEBUG
    var envFile = "appsettings.Development.json";
#else
    var envFile = "appsettings.Production.json";
#endif

    try
    {
        using var envStream = FileSystem.OpenAppPackageFileAsync(envFile)
            .GetAwaiter().GetResult();
        var envMemoryStream = new MemoryStream();
        envStream.CopyTo(envMemoryStream);
        envMemoryStream.Position = 0;
        configBuilder.AddJsonStream(envMemoryStream);
    }
    catch (FileNotFoundException)
    {
        // Environment file optional
    }

    var config = configBuilder.Build();
    builder.Configuration.AddConfiguration(config);
}

Build Configuration-Specific Settings

Use conditional compilation for environment-specific logic:

builder.UseDatadogFromConfiguration(configure: datadog =>
{
#if DEBUG
    datadog.Environment = "development";
#elif STAGING
    datadog.Environment = "staging";
#elif RELEASE
    datadog.Environment = "production";
#endif
});

Next Steps