Repository navigation
Conversation
pblazej
force-pushed
the
blaze/telemetry
branch
2 times, most recently
from
September 4, 2026 12:35
a951a3b to
6f17b80
Compare
This was referenced Sep 15, 2026
Open
Open
This was referenced Oct 1, 2026
pblazej
force-pushed
the
blaze/telemetry
branch
from
October 1, 2026 13:53
6aba1b6 to
a9ec8d5
Compare
pblazej
changed the base branch from
main
to
blaze/telemetry-stack/7-process-pipeline
October 1, 2026 13:53
pblazej
marked this pull request as ready for review
October 1, 2026 14:33
pblazej
requested review from
1egoman,
davidliu,
hiroshihorie,
lukasIO and
xianshijing-lk
and removed request for
ladvoc
October 1, 2026 14:33
pblazej
force-pushed
the
blaze/telemetry
branch
from
October 23, 2026 06:09
a9ec8d5 to
3d50ec6
Compare
`telemetry_configure` installs the process pipeline with a host-implemented `TelemetryTransport` or the `livekit-net` HTTP client, and `telemetry_configure_pulled` hands bindings with thread-bound callbacks (Dart) a bounded pull queue instead. `telemetry_scope` gives each Room its `TelemetryScope`: `set_server` at connect and on every token refresh, spans, the subscribe lifecycle, `record_peer_stats` paced by `stats_poll_interval_ms`, `track_ended`, `disconnected`, and the app API (`emit_custom`, `set_attribute`). `telemetry_disable` is the synchronous opt-out and `telemetry_is_disabled` reads it process-wide; device state, device events and log records come from the host. Dart tests cover the pull queue, and macOS links reserve header padding for Flutter's install name rewrite. The module is a core module of `livekit-ffi` (feature `core-modules`), the crate `livekit-uniffi` merged into.
Once a platform installs the log forwarder, warnings and errors from `livekit*` targets also reach the telemetry pipeline as log records, with any compact JWT and bearer or token credential values masked and the target as the logger (`log` has no function name to fill in); the telemetry crate's own records are never copied. The platform's level still filters only what is forwarded to it, so the console output is unchanged.
`telemetry_ping` records a small session (an `lk.connect` span with its checkpoints, an `lk.publish`, an `lk.subscribe` ended by first media, a custom event) and exports it to the collector `LK_TELEMETRY_ENDPOINT` names; the README shows how to run it against a local OpenTelemetry backend, and a Dart test does the same through the pull queue.
One lifecycle task now owns the whole process pipeline lifecycle. Configure, shutdown and the opt-out's purge are commands it carries out one at a time, in the order they were sent: it installs, uninstalls and purges itself, so a configure and a shutdown can no longer interleave. `telemetry_configure` waits until its pipeline is installed, which never includes a drain, and the instruments start on the lifecycle's thread. The lifecycle hands each installed pipeline's exporter to its exporter runner, which runs them one at a time, in install order. A replacing exporter starts only once the one before it stopped: drained within its pipeline's own `shutdown_bound`, or else cancelled and joined at that bound, never left running behind a dropped task handle. Two exporters therefore never replay a shared cache at the same time, and a batch in flight is not sent twice, however many replaces come within one drain. `telemetry_shutdown` returns once every exporter queued before it has stopped; when the slot still holds the lifecycle its command went to, and that command was the last one, it then ends the lifecycle and joins it with its runner and exporter, and the next configure starts a fresh one. Each lifecycle has a generation of its own, so a shutdown that resumes late never ends a newer lifecycle whose last command has the same number. `telemetry_flush` and `telemetry_shutdown` wait for the opt-out's purge through the same command order. `global::uninstall` is public, the synchronous half of `global::shutdown`, for the lifecycle to uninstall in turn. The pull queue's `serve` and `finish` read the finished flag under the in-flight lock, so nothing is handed out or kept once `finish` returned. A pulled configure that is refused after the opt-out finishes its queue, so `next` no longer waits forever. Platform log lines passed to `telemetry_log` or `TelemetryScope.log` get the same credential masking as the core's own copied lines. Its test reads the body uploaded through `TelemetryScope.log`, so it fails if that call drops the mask (`flate2` is a new dev-dependency for it). The pull queue's docs sit on the queue, it has no foreign constructor, and its fields and the span methods document their edge cases. `Pending` is `QueuedExport` and `mask_jwts` is `mask_credentials`. The forwarder maps levels with a new `From<log::Level>` for `Severity`. The module has its license header, the SPEC states the real masking rules, the example uses the workspace edition, the stray `.gitignore` entry is gone, and the `livekit-ffi` changeset names the `log_forward_bootstrap` change. A `livekit-telemetry` changeset records the new conversion and `global::uninstall`.
pblazej
force-pushed
the
blaze/telemetry
branch
from
October 23, 2026 09:56
3d50ec6 to
a2558a4
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
livekit-telemetry— the shared client-telemetry core — and its UniFFI surface for the Swift, Kotlin and Dart SDKs. Wire spec (events, attributes, cadence, upload policy):livekit-telemetry/SPEC.md.#1566 stacks on this PR: it takes every telemetry name from the protocol registry.
Changes
livekit-ffi/src/core_modules/telemetry.rs: configure and configure-pulled, scopes, spans, stats, the app API, the opt-out, device statelog_forward.rs: the core's own warnings and errors copied into telemetrytelemetry_pingexample and the README's local testing guidetelemetry_configurewaits until its pipeline is installedtelemetry_shutdownreturns once every exporter queued before it stopped, and a shutdown that resumes late never ends a newer lifecyclefinishreturned, and a pulled configure refused after the opt-out finishes its queuetelemetry_logorTelemetryScope.logget the same credential masking as the core's own copied linesWhat a platform calls, and when
telemetry_configure(config, transport, instruments)— storage and tuning only; Dart:telemetry_configure_pulled(config, [])+queue.try_next()telemetry_scope()scope.set_server(url, token)— cheap, idempotentscope.start(SpanName, parent)→step/set_track/end;subscribe_started/subscribed(tracks already in the room at join:subscribe_startedat connect, solk.subscribemeasures from join; a lonesubscribedstill opens the span as a fallback) /subscribe_failed;track_ended(sid);set_room;disconnected(reason)scope.stats_poll_interval_ms():scope.record_peer_stats(report, {trackId → sid}, ts)per peer connectiontelemetry_set_device_state(thermalUnknown/ low powerNonewithout a source),telemetry_device_event,telemetry_logscope.emit_custom(name, {k: v}),scope.set_attribute(key, value?),telemetry_disable()— synchronous, in effect when it returns (TODO on platforms: token discussion);telemetry_is_disabled()reads that opt-out process-wide (every thread and Dart isolate), for platforms to check right before eachgetStatsor submitNo endpoint, header or sink exists in any client-language API. Local end-to-end tests set
LK_TELEMETRY_ENDPOINT(read by the core; see the README).Local testing against a local OpenTelemetry backend (LGTM)
Everything can be tried against a local OpenTelemetry backend (LGTM) in a couple of minutes; no LiveKit server is needed for the Rust example.
Then open http://localhost:3000 → Explore: Loki
{service_name="telemetry_ping"}for the log records (otel.event.name:custom.ping,lk.rtc.stats.sample,lk.telemetry.report), Tempo with the printed trace id for the spans (lk.connect,lk.publish,lk.subscribe). Platform e2e tests setLK_TELEMETRY_ENDPOINTthe same way (withlivekit-server --devfor a real Room).Verification
At
a2558a40, from a clean checkout:cargo fmt --checkcargo clippy -p livekit-telemetry --all-targets -- -D warnings, also with--all-featuresand as acargo hack --feature-powerset --depth 2runcargo test -p livekit-telemetry: 275 unit, 2 docRUSTDOCFLAGS="-D warnings" cargo doc -p livekit-telemetry --no-depscargo test -p livekit-ffi --no-default-features --features core-modules: 34cargo clippy -p livekit-ffi --no-default-features --features core-modules --all-targets: only the 7 warnings thatmain(6473ec17) hascargo clippy -p telemetry_ping --all-targets -- -D warningscargo make --profile release android-packageinlivekit-ffigenerates the bindings andassembleReleasesucceeds, and a fresh./gradlew compileReleaseKotlin --rerun-taskspasses.android-check-sizefails: the arm64 library is 2,298,256 bytes against a limit of 1,769,472 bytes (main: 1,593,408 bytes)cargo make dart-package,dart pub get,dart test: 7 pass, 1 skip. The skipped live collector test passes against a local collectorStages 2–7 pass the same
livekit-telemetrygates on their own tips (see each PR).