Skip to content

Telemetry - #1209

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

pblazej wants to merge 16 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 Flutter 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: LiveKitClient.setTelemetry(options) / Telemetry.configure(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): device (app lifecycle, memory pressure, connectivity type, audio device changes, capture failures), warn/error records from the SDK logger and the Rust core, and per Room the lk.connect / lk.reconnect / lk.publish spans with their checkpoints from Room, Engine and LocalParticipant; remote-track lifecycle goes to the core, which runs the lk.subscribe span itself; each track's existing stats monitor hands the raw getStats() report to the core, which maps and windows it.
  • Transport: the core's pull queue served by a Dart loop with package:http (uniffi-dart callbacks are isolate-bound); the destination is derived from the connect URL unless TelemetryOptions.endpoint is set.
  • Web stays a compiled no-op behind the same conditional import the UniFFI facade (Integrate initial uniffi rust core #1160) uses.

Testing

test/telemetry/telemetry_e2e_test.dart runs a mock session (mock websocket and peer connection) with telemetry pointed at an otelcol-contrib writing OTLP/JSON to disk and asserts the spans, steps, records and stats windows that reached it; test/telemetry/telemetry_instruments_test.dart covers the instrument lifecycle. pubspec.yaml carries a dependency_overrides to a locally built livekit_uniffi for now.

1egoman and others added 16 commits August 7, 2026 16:30
Groundwork for delivering the Rust core through Dart's Native Assets,
which is only stable from Flutter 3.38 / Dart 3.10 onwards.

The newer language version turns on lints the tree does not yet satisfy:

- `*.g.dart` joins the analyzer exclusions. AGENTS.md already described
  json_serializable output as excluded, but only the protobuf patterns
  actually were; 47 of the 48 new `use_null_aware_elements` hits are in
  generated files nobody edits by hand.
- The remaining `use_null_aware_elements` hit, in `Room`, and four
  `unnecessary_underscores` hits in the RPC tests, are fixed directly.

Crossing Dart 3.7 also switches `dart format` to the tall style, which
reformats the whole tree. That is mechanical and lands separately, in the
commit that follows, so it does not bury this one. `dart format
--set-exit-if-changed` is therefore red at this commit and green again at
the next.

No functional change.
Pure output of `dart format .`. No functional or semantic change.

The formatter picks its style from the package language version, so the
SDK bump in the previous commit crosses Dart 3.7 and switches the whole
tree from the short style to the tall one. `dart format
--set-exit-if-changed` is CI-enforced, so this has to land -- it is split
out only so the bump and its lint fixes stay readable.

Reproduce with `dart format .` on the parent commit.
Establishes the development loop for calling into `livekit-uniffi` from
this SDK, and exercises it with `buildVersion()` -- a synchronous,
argument-free call returning a string, so a green test means the whole
chain is intact: the build hook resolved a cdylib for the target, Native
Assets bundled it, `@Native` bound the symbol, and a value crossed back.

`lib/src/uniffi/` splits native/web with the same conditional-import
pattern as the rest of the SDK. There is no dynamic library to load on
the web, so `uniffi_io.dart` is the only file permitted to import the
generated bindings -- reaching them from anywhere web-reachable pulls
`dart:ffi` into a web compile and breaks `flutter build web`/`--wasm`.
Callers branch on `LiveKitUniffi.isAvailable`. The facade is internal for
now; nothing is added to the public API surface.

`livekit_uniffi` is not on pub.dev, so both pubspecs override it to a
sibling rust-sdks checkout -- overrides do not propagate from a
dependency, hence both. Until it is published this branch cannot merge:
CI has no rust-sdks checkout, so `flutter pub get` will fail there.

Verified: 392 tests pass, web and wasm builds succeed, analyze and format
are clean.
# Conflicts:
#	example/pubspec.yaml
#	lib/src/core/engine.dart
#	lib/src/core/room.dart
#	lib/src/e2ee/key_provider.dart
#	lib/src/extensions.dart
#	lib/src/json/agent_attributes.g.dart
#	lib/src/token_source/caching.g.dart
#	lib/src/token_source/jwt.g.dart
#	lib/src/token_source/room_configuration.g.dart
#	lib/src/token_source/token_source.g.dart
#	pubspec.lock
#	pubspec.yaml
livekit_uniffi 0.1.10 is published, and its release carries the prebuilt
libraries the build hook downloads, so the path overrides to a sibling
rust-sdks checkout are no longer needed in either pubspec.
The path override becomes the opt-in for developing against unreleased
crate changes rather than the default, and the note about releases
lacking assets is replaced with how download mode behaves.
flutter_webrtc moved to 1.6.2+hotfix.1 on main.
0.1.12 is the first version published by the release pipeline. Its build
hook keeps each target architecture's downloaded library in its own
directory, which the universal macOS release build needs; 0.1.10 wrote
both to one path and the macOS CI job failed in lipo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The example logs the Rust core build version at startup and shows it on
the connect page, one synchronous FFI call that proves the native
library was bundled and loads on the platform.
…ngine and tracks

Mirrors the Swift integration. The core (livekit-telemetry over UniFFI) owns
every policy; Dart adds only what a core cannot: a bytes-moving transport,
instruments that feed OS signals in, and the wiring of Room / Engine / tracks
/ stats / logs into the core's typed API.

Shape:
- lib/src/telemetry/telemetry.dart: public TelemetryOptions (mirrors
  TelemetryConfig), TelemetryInstrument, Telemetry (configure / setAttribute
  / diagnostics / log), plus the internal ConnectStep vocabulary and the
  zone-carried ambient span. Conditionally exports the hooks from
  telemetry_io.dart (native) or telemetry_web.dart (all no-ops), so the SDK
  keeps compiling for web with telemetry off. UniFFI types never appear in
  the public API; uniffi_io.dart stays the single import site of the
  bindings and re-exports them for telemetry_io.dart.
- Transport: telemetryConfigurePulled + a Dart loop serving the
  TelemetryExportQueue with package:http (uniffi-dart callbacks are
  isolate-bound, so the core never calls into Dart from its threads).
- Instruments: device (app lifecycle and memory pressure via
  WidgetsBindingObserver, network via connectivity_plus, audio outputs via
  Hardware.onDeviceChange), logs (the SDK logger at warn+ into telemetryLog,
  the core's own log lines pulled through log_forward into Logger
  'livekit.ffi.<target>'), rtc (raw getStats reports forwarded per track
  with recordStatsReport; remote track lifecycle for the core-owned
  lk.subscribe span). Capture failures from LocalTrack.createStream.
- Room takes its scope at construction; telemetrySetServer on connect;
  lk.connect span with ws_open / signal / join_recv / pc_created /
  offer_sent / answer_sent / engine / pc_connected / room_connected;
  lk.reconnect per cycle with attempt N quick|full steps; setRoom after the
  join response; disconnected(reason) once per real session;
  room.telemetryTraceId and room.emitTelemetryEvent; lk.publish spans in
  LocalParticipant.publishAudioTrack / publishVideoTrack (parented to an
  open connect span). LiveKitClient.setTelemetry(options) next to initialize.
- Reasons map through telemetryDisconnectReason / telemetryReconnectReason
  from the protocol numbers; the SDK's client-side values map directly.

Tests: test/telemetry/telemetry_e2e_test.dart runs a mock session against a
local collector (connect, publish, quick reconnect, stats windows,
disconnect) and asserts on the OTLP/JSON it wrote; the mock peer connection
grew a sender whose getStats reports an outbound-rtp stream.
telemetry_instruments_test.dart checks instruments start/stop on the Dart
thread.

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

Follows the core fixes (63-bit span ids, a finishable pull queue,
scope-level log records):

- Span ids always round-trip now: TraceSpan.spanId has no fallback and the
  e2e test asserts the log-to-span correlation unconditionally.
- The pipeline's export queue is kept; configure(null) calls finish() after
  the shutdown and a replacing configure() finishes the previous queue, so
  next() resolves null and the serving loop exits, closing its http.Client.
  Covered in telemetry_instruments_test.dart.
- The Room subscribes its engine, signal, RPC and data-stream listeners in
  a zone carrying its RoomTelemetry; a warn/error record logged from one of
  its handlers with no span open goes through TelemetryScope.log and lands
  in the Room's session instead of the process scope. Records inside a span
  keep going through telemetryLog with the span id; records with no Room
  stay process-scoped. The e2e test drives a Room-handler warning and asserts
  it carries the Room's trace and scope attributes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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