Skip to main content

Overview

whatsapp-rust ships an optional tracing 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

Add whatsapp-rust with the tracing feature, plus tracing-subscriber for the consumer side:
Cargo.toml
The existing 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.
Do not enable the log feature on the tracing crate together with the log → tracing bridge. That recurses. whatsapp-rust already pins tracing with default-features = false so the hazard cannot happen inside the library, but be careful when adding tracing to your own dependencies.

Wiring a subscriber

A minimal tracing-subscriber setup driven by RUST_LOG:
src/main.rs
Run it with the feature on:
A runnable version of this wiring (with OpenTelemetry stubs) ships as examples/observability.rs in the source repo.

OpenTelemetry / OTLP

To export spans to an OTLP collector (Jaeger, Tempo, Honeycomb, etc.), add opentelemetry, opentelemetry-otlp, and tracing-opentelemetry, then append a layer to the subscriber:
src/main.rs
Every wa.* span is then exported as an OTLP span with its fields intact.

Span taxonomy

Spans are grouped under a stable wa.<area>.<op> naming scheme so you can filter or build dashboards per area. The current areas are:

Levels

Most spans are emitted at debug or trace. The connection-lifecycle spans (wa.conn.connect, wa.conn.disconnect, wa.conn.reconnect, wa.conn.run, 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. The wa.conn.run session-root span records your account’s own LID, so traces are attributable per account in multi-account deployments. A downstream binary can statically strip lower levels at compile time with tracing’s release_max_level_info / release_max_level_warn features.

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 with pn#<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 Signal ProtocolAddress names 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.
Redaction only covers identifiers the library emits. Anything your own application code logs — raw JIDs, phone numbers, message bodies — reaches the exporter unredacted under your own targets. Scrub them with Jid::observe() (and observe_protocol_address() for Signal addresses) before logging.

tracing-pii (local debugging only)

For local debugging where you need to see raw phone numbers, enable the tracing-pii feature:
This makes 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.