Skip to content

feat: supporting advanced trace cases#645

Merged
abelonogov-ld merged 29 commits into
mainfrom
docs/rn-distributed-tracing
Jun 25, 2026
Merged

feat: supporting advanced trace cases#645
abelonogov-ld merged 29 commits into
mainfrom
docs/rn-distributed-tracing

Conversation

@abelonogov-ld

@abelonogov-ld abelonogov-ld commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds React Native distributed-tracing documentation and the supporting LDObserve API surface, automatic track tracing via the LD plugin hook, an iOS/Android-parity example app, the RN external session-id wiring, and a tracing-origin matching fix.

Changes

Docs

  • Distributed tracing guide (observability-react-native/guides/tracing.md): a cookbook of common patterns — root/nested spans, manual HTTP spans, automatic fetch/XHR instrumentation, exception recording, correlated logs, explicit context propagation across async boundaries, independent root spans via { root: true }, span events, end-to-end mobile-to-backend trace linking via tracingOrigins, and OpenTelemetry baggage propagation. Placed under guides/ (not the gitignored, Typedoc-generated docs/) and linked from the README.
  • README: added a "Guides" link and a withSpan "Manual tracing" example.

SDK (observability-react-native)

  • withSpan API (src/sdk/withSpan.ts, src/api/SpanScope.ts): an ergonomic wrapper that runs a callback inside a span and ends it automatically, with scope.child to parent nested spans off the captured context — important in RN where the active context is only tracked synchronously (lost across await).
  • LDObserve.track (Observe, LDObserve, ObservabilityClient, InstrumentationManager): records a custom track span carrying the event key, optional numeric value, and properties — mirroring the iOS/Android track APIs.
  • Automatic track tracing hook (plugin/observability.ts): the Observability plugin's TracingHook now implements afterTrack, so every LDClient.track call automatically emits a span (event key, optional metric value, evaluation context keys, and primitive track data) — no manual instrumentation needed. Flag-evaluation spans are additionally tagged with an internal attribute so SDK-emitted telemetry can be filtered out universally (independent of instrumentation scope).
  • Root spans: { root: true } on startSpan/startActiveSpan is forwarded to the underlying OpenTelemetry tracer, which drops the active span from context so the new span begins a fresh trace regardless of ambient context.
  • tracingOrigins matching fix (observability-shared/src/instrumentation/utils.ts + tests): match against the URL's origin (anchored host/subdomain regex) instead of an unanchored substring, so a configured host appearing as a query-param value in a third-party URL no longer leaks trace/baggage headers. String entries are escaped so . is literal.

React Native external session id (react-native-ld-session-replay)

  • Wire the JS sessionId through to the native Android SDK so RN, native observability, and session replay share one session.id (SessionReplayClientAdapter.kt/.swift, NativeSessionReplayReactNative.ts, src/index.tsx).
  • Bump com.launchdarkly:launchdarkly-android-client-sdk and the iOS LaunchDarklySessionReplay pod to 0.46.1.

Example app (react-native-ld-session-replay/example)

  • New Tracing tab (TracingScreen.tsx) exercising every recipe in the guide, and an API tab (ApiScreen.tsx) mirroring the iOS TestApp (MainMenuViewModel): identify, spans, metrics, logs, errors, track (via LD client), network, crash.
  • The independent-root-spans recipe creates root spans inside an active parent and asserts each child trace is detached from the parent and mutually unique (logs PASS/FAIL), so it actually exercises { root: true }.
  • Register the Observability plugin with demo tracingOrigins in App.tsx; add @launchdarkly/observability-react-native + @opentelemetry/api deps; monorepo metro.config.js resolution (workspace watch folders + forced react/react-native singletons).
  • Repo hygiene: gitignore the example's iOS/Ruby lock files and stop tracking Podfile.lock / Gemfile.lock.

Notes

  • The original prompt referenced react-native-ld-session-replay, but that package is purely session replay/masking; the tracing API lives in observability-react-native, so the guide and APIs were added there.
  • The LDObserve.track plain-dictionary (nested) reshaping is intentionally split into a separate stacked PR (feat(observability-react-native): accept plain nested dictionaries in track #650) on top of this branch.

Test plan

  • observability-react-native unit tests pass (incl. tracingOrigins matching).
  • Verify the guide renders on GitHub (Markdown + mermaid).
  • Run the example app: Tracing tab recipes produce expected spans; root-span recipe logs PASS; LDClient.track auto-emits a span via the hook; API tab mirrors the iOS demo; shared session.id across JS + native (Android).
  • Confirm trace/baggage headers are only sent to configured tracingOrigins.

Note

Medium Risk
Changes public tracing APIs and distributed trace header matching (security-sensitive); native session ID wiring and dependency bumps affect session-replay integration.

Overview
Adds manual tracing for React Native via LDObserve.withSpan and SpanScope (child, active, ctx) so span hierarchies survive awaits where OTel’s stack context does not, plus LDObserve.track and an afterTrack plugin hook so LDClient.track emits track spans like other mobile SDKs. Flag-evaluation spans get launchdarkly.internal for filtering.

Ships a tracing cookbook (guides/tracing.md) and README updates; the session-replay example gains Observability, Tracing and API tabs, and Metro monorepo fixes.

Security / propagation: tracingOrigins string hosts are origin-anchored in observability-shared so trace/baggage headers are not matched inside third-party URLs.

Session replay: Android forwards JS sessionId / serviceVersion into native observability; bumps Android/iOS LD dependencies; example lockfiles are gitignored.

E2E: Android sample sets sessionBackgroundTimeout to 3 minutes.

Reviewed by Cursor Bugbot for commit 9d06f16. Bugbot is set up for automated code reviews on this repo. Configure here.

Add a cookbook of distributed tracing patterns for the React Native
observability SDK, mirroring the existing .NET MAUI guide but adapted to
the JS/OpenTelemetry API (startActiveSpan/startSpan, explicit context
propagation across async boundaries, tracingOrigins-based mobile-to-backend
trace linking). Lives in guides/ since docs/ is reserved for generated
Typedoc output, and is linked from the package README.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abelonogov-ld abelonogov-ld requested a review from a team as a code owner June 24, 2026 20:49
abelonogov-ld and others added 2 commits June 24, 2026 14:12
… guide

Document setting, reading, and updating W3C baggage and how it propagates
to backends via tracingOrigins, plus a note that baggage is not copied
onto spans automatically.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a Tracing tab to the session-replay example with a button per recipe
from the distributed tracing guide and a live output log, registering the
Observability plugin with tracingOrigins so the backend-propagation and
baggage recipes exercise real header injection.

Co-authored-by: Cursor <cursoragent@cursor.com>
abelonogov-ld and others added 3 commits June 24, 2026 14:42
Stop tracking the session-replay example's ios/Podfile.lock and Gemfile.lock,
which are regenerated per-machine and churn on every pod/bundle install. The
monorepo yarn.lock remains tracked for reproducible installs.

Co-authored-by: Cursor <cursoragent@cursor.com>
…s resource attributes

buildObservabilityResource strips the default service.name from the OTel
resource but never re-adds one from options, so exported spans, logs, and
metrics had an empty service.name (the value only flowed to Session Replay).

Populate service.name / service.version from ObservabilityOptions.serviceName
and serviceVersion in the shared resource builder so both init paths (LDClient
plugin and standalone LDObserve.init) emit a populated service identity.

Co-authored-by: Cursor <cursoragent@cursor.com>

@Vadman97 Vadman97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could note to @dyozie-ld that we should make a docs update based on some of this?

abelonogov-ld and others added 4 commits June 25, 2026 10:23
…-tracing

Bring in the launchdarkly-android-client-sdk bump (0.46.1).
abelonogov-ld and others added 7 commits June 25, 2026 10:34
Introduce a LaunchDarkly-owned SessionManager (LDSessionManager) that can
be seeded with an external session id, so the native instance can adopt a
session id created elsewhere (e.g. the JS SDK in a React Native app) and
report a single shared session.id across spans, logs, metrics, and replay.

- LDSessionManager: seedable session manager replicating OTel Android's
  rotation semantics (foreground never expires, background inactivity and
  max-lifetime rotation) and observer notifications.
- LDRumSessionManagerAccessor: same-package shim to call the package-private
  OpenTelemetryRumBuilder.setSessionManager, making our manager back the RUM
  SDK's session.id span/log appenders (single source of session identity).
- ObservabilityService: inject the custom manager, drop the old
  session-manager-bridge instrumentation and SessionConfig, drive the
  background-timeout from the app lifecycle, and read session.id from it.
- Thread an optional customSessionId through the Observability plugin and
  LDObserve.init for callers (RN integration to follow).

Co-authored-by: Cursor <cursoragent@cursor.com>
…on id

When a session id is supplied externally the caller owns the session
lifecycle, matching iOS's isCustomSession. Skip both background-inactivity
and max-lifetime rotation so the seeded id is used unchanged for the
lifetime of the manager.

Co-authored-by: Cursor <cursoragent@cursor.com>
…pendency

The OpenTelemetry Android `activity` instrumentation is always suppressed
(its app/activity lifecycle spans are superseded by our own), so the
dependency was dead weight. Remove it (also drops `common-api`) and keep
suppressInstrumentation("activity") as a defensive guard in case a host
reintroduces it transitively.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ound state

AppLifecycleTracker only reports genuine transitions (it suppresses the
initial replay and never emits a catch-up background), so an SDK init while
the app is already backgrounded left LDSessionManager stuck in FOREGROUND
and skipped background-inactivity rotation until the next stop/start cycle.
Query the process lifecycle at init and mark the manager backgrounded when
appropriate; a later genuine onStart settles it back to foreground.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ted-tracing

* feat/android-external-session-id:
  fix(observability-android): prime session manager with initial background state
  chore(observability-android): drop unused activity instrumentation dependency
  feat(observability-android): disable auto rotation for external session id
  feat(observability-android): support external session id
@abelonogov-ld abelonogov-ld changed the title docs(observability-react-native): add distributed tracing guide feat: supporting advanced trace cases Jun 25, 2026
Comment thread sdk/@launchdarkly/observability-react-native/guides/tracing.md
abelonogov-ld and others added 4 commits June 25, 2026 12:15
…an/scope.active

Co-authored-by: Cursor <cursoragent@cursor.com>
…e runnable example screen

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

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.

Reviewed by Cursor Bugbot for commit e2fc3e2. Configure here.

Comment thread sdk/@launchdarkly/observability-react-native/guides/tracing.md
abelonogov-ld and others added 3 commits June 25, 2026 14:16
…s stand alone

Co-authored-by: Cursor <cursoragent@cursor.com>
… URL origin

The instrumentations' propagateTraceHeaderCorsUrls matches these patterns
against the full request URL, so an unanchored host string could match a
third-party URL carrying the host as a query value and leak trace/baggage
headers. Anchor string entries to the origin (host + subdomains).

Co-authored-by: Cursor <cursoragent@cursor.com>
…ver track data in afterTrack

Co-authored-by: Cursor <cursoragent@cursor.com>
@abelonogov-ld abelonogov-ld merged commit dac45eb into main Jun 25, 2026
23 checks passed
@abelonogov-ld abelonogov-ld deleted the docs/rn-distributed-tracing branch June 25, 2026 21:40
abelonogov-ld pushed a commit that referenced this pull request Jun 25, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>observability-react-native: 0.10.0</summary>

##
[0.10.0](observability-react-native-0.9.3...observability-react-native-0.10.0)
(2026-06-25)


### Features

* supporting advanced trace cases
([#645](#645))
([dac45eb](dac45eb))
</details>

<details><summary>session-replay-react-native: 0.12.0</summary>

##
[0.12.0](session-replay-react-native-0.11.1...session-replay-react-native-0.12.0)
(2026-06-25)


### Features

* supporting advanced trace cases
([#645](#645))
([dac45eb](dac45eb))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/observability-react-native bumped to 0.10.0
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Version and changelog-only release PR; behavioral changes were already
merged in #645.
> 
> **Overview**
> Release Please version bump with **no application code** in this
diff—only manifest, `package.json`, and changelog updates.
> 
> **`@launchdarkly/observability-react-native`** goes **0.9.3 → 0.10.0**
(minor). The new changelog entry records **advanced trace cases**
([#645](#645))
as the shipped feature.
> 
> **`@launchdarkly/session-replay-react-native`** goes **0.11.1 →
0.12.0**, with the same feature noted and a workspace dependency bump to
**`@launchdarkly/observability-react-native` 0.10.0**.
`.release-please-manifest.json` is updated to match.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
71e0ede. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

3 participants