Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions e2e/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,22 @@ dependencies {

implementation("com.launchdarkly:launchdarkly-android-client-sdk:5.11.0")

implementation("io.opentelemetry:opentelemetry-api:1.51.0")
implementation("io.opentelemetry:opentelemetry-sdk:1.51.0")
implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.51.0")
implementation("io.opentelemetry:opentelemetry-sdk-metrics:1.51.0")
implementation("io.opentelemetry:opentelemetry-api:1.52.0")
implementation("io.opentelemetry:opentelemetry-sdk:1.52.0")
implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.52.0")
implementation("io.opentelemetry:opentelemetry-sdk-metrics:1.52.0")

// Android HTTP Url instrumentation
implementation("io.opentelemetry.android.instrumentation:httpurlconnection-library:0.11.0-alpha")
byteBuddy("io.opentelemetry.android.instrumentation:httpurlconnection-agent:0.11.0-alpha")
implementation("io.opentelemetry.android.instrumentation:httpurlconnection-library:0.13.0-alpha")
byteBuddy("io.opentelemetry.android.instrumentation:httpurlconnection-agent:0.13.0-alpha")

// Used for accessing the SignalFromDiskExporter class in TestApplication
implementation("io.opentelemetry.android:core:0.11.0-alpha")
implementation("io.opentelemetry.android:core:0.13.0-alpha")

// OkHTTP instrumentation
implementation("io.opentelemetry.android.instrumentation:okhttp3-library:0.11.0-alpha")
byteBuddy("io.opentelemetry.android.instrumentation:okhttp3-agent:0.11.0-alpha")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("io.opentelemetry.android.instrumentation:okhttp3-library:0.13.0-alpha")
byteBuddy("io.opentelemetry.android.instrumentation:okhttp3-agent:0.13.0-alpha")
implementation("com.squareup.okhttp3:okhttp:5.1.0")

implementation("com.google.android.material:material:1.12.0")
implementation("androidx.recyclerview:recyclerview:1.3.2")
Expand Down Expand Up @@ -147,8 +147,8 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.core.ktx)
testImplementation(libs.robolectric)
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.51.0")
testImplementation("com.squareup.okhttp3:mockwebserver:5.1.0")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.52.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation(testFixtures(project(":observability-android")))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Ignore
import org.junit.Test
Expand Down Expand Up @@ -264,17 +265,21 @@ class DisablingConfigOptionsE2ETest {
private fun requestsContainsUrl(url: String): Boolean {
while (true) {
val request = application.mockWebServer?.takeRequest(100, TimeUnit.MILLISECONDS) ?: return false
if (request.requestUrl.toString() == url) return true
if (recordedUrl(request) == url) return true
}
}

private fun requestsStartWith(prefix: String): Boolean {
while (true) {
val request = application.mockWebServer?.takeRequest(100, TimeUnit.MILLISECONDS) ?: return false
if (request.requestUrl.toString().startsWith(prefix)) return true
if (recordedUrl(request).startsWith(prefix)) return true
}
}

// MockWebServer 5 records the host as 127.0.0.1, while the expected URLs use `localhost`.
private fun recordedUrl(request: RecordedRequest): String =
request.requestUrl?.newBuilder()?.host("localhost")?.build().toString()

private fun triggerTestLog(severity: Severity = Severity.INFO) {
LDObserve.recordLog(
message = TEST_LOG_MESSAGE,
Expand Down
3 changes: 3 additions & 0 deletions sdk/@launchdarkly/mobile-dotnet/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "mobile-dotnet.sln"
}
41 changes: 35 additions & 6 deletions sdk/@launchdarkly/mobile-dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,33 @@ The LaunchDarkly Observability SDK for .NET MAUI provides automatic and manual i
### Automatic Instrumentation

The .NET MAUI observability plugin automatically instruments:
- **HTTP Requests**: Outgoing HTTP requests
- **Crash Reporting**: Automatic crash reporting and stack traces
- **HTTP Requests**: Outgoing HTTP requests made through `HttpClient` (`instrumentation.networkRequests`; requires `HttpActivityPropagationSupport`, see [Enabling HTTP request tracing](#enabling-http-request-tracing))
- **Launch Times**: App launch performance (`instrumentation.launchTimes`)
- **Feature Flag Evaluations**: Evaluation events added to your spans
- **Session Management**: User session tracking and background timeout handling

Automatic crash reporting is not enabled yet; use `LDObserve.RecordError` to report exceptions.

## Prerequisites

* **.NET 9.0** or higher is required.
* MAUI support for **iOS** and **Android**.
* **.NET 9** or **.NET 10** with the MAUI workload.
* **Android** API 23 or later and **iOS** 15 or later.

### Enabling HTTP request tracing

HTTP tracing needs one setting in your app's project file:

```xml
<PropertyGroup>
<HttpActivityPropagationSupport>true</HttpActivityPropagationSupport>
</PropertyGroup>
```

The Android SDK sets this to `false` by default in every configuration, and the iOS SDK sets it to `false` in Release builds. While it is `false`, the trimmer removes the `System.Net.Http` diagnostics that the SDK listens to, so no HTTP spans are recorded even with `networkRequests: true`. The SDK cannot turn it back on at runtime.

## Example Application

A complete example application is available in the [sample](./sample) directory.
A complete example application is available in the [sample](./sample) directory. `MauiSample9.csproj` builds it for .NET 9 and `MauiSample10.csproj` for .NET 10; both share the same sources.

## Usage

Expand Down Expand Up @@ -64,6 +78,21 @@ public static class MauiProgram
}
```

Alternatively, register the plugin yourself when building the client configuration. `ObservabilityPlugin` takes the same options, including optional `SessionReplayOptions` as its second argument:

```csharp
using LaunchDarkly.Sdk.Client.Integrations;
using LaunchDarkly.SessionReplay;

var ldConfig = Configuration.Builder(mobileKey, ConfigurationBuilder.AutoEnvAttributes.Enabled)
.Plugins(new PluginConfigurationBuilder()
.Add(new ObservabilityPlugin(
new ObservabilityOptions(isEnabled: true, serviceName: "maui-sample-app"),
new SessionReplayOptions(isEnabled: true))))
.Build();
var client = LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));
```

#### Standalone (without a LaunchDarkly client)

If you are not using the LaunchDarkly client, pass your mobile key directly. This boots the native observability stack without registering a plugin:
Expand Down Expand Up @@ -162,7 +191,7 @@ LDObserve.RecordLog(
);
```

Supported severity levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`.
Supported severity levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`, each with numbered variants (for example `Info2` to `Info4`) matching the OpenTelemetry severity numbers.

##### Logs with Span Context

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@
<PackageReference Include="CommunityToolkit.Maui.NativeLibraryInterop.BuildTasks" Version="0.0.1-pre1" PrivateAssets="all" />
</ItemGroup>

<!-- KotlinX Serialization (Json) via NuGet so apps get it transitively -->
<ItemGroup>
<PackageReference Include="Org.Jetbrains.Kotlinx.KotlinxSerializationJson" Version="1.9.0" />
</ItemGroup>

<!-- AndroidX Compose UI and Lifecycle -->
<ItemGroup>
<!-- These are only needed to build the binding project itself.
Expand Down
Loading
Loading