Skip to content

feat: observability features for MAUI and Android#421

Open
abelonogov-ld wants to merge 71 commits intomainfrom
andrey/recordlog
Open

feat: observability features for MAUI and Android#421
abelonogov-ld wants to merge 71 commits intomainfrom
andrey/recordlog

Conversation

@abelonogov-ld
Copy link
Contributor

@abelonogov-ld abelonogov-ld commented Mar 12, 2026

Summary

How did you test this change?

Are there any deployment considerations?


Note

Medium Risk
Touches cross-platform native bridge layers and session replay export/identify flow, so regressions could impact telemetry capture and replay attribution. Changes are mostly additive but span Android SDK internals, MAUI bindings, and sample/e2e apps.

Overview
Adds end-to-end observability recording support for MAUI: the Android/iOS native bridges now expose LDObserve APIs for logs, errors, and multiple metric types, plus updated bindings/exports (including a separate session replay hook proxy) so C# hooks can forward identify events into native replay.

Refactors Android session replay integration to initialize via plugin lifecycle (removing the instrumentation-contributor pattern), plumbs the OTel SessionManager through ObservabilityContext, and updates replay exporting to include an app title, set client_id, attach identify events even when session id is missing, and emit one-time "wake-up" events for resumed playback.

Updates docs and packaging metadata (README rebrand to Observability, dependency/version bumps), and extends the MAUI sample/e2e apps with a new Dialogs test page plus small UI tweaks and benchmark generator updates.

Written by Cursor Bugbot for commit 14fb38c. This will update automatically on new commits. Configure here.

* main:
  feat: Optional Jet Compose (#402)
  feat: Android Incremental Image Diff compression (#390)
  chore: add CLAUDE.md (#398)
  chore: release main (#400)
  fix: correct react native session replay build step (#399)
  chore: release main (#396)
  fix: Android span e2e tests (#397)
  fix: improve network response capture (#379)

# Conflicts:
#	sdk/@launchdarkly/mobile-dotnet/.vscode/tasks.json
(cherry picked from commit f883e975ca79da891b4178d8a12e27868f0931eb)
* main:
  chore: release main (#401)
(cherry picked from commit 9901600)
* main:
  chore: release main (#406)
  feat: Make Android SDK35 compilable (#405)
* andrey/hooks:
  comment identify stuff
  fat working
  working
  can launch
@abelonogov-ld abelonogov-ld changed the title WIP Android testing feat: observability features for MAUI and Android Mar 14, 2026
.anonymous(true)
.build()

//LDClient.get().identify(anonContext)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable and commented-out code committed

Low Severity

The anonContext variable is created but never used because the LDClient.get().identify(anonContext) call on line 96 is commented out. This looks like leftover debugging or experimentation code that was accidentally included in the commit.

Fix in Cursor Fix in Web

}

otelRUM = rumBuilder.build()
sessionManager = capturedSessionManager!!
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force-unwrap of captured session manager is fragile

Medium Severity

capturedSessionManager!! will throw a NullPointerException if the ld-session-manager-bridge instrumentation's install callback is never invoked during rumBuilder.build(). This relies on OpenTelemetryRum synchronously installing all instrumentations during build(), which is an internal implementation detail of the OTel Android library that could change.

Fix in Cursor Fix in Web

#elif ANDROID
var bridge = new LDObserveAndroid.ObservabilityBridge();
var map = DictionaryTypeConverters.ToJavaDictionary(attributes);
bridge.RecordLog(message, severity, map);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New bridge instance allocated per API call

Low Severity

Every Android method (RecordLog, RecordError, RecordMetric, RecordCount, RecordIncr, RecordHistogram, RecordUpDownCounter) creates a new LDObserveAndroid.ObservabilityBridge() on each call. This allocates a new JVM object via JNI for every telemetry event, which adds unnecessary overhead—especially for high-frequency metrics or logs. A single cached instance would suffice.

Additional Locations (2)
Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

// put wake up in the try/catch do not break buffering logic
logger.error(e)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wake-up state inconsistent with export rollback on failure

Medium Severity

The wakeUpEvents method calls nextPayloadId() and sets shouldWakeUpSession = false inside its own try-catch, but these side effects are not part of the snapshot/restore logic in the outer catch block. If wake-up events are successfully sent but a subsequent pushPayload for a different session in the same batch throws, payloadIdCounter is rolled back to the snapshot while shouldWakeUpSession remains false. On retry, the payload ID used by the already-sent wake-up payload may be reused for a regular payload, and no wake-up will be re-sent.

Fix in Cursor Fix in Web

LDObserveBridge.RecordLog(message, severity, dict);
#elif ANDROID
var bridge = new LDObserveAndroid.ObservabilityBridge();
var map = DictionaryTypeConverters.ToJavaDictionary(attributes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been trying out the observability plugin locally in Android using SessionReplay V0.4.1 (https://www.nuget.org/packages/LaunchDarkly.SessionReplay) and the attribute I am passing does not seem to show up in LaunchDarkly in the log entry. Could I be doing something wrong or looking in the wrong place? I would have expected to see "anonymous" show up in the log entry somewhere:

new Dictionary<string, object?>
{
    { "anonymous", context.UserId.IsBlank() }
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if an issue should be opened for this instead. I noticed that there was an open PR for this, so I thought it would be most convenient to add here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cam-line we have an issue of processing Boolean values on the backend. Thanks for filling. It should be fixed soon.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend fixed and SessionReplay V0.5.0 published

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants