Debugging Connection Issues with iroh's Structured Events and Logging
Iroh uses the tracing crate to emit structured events under the iroh::_events target hierarchy, enabling you to diagnose connection failures by filtering logs for path state changes, relay connections, and link events.
Debugging connection issues with iroh's structured events and logging requires understanding how the library emits structured diagnostics via the tracing crate. The n0-computer/iroh repository instruments its QUIC socket layer with granular events that reveal why paths are abandoned, why hole-punching fails, or why relays are unreachable. These structured events are emitted using the tracing::event! macro rather than standard debug! calls, allowing you to attach arbitrary key-value pairs that can be filtered or inspected by log collectors.
Where Structured Events Are Emitted
Iroh emits structured events from several key components in the socket layer. Each event uses a specific target string under the iroh::_events namespace that you can filter in your logging configuration.
The primary event sources are:
iroh::_events::path::open– Emitted iniroh/src/socket/remote_map/remote_state.rswithin theRemoteStateActor::handle_path_eventmethod when a new QUIC path is opened.iroh::_events::path::selected– Emitted when the path selector chooses a new preferred path for a remote endpoint.iroh::_events::path::abandoned– Emitted when a path is dropped because no connection uses it anymore.iroh::_events::relay::connected– Emitted iniroh/src/socket/transports/relay/actor.rswhen the relay actor successfully connects to a relay server.iroh::_events::link_change– Emitted when a local network interface is added or removed.
These events are emitted using the event! macro with explicit targets, allowing you to attach structured fields such as remote = %self.state.endpoint_id.fmt_short(). The handle_path_event method in remote_state.rs (lines 616-1020) contains the logic for path-related events, while relay events appear in actor.rs around lines 388-511.
How Events Propagate Through the System
The event pipeline follows an actor-based architecture that broadcasts state changes to all subscribers.
PathStateSender owns the broadcast channel and records path state changes. Located in iroh/src/socket/remote_map/remote_state/path_watcher.rs, the record_opened method (lines 69-96) forwards events over a broadcast channel with a fixed capacity of BROADCAST_CAPACITY = 8.
PathStateReceiver is held by each Connection and builds a PathEventStream from the broadcast channel. Consumers can await on this stream to receive live events.
RemoteStateActor consumes a merged stream of all connections' path events and reacts to them, such as selecting new paths or triggering hole-punching. The handle_path_event method contains the match logic on NoqPathEvent (around lines 610-640 in remote_state.rs).
When subscribers cannot keep up with the broadcast channel's capacity, the system emits a PathEvent::Lagged event. This serves as a diagnostic signal that the event pipeline is being saturated.
Enabling and Filtering Logs
Iroh does not hard-code a logger; instead, you must initialize a tracing_subscriber layer in your binary or test. The standard pattern appears in iroh/tests/integration.rs (lines 150-154):
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
Control verbosity via the RUST_LOG environment variable (or IROH_LOG). To display all path-related events at debug level:
RUST_LOG=iroh::_events=debug cargo run --example monitor-connections
Filter specific targets to isolate issues:
RUST_LOG=iroh::_events::path::selected=info,iroh::_events::relay::connected=debug cargo run
Adding ,trace to the filter includes lower-level trace! calls scattered throughout the packet processing code.
Inspecting Events Programmatically
To consume events inside your application, create a PathEventStream from a connection:
use iroh::endpoint::Connection;
use iroh::socket::remote_map::remote_state::PathEventStream;
async fn watch_path_events(conn: Connection) {
let mut events: PathEventStream = conn.path_events();
while let Some(event) = events.next().await {
match event {
PathEvent::Opened { id, remote_addr, .. } => {
println!("opened path {id} to {remote_addr}");
}
PathEvent::Selected { id, .. } => {
println!("selected path {id}");
}
PathEvent::Lagged { missed } => {
eprintln!("lost {missed} path events");
}
_ => {}
}
}
}
The PathEventStream type is defined in iroh/src/socket/remote_map/remote_state/path_watcher.rs (lines 31-38) and implements Stream<Item = PathEvent>.
Typical Debugging Workflow
Follow these steps to diagnose connection failures:
- Enable full tracing – Set
RUST_LOG=iroh=debug,iroh::_events=debugto see every structured event, including path opens, selections, and failures. - Narrow to specific targets – Use
RUST_LOG=iroh::_events::path::abandoned=debugto isolate when a path is being dropped. - Observe lagged events – Look for
PathEvent::Laggedin the stream ortrace!messages aboutevent laggedto identify broadcast channel saturation. - Correlate with connection IDs – Filter on a single
conn_idincluded in all events to reduce noise. - Verify selector decisions – Enable
iroh::_events::path::selectedand compare the selected address with the expected one. - Check relay status – Enable
iroh::_events::relay::connectedto verify relay connectivity, as missing relay connections often explain why paths cannot be opened.
Common Pitfalls
- Missing
tracing_subscriberinitialization – Without a subscriber, allevent!calls are silently dropped. Ensure you calltracing_subscriber::fmt::init()early inmain. - Over-filtering – Setting only
infolevel globally suppresses debug-level path events. UseRUST_LOG=debugor explicitly set the target level. - Broadcast overflow – The default capacity of 8 may be too low for high-throughput workloads. When you see
PathEvent::Lagged, consider increasingBROADCAST_CAPACITYinpath_watcher.rs.
Summary
- Iroh emits structured events via the
tracing::event!macro under theiroh::_eventshierarchy. - Path events are emitted in
remote_state.rsand propagated viaPathStateSenderinpath_watcher.rs. - Relay events are emitted in
transports/relay/actor.rs. - Initialize logging with
tracing_subscriber::fmt()and control verbosity withRUST_LOGenvironment variables. - Use
Connection::path_events()to obtain aPathEventStreamfor programmatic inspection. - Watch for
PathEvent::Laggedto detect event pipeline saturation.
Frequently Asked Questions
Why don't I see any iroh log events?
Ensure you have initialized a tracing_subscriber before creating the iroh endpoint. Without a subscriber, all tracing::event! calls are silently discarded. Call tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init() early in your main function.
What does PathEvent::Lagged indicate?
This event indicates that the broadcast channel (with capacity BROADCAST_CAPACITY = 8) is full and your consumer missed events. It signals that the event pipeline is being saturated, which may happen under high-throughput workloads. Consider increasing the buffer capacity or consuming events faster.
How do I filter logs for a specific connection?
All events include a conn_id field. You can filter your logs to show only events for a specific connection by using structured filtering in your log collector, or by searching for the specific connection ID string in the log output. The events include structured fields like remote = %self.state.endpoint_id.fmt_short() that help correlate events.
Where are relay connection events emitted?
Relay connection events are emitted in iroh/src/socket/transports/relay/actor.rs (lines 388-511) using the target iroh::_events::relay::connected. These events fire when the relay actor successfully establishes a connection to a relay server, which is crucial for diagnosing hole-punching failures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →