Overview
whatsapp-rust ships an optionaltracing Cargo feature that instruments the library end-to-end: connect/disconnect, receive and decrypt, send, IQ, app state, pairing, media, receipts, retries, notifications, and session/crypto flows. With the feature on you can map a production error to who (which account), where (which span), how (the call path), and why (the failure attached to the span).
The library only emits tracing spans and events. It never installs a subscriber and does not depend on OpenTelemetry — your application owns the subscriber, the filtering, and any OTLP/Jaeger exporter.
The
tracing feature is off by default. With it disabled there is no tracing dependency and the instrumentation attributes vanish at compile time, so there is zero runtime cost.Enabling the feature
Addwhatsapp-rust with the tracing feature, plus tracing-subscriber for the consumer side:
Cargo.toml
log::{info,warn,error}! calls inside the library continue to work. tracing-subscriber’s default tracing-log feature bridges them into the subscriber, so they appear as events attached to the active wa.* span — even before you adopt any new span yourself.
Wiring a subscriber
A minimaltracing-subscriber setup driven by RUST_LOG:
src/main.rs
examples/observability.rs in the source repo.
OpenTelemetry / OTLP
To export spans to an OTLP collector (Jaeger, Tempo, Honeycomb, etc.), addopentelemetry, opentelemetry-otlp, and tracing-opentelemetry, then append a layer to the subscriber:
src/main.rs
wa.* span is then exported as an OTLP span with its fields intact.
Span taxonomy
Spans are grouped under a stablewa.<area>.<op> naming scheme so you can filter or build dashboards per area. The current areas are:
Levels
Most spans are emitted atdebug or trace. The connection-lifecycle spans (wa.conn.connect, wa.conn.disconnect, wa.conn.reconnect, wa.conn.logout) are at info so connection state is visible at the default level. Failures surface at ERROR via err(Debug) on the instrumented function, and the existing warn!/error! log calls surface through the bridge — with two exceptions: wa.conn.connect surfaces failures at WARN instead (its caller already classifies the real failures as error!, so the default ERROR was double-reporting transient handshake retries), and wa.conn.read_loop returns Ok (not Err) for a routine server-initiated stream recycle, so its ERROR capture fires only for genuine failures — never for WhatsApp’s normal periodic reconnects.
There is no span wrapping the client’s outer auto-reconnect loop (
Client::run). A span there would live for the entire client lifetime and only report at shutdown, distorting duration/throughput dashboards the same way a whole-connection keepalive span would — so it is deliberately left uninstrumented. Connection-lifecycle visibility comes from the per-attempt spans above; account identity comes from the per-operation spans below.tracing’s release_max_level_info / release_max_level_warn features.
Account identity in spans
Thewa.conn.connect, wa.conn.read_loop, wa.iq, and wa.send.message spans carry lid and pn fields for your own account, so traces are filterable and groupable per account in multi-account deployments. pn is redacted via Jid::observe() like any other phone-number field (see PII handling below); lid is rendered in full since it is pseudonymous.
To tag your own spans or error context with the same account identity, call Client::identity_tags() (available whenever the tracing feature is enabled on the whatsapp-rust dependency — no local feature of your own is required):
tracing’s Value impl for Option<T> records the field only when the value is Some, so a missing lid or pn is left absent on the event instead of printing as None or an empty string — matching how wa.conn.connect / wa.iq / wa.send.message behave internally, and keeping both tags on a single event instead of splitting them across two.
identity_tags() returns an IdentityTags { lid: Option<String>, pn: Option<String> } snapshot — a named struct rather than a tuple, so LID/PN cannot be silently transposed at the call site. pn is already the redacted pn#<token> form produced by Jid::observe(), not the raw phone number, so logging it directly — as in the example above — does not leak PII by default; lid is rendered in full since it is pseudonymous. This guarantee only holds while the tracing-pii feature stays disabled (the default) — with tracing-pii enabled, identity_tags() inherits Jid::observe()’s raw-number behavior like every other redacted value on this page, so pn becomes the actual phone number (see tracing-pii below). Both fields read from the same device snapshot used internally, so they stay consistent with what the library’s own spans record.
Filtering examples
RUST_LOG accepts span/event targets the same way it accepts log targets:
PII handling
WhatsApp identifiers contain phone numbers, so the library redacts them before they reach a span field or a log line.Jid::observe()renders LID, group, broadcast, newsletter, and bot JIDs in full — they are pseudonymous or non-personal, so the same peer or chat still correlates across spans. Phone-number user JIDs are replaced withpn#<token>, where the token is a keyed SipHash (the key is a process-lifetime random seed kept only in memory). An unkeyed hash of an E.164 number is reversible by precomputation; the keyed scheme is not.- Legacy group IDs of the form
<creator-phone>-<timestamp>keep the timestamp and redact only the numeric prefix. observe_protocol_address()applies the same scheme to SignalProtocolAddressnames embedded in logs.- The library’s own
log!calls already pipe JIDs and addresses through these helpers, so the bridged log lines carry the same redaction as the span fields.
tracing-pii (local debugging only)
For local debugging where you need to see raw phone numbers, enable the tracing-pii feature:
Jid::observe() and observe_protocol_address() render raw numbers instead of the pn#<token> placeholder. Never enable this in production.
Overhead
Because spans are mostly
debug/trace/info, a release build with release_max_level_info strips the rest without code changes.
Related
- Metrics with the metrics facade — aggregate
wa_*counters, histograms, and gauges that complement these per-case spans. - Configuring log targets —
log-based filtering that also works with the tracing bridge. - Installation — feature flags — full feature matrix including
tracing,tracing-pii, andmetrics.