Debugging
Local diagnostic captures, structured Rust traces, and admission snapshots
Start collection before reproducing the problem. Keep the typed error returned by the failing operation separate from the diagnostic timeline. Diagnostics observe OpenRTC's current owners; they never reconnect, renew a ticket or change readiness.
Browser, Node and Tauri TypeScript
Enable an app-owned logger at construction to see activation start, success and failure, including failures before a capability handle exists:
import { OpenRTC } from 'openrtc';
const rtc = OpenRTC({
apiKey,
diagnostics: { logger: event => console.debug('[openrtc]', event) },
});
const ticket = await rtc.tickets.join(invitation);
Activation records contain SDK-realm client and operation references, avenue kind, outcome, timestamp and duration. Duplicate activation calls share the existing owner and produce one operation. Logger exceptions do not change the returned result. Use the operation's typed error for the detailed failure code; arbitrary error messages and credentials are deliberately absent from these records.
Once a handle exists, capture its connection changes:
const capture = ticket.diagnostics.capture({
maxEvents: 256,
// Optional: forward these structured records to your existing logger or
// breadcrumb adapter. Keep the callback quick and avoid runtime commands.
logger: event => console.debug('[openrtc.peer]', event),
});
// Reproduce the problem through your normal application controls.
capture.stop();
const supportJson = JSON.stringify(capture.snapshot(), null, 2);
// Show/save this string through your app's normal support flow.
Capture starts when called; it cannot recover past events. It uses the existing
peer observer without timers, pings or coordination reads. Identical projected
states are coalesced. Defaults are 256 events, with an explicit maximum of 1024;
the oldest event is evicted and counted in droppedEvents. At most 64 distinct
peers are tracked; peerLimitReached reports truncation. stop() unsubscribes
and keeps the retained timeline available. Saving and uploading are app-owned.
Ticket capture includes pending peers already assigned to that ticket, not
other tickets sharing its runtime. A peer without an assigned ticket scope
cannot be attributed to that ticket yet. Constructor activation logging covers
that earlier stage. A snapshot includes SDK version (source for unbundled
source), generation numbers, stable readiness codes, coarse admission/transport
state and capture-local peer aliases. It omits names, full identifiers, grant
IDs, invitations, URLs, credentials, raw errors and payloads. Aliases are local
to one capture and are not cross-device trace IDs.
Rust native hosts and iOS packet-tunnel extensions
Request the SDK owner's snapshot directly; stdout capture is unnecessary:
let snapshot = client.diagnostic_snapshot().await;
let support_json = serde_json::to_string_pretty(&snapshot)?;
This returns at most 64 peers plus omittedPeers, SDK version, timestamp and
target. Each peer includes settledReady, a stable readinessCode, current
transport/route generations and these security facts:
admissionandauthoritativeDeviceBound;cryptoKeyInstalledandcryptoConfirmed(no key material);nativeDirectionalAdmission.inboundRequired/inboundProvenandoutboundRequired/outboundProvenfor the selected physical generation.
The WASM binding also exposes diagnostic_snapshot(). Native directional stream
contracts do not exist on WASM, so that field is null. Browser TypeScript
captures observe public peer projections; they do not promise native proof
details from an older/custom host. Snapshot facts are best-effort observations
read from their owners, not a cross-owner atomic authorization receipt.
On iOS, return the serialized snapshot through an existing authenticated, bounded app/extension message channel. Use the app's support export UI. Do not redirect stderr into account storage, clear App Group data to collect a log, or add another endpoint/lifecycle controller for diagnostics.
Structured Rust tracing
OpenRTC emits tracing spans/events without installing a global subscriber.
Connect, ticket issue/join and admission operations have spans; critical scope,
epoch, channel-open and protected-stream-fence decisions emit structured events.
Arguments use skip_all; raw tickets, keys, error text and payloads are never
recorded by these new instrumentation sites.
If your application already has tracing, add these targets to its existing filter and keep its subscriber/exporter. For an application without one:
[dependencies]
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
use tracing_subscriber::fmt::format::FmtSpan;
// Application startup, once. OpenRTC itself never installs this subscriber.
tracing_subscriber::fmt()
.with_env_filter("off,openrtc::admission=debug,openrtc::stream=debug")
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.with_ansi(false)
.json()
.with_writer(std::io::stderr)
.try_init()?;
JSON events carry span context; close events include timing. connection_ref
is a short SHA-256 pseudonym for correlating an SDK connection without emitting
its full identifier. It is not a credential or an anonymity guarantee. A
scope_accepted event records scope kind and whether a durable device binding
exists; inbound_epoch_changed/outbound_epoch_changed mark security transitions.
Protected-stream access_denied identifies changed authority or the key and
confirmation-generation predicates. Collection does not weaken those fences.
For persistent native logs, use your app's existing private log directory and standard bounded/non-blocking writer, such as tracing-appender: choose rotation and retention, use a distinct filename prefix, bound the queue, and retain its worker guard until shutdown. File-count retention does not bound the bytes in one active file; impose your app's size/session budget too. Avoid unbounded retention. SDK collection and exports never happen automatically.
If you already use OpenTelemetry, the standard tracing-opentelemetry layer can export the same Rust spans through your configured provider. OpenRTC does not install a telemetry SDK, exporter, remote collector or tracing subscriber.
Reading a pending connection
| Code/fact | Interpretation |
|---|---|
waiting-for-transport / missing-active-transport | No currently usable physical generation. |
session-admission-pending | Application authority has not been accepted. |
native-main-route-pending | Inspect required/proven inbound and outbound facts for this generation. |
application-crypto-key-pending | The required application key has not been installed. |
application-crypto-confirmation-pending | The peer's key confirmation for this generation is pending. |
bounded-health-confirmation-pending | Admission/key facts are insufficient to declare transport health. |
access_denied with authority_changed | The stream's exact scoped authority expired, was revoked or changed. |
unclassified | This free-form reason was omitted; inspect the typed operation error locally. |
Match timestamp, connection reference and generation before comparing two observations. A changed generation is not automatically a new logical peer. Historical verbose flags and free-form stderr/console output are not the allowlisted support format; do not append them blindly to a shareable capture.
This follows Rust tracing's library/application split and OpenTelemetry's library instrumentation guidance.