Skip to content

Telemetry - #1021

Draft
pblazej wants to merge 2 commits into
mainfrom
blaze/telemetry
Draft

pblazej wants to merge 2 commits into
mainfrom
blaze/telemetry

Conversation

@pblazej

@pblazej pblazej commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Telemetry

Draft: integrates the shared Rust telemetry core (livekit-telemetry, livekit/rust-sdks#1396) into the Android SDK, per the "Client Telemetry" design doc. Same shape as the Swift integration (livekit/client-sdk-swift#1108); the core owns policy and vocabulary, the SDK contributes a transport, instruments and the wiring.

Design

  • Telemetry is the one entry point: LiveKit.setTelemetry(context, options) / Telemetry.configure(context, options) configure the process-wide pipeline (it lives in the Rust core) at any time; every Room takes a scope with its own trace id at construction (room.telemetryTraceId), so pre-connect failures are captured and nothing leaks a session handle. TelemetryOptions mirrors the core's config (endpoint, headers, storage directory, flush interval, stats window, instruments, log level); nothing from UniFFI is public.
  • Instruments, selectable with TelemetryOptions.instruments (ROOM, RTC, LOGS, DEVICE; all by default): DeviceTelemetry (thermal status, power save, trim-memory, network capabilities incl. metered and Data Saver, battery, AudioSwitch route and focus changes, camera and microphone capture failures), warn/error records from LKLog, the Rust core and WebRTC, and per Room the lk.connect / lk.reconnect / lk.publish spans with their checkpoints from Room, RTCEngine, SignalClient and LocalParticipant; RTCTelemetry reports the remote tracks' lifecycle and the core runs the lk.subscribe span itself; once a second each track's raw getRTCStats() report goes to the core, which maps and windows it. A Room's records carry its session even outside a span.
  • Transport: OkHttp POST returning the collector's raw answer (the core decides retry / drop / go-silent); the destination is derived from the connect URL unless TelemetryOptions.endpoint is set.

Testing

TelemetryMockE2ETest runs a mock session (MockE2ETest: mock websocket, peer connections and a mock audio track) with telemetry pointed at an otelcol-contrib writing OTLP/JSON to disk and asserts the spans, steps and records that reached it; it skips when no collector listens. Robolectric runs the real Rust core through JNA (-PlivekitUniffiLibraryPath names the host library). The UniFFI dependency points at a locally built io.livekit:livekit-uniffi-android:0.0.1 in Maven Local for now.

pblazej and others added 2 commits September 15, 2026 20:18
Android mirrors the Swift integration. The Rust core (livekit-uniffi 0.0.1,
prototype local build from Maven Local) owns the pipeline, every policy and
the vocabulary; the SDK contributes a bytes-moving OkHttp transport, the
platform instruments, and the wiring of Room / engine / tracks / stats / logs
into the core's API. UniFFI types stay out of the public API, and telemetry
off means every hook is a no-op (the core is not even loaded).

- TelemetryOptions mirrors TelemetryConfig; Telemetry.configure / shutdown /
  setAttribute / diagnostics; LiveKit.setTelemetry(context, options).
- Room takes its scope at creation: the lk.connect span (ws_open, signal,
  join_recv, pc_created, offer_sent / answer_sent, engine, pc_connected,
  room_connected), lk.reconnect with the reason and "attempt N quick|full"
  steps, lk.publish with the track, setRoom after join, disconnected(reason)
  once per real session, telemetryTraceId and emitTelemetryEvent.
- Instruments: device (thermal status, battery saver, trim-memory, default
  network type / metered / Data Saver, battery; AudioSwitch route and focus
  changes; camera and microphone capture failures), logs (LKLog warn+
  whatever the console level, WebRTC at error, the core's own lines forwarded
  into LKLog), rtc (raw getStats reports per published / subscribed track once
  a second via recordStatsReport, and the subscribe lifecycle via
  subscribeStarted / subscribed / subscribeCancelled / subscribeFailed; the
  core maps the stats and runs the lk.subscribe span).
- TelemetryMockE2ETest drives connect, quick reconnect, publish, a log record
  inside a span, a custom event and disconnect through the mocks against a
  local collector and asserts what reached it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… span

The Room's scope rides the Room's, the engine's and the signal client's
coroutine scopes as a ThreadLocal context element (Telemetry.currentScope),
the way the current span does. The log capture files a warn/error record
through TelemetryScope.log when a Room scope is ambient and no span is in
flight; inside a span, and with neither, it stays telemetryLog. The engine
now holds the ungated scope and gates the lk.reconnect span itself.

The e2e test asserts a Room-handler warning (an unpublish for an unknown
track) in the Room's trace with no span, and a record logged outside any
Room context in the process scope.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d37f4a9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Dependency diff:

 +--- com.squareup.okhttp3:okhttp:4.12.0
|    \--- com.squareup.okio:okio:3.6.0
|         \--- com.squareup.okio:okio-jvm:3.6.0
|              \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
|                   \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.10 -> 1.9.25
-|                        \--- org.jetbrains:annotations:13.0 -> 23.0.0
+|                        \--- org.jetbrains:annotations:13.0
+--- com.github.davidliu:audioswitch:039a35aefab7747c557242fa216c9ea11743b604
-|    \--- androidx.annotation:annotation:1.3.0 -> 1.9.0
-|         \--- androidx.annotation:annotation-jvm:1.9.0
-|              \--- org.jetbrains.kotlin:kotlin-stdlib:1.7.10 -> 1.9.25 (*)
+|    \--- androidx.annotation:annotation:1.3.0 -> 1.7.1
+|         \--- androidx.annotation:annotation-jvm:1.7.1
+|              \--- org.jetbrains.kotlin:kotlin-stdlib:1.7.10 -> 1.9.25 (*)
-+--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0 -> 1.8.1
-|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1
-|    |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1
-|    |         +--- org.jetbrains:annotations:23.0.0
-|    |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1
-|    |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 (c)
-|    |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 (c)
-|    |         |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 (c)
-|    |         \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.21 -> 1.9.25 (*)
-|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 (*)
-|    \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.21 -> 1.9.25 (*)
++--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0 -> 1.6.4
+|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4
+|    |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4
+|    |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4
+|    |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 (c)
+|    |         |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4 (c)
+|    |         |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4 (c)
+|    |         +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21 -> 1.9.10 (*)
+|    |         \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21 -> 1.9.25 (*)
+|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4 (*)
+|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21 -> 1.9.10 (*)
-+--- io.livekit:livekit-uniffi-android:0.1.12
-|    +--- androidx.annotation:annotation:1.9.0 (*)
-|    +--- net.java.dev.jna:jna:5.19.1
-|    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 (*)
-|    \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 1.9.25 (*)
++--- io.livekit:livekit-uniffi-android:0.0.1 FAILED
+--- com.auth0.android:jwtdecode:2.0.2
-|    \--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+|    \--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
-+--- androidx.annotation:annotation:1.7.1 -> 1.9.0 (*)
++--- androidx.annotation:annotation:1.7.1 (*)
\--- androidx.core:core:1.13.1
-     +--- androidx.annotation:annotation:1.6.0 -> 1.9.0 (*)
+     +--- androidx.annotation:annotation:1.6.0 -> 1.7.1 (*)
    +--- androidx.collection:collection:1.0.0
-     |    \--- androidx.annotation:annotation:1.0.0 -> 1.9.0 (*)
+     |    \--- androidx.annotation:annotation:1.0.0 -> 1.7.1 (*)
    +--- androidx.concurrent:concurrent-futures:1.0.0 -> 1.1.0
-     |    \--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+     |    \--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
    +--- androidx.interpolator:interpolator:1.0.0
-     |    \--- androidx.annotation:annotation:1.0.0 -> 1.9.0 (*)
+     |    \--- androidx.annotation:annotation:1.0.0 -> 1.7.1 (*)
    +--- androidx.lifecycle:lifecycle-runtime:2.6.2
-     |    +--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+     |    +--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
    |    +--- androidx.arch.core:core-common:2.2.0
-     |    |    \--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+     |    |    \--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
    |    +--- androidx.arch.core:core-runtime:2.2.0
-     |    |    \--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+     |    |    \--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
    |    +--- androidx.lifecycle:lifecycle-common:2.6.2
-     |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+     |    |    +--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
-     |    |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 -> 1.8.1 (*)
+     |    |    \--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 (*)
    |    \--- androidx.profileinstaller:profileinstaller:1.3.0
-     |         +--- androidx.annotation:annotation:1.2.0 -> 1.9.0 (*)
+     |         +--- androidx.annotation:annotation:1.2.0 -> 1.7.1 (*)
    |         \--- androidx.startup:startup-runtime:1.1.1
-     |              +--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+     |              +--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
    |              \--- androidx.tracing:tracing:1.0.0
-     |                   \--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+     |                   \--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)
    \--- androidx.versionedparcelable:versionedparcelable:1.1.1
-          \--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
+          \--- androidx.annotation:annotation:1.1.0 -> 1.7.1 (*)

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.

1 participant