Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tracing and Logging

Peeroxide uses the tracing crate for all diagnostic output across its four crates (libudx, peeroxide-dht, peeroxide, peeroxide-cli). This appendix documents the conventions operators and developers can rely on when filtering, capturing, or extending log output.

Target conventions

Every tracing call has a target — a string used by the EnvFilter to decide whether to emit the event. Peeroxide uses two kinds of targets:

1. Module-path targets (default). Most calls inherit their target from the Rust module path: peeroxide_dht::holepuncher, peeroxide_dht::hyperdht, libudx::native::stream, and so on. These are the natural granularity for developers debugging a specific subsystem. EnvFilter directives prefix-match, so peeroxide_dht::holepuncher=trace enables that one module and its children.

2. Reserved peeroxide::_events::* lifecycle targets. A curated set of high-signal operator-facing events use a stable, hand-picked target under peeroxide::_events::. These are not Rust modules — they are fixed labels that survive refactors. Examples:

peeroxide::_events::swarm::started
peeroxide::_events::dht::bootstrapped
peeroxide::_events::peer::connected
peeroxide::_events::peer::connect_failed
peeroxide::_events::holepunch::probe_received
peeroxide::_events::holepunch::passive_reflected
peeroxide::_events::holepunch::nat_settled
peeroxide::_events::holepunch::final_punch_sent
peeroxide::_events::holepunch::connected
peeroxide::_events::holepunch::aborted
peeroxide::_events::holepunch::failed_no_verified_addr

Operators tail these to get a clean lifecycle stream without developer noise:

RUST_LOG=peeroxide::_events=info peeroxide cp send ./file

Level discipline

LevelUsed forDefault visibility
errorFatal conditions; the operation cannot proceedalways
warnRecoverable anomalies, validation failures, retriesalways
infoLifecycle events (peeroxide::_events::*) + startup-v and above
debugPer-connection / per-round state transitions-vv and above
tracePer-packet, per-loop iterationonly with explicit RUST_LOG

Anything that fires more than once per significant operation lives at debug or below. Per-packet paths live at trace. The info level is reserved for the _events::* subtree plus a small handful of true startup events.

CLI verbosity

The peeroxide CLI exposes three verbosity levels via the -v flag, each composing a default EnvFilter:

FlagDefault filterWhat you see
(none)warn,peeroxide::_events=infoWarnings + lifecycle events
-vpeeroxide=info,peeroxide_dht=info,peeroxide::_events=info,warnInfo-level developer events across both swarm and DHT crates
-vvpeeroxide=debug,peeroxide_dht=debug,libudx=debug,peeroxide::_events=info,infoFull debug stream across all peeroxide crates

The RUST_LOG environment variable always overrides the default. Any EnvFilter directive syntax is supported:

RUST_LOG=peeroxide_dht::holepuncher=trace peeroxide cp send ./file
RUST_LOG=peeroxide::_events=info,libudx=warn peeroxide cp recv <topic> -
RUST_LOG=peeroxide_dht::hyperdht=trace,peeroxide_dht::io=debug peeroxide node

Subsystem map

The 8 natural subsystems and the targets that feed them:

SubsystemModule pathEvent subtree
holepunchpeeroxide_dht::holepuncherpeeroxide::_events::holepunch::*
natpeeroxide_dht::nat(none currently)
socket_poolpeeroxide_dht::socket_pool(none currently)
relaypeeroxide::cmd::relay, peeroxide::cmd::node, peeroxide_dht::relay_service, peeroxide_dht::blind_relay(none currently; module-path logs only)
discoverypeeroxide_dht::query, peeroxide::peer_discovery(none currently)
swarmpeeroxide::swarmpeeroxide::_events::swarm::*, peeroxide::_events::peer::*
dht_rpcpeeroxide_dht::rpc, peeroxide_dht::iopeeroxide::_events::dht::*
udxlibudx::native::*(none currently)

Relay is intentionally module-path only today. peeroxide::cmd::relay and peeroxide::cmd::node emit the operator-facing startup / shutdown / periodic relay stats at info; peeroxide_dht::relay_service emits self-announce success/failure, handshake/finalization, capacity rejections, and bridge failures at debug/warn; and peeroxide_dht::blind_relay emits pair request/response activity, pairing matches, idle-session / expired-pairing sweeps, and malformed frame drops at debug/trace.

New _events::* labels should be added sparingly, only when an event represents an operator-visible lifecycle transition (something a production operator would want in a clean default-level log).

Adding a new lifecycle event

When wiring a new high-signal event, use an explicit target:

#![allow(unused)]
fn main() {
tracing::info!(
    target: "peeroxide::_events::holepunch::nat_settled",
    round,
    "NAT settled + verified remote, transitioning to final punch round"
);
}

When the same site also wants developer-level detail at debug level, emit two separate calls or include enough structured fields in the single info! so it serves both audiences (the latter is preferred).

Anti-patterns

  • eprintln! for telemetry. Bypasses level filtering and structured fields. Always use a tracing macro.
  • Emitting at info from a per-packet path. Demote to debug or trace; the info level is reserved for lifecycle events.
  • Inventing many ad-hoc targets. Stick to module-path defaults unless the call belongs to a curated _events::* lifecycle category.
  • Putting expensive computation outside the tracing macro. The tracing macros short-circuit on the level filter before evaluating field expressions, so inline format!() / .collect() calls inside the macro are gated. The same code as a let outside the macro always runs.

Reference