Table of Contents
Observability
Metrics, watchers, path and connection introspection, net reports, qlog, and the TLS key log — and the one thing go-iroh does not have, a debug log.
go-iroh exposes its runtime state through typed APIs rather than a log stream: counters you snapshot, watchers you subscribe to, and per-connection introspection you poll or stream. This page walks the whole surface, then is honest about the gap.
Endpoint counters
Endpoint.Metrics() returns a point-in-time snapshot: endpoint-level
connect/accept counters plus two nested groups, SocketMetrics (per-transport
datagram counters, path counts, holepunch attempts, relay home changes) and
NetReportMetrics. The same two groups exist in Rust iroh’s
EndpointMetrics, so dashboards translate directly.
The snapshot integrates with the standard ecosystems three ways:
String()implementsexpvar.Var, soexpvar.Publish("iroh", ep.Metrics())works — though note it captures one snapshot, not a live view.Snapshot()returns named counters for ametrics.Registry.WriteOpenMetrics(w)writes OpenMetrics text under theendpointprefix, which is what a Prometheus scrape wants:
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
if err := ep.Metrics().WriteOpenMetrics(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
Watchers
Two endpoint properties are observable as they change, through
watch.Observer (the Go analog of Rust’s n0-watcher): Current() for the
value now, Updated(ctx) to block for the next change, Stream(ctx) for an
iterator.
// React to this endpoint's address changing (new direct address,
// relay change, path loss).
for addr := range ep.WatchAddr().Stream(ctx) {
fmt.Println("addresses now:", addr)
}
Endpoint.HomeRelayStatus() is the same shape for the home relay connection:
the watched value is nil until a home relay is selected and updates whenever
the relay or its connection state changes.
Per-connection introspection
Conn.Paths()— snapshot of the connection’s QUIC multipath paths:PathInfocarries the path ID, whether the path is validated, its transport address, and its smoothed RTT when observed.Conn.WatchPaths(ctx)— the same snapshots as a stream. The first value is the current state; later values arrive when the endpoint observes a path change for the peer. This is how you watch a connection migrate from relay to a direct path.Conn.Stats()— transport totals for the connection:MinRTT,LatestRTT,SmoothedRTT,MeanDeviation, plus bytes and packets sent and received.Endpoint.RemoteInfo(id)— everything the endpoint currently knows about how to reach a remote: its transport addresses and their state.
paths, err := conn.WatchPaths(ctx)
if err != nil {
return err
}
for snapshot := range paths {
for _, p := range snapshot {
fmt.Println(p.ID, p.Validated, p.Addr, p.RTT)
}
}
Net reports
With iroh.WithNetReport(), the endpoint refreshes a network report in the
background after Bind. Endpoint.NetReport() returns the most recent
report — UDP reachability per family, whether the observed public address
varies by destination (the NAT-mapping question), and related findings. The
second result is false until the first report completes. See
Troubleshooting for reading one.
qlog
The QUIC transport writes qlog
traces when the QLOGDIR environment variable names a directory:
mkdir -p qlogs
QLOGDIR=qlogs go run .
Files are named <odcid>_<perspective>.sqlog, one per connection, and
capture packet-level transport behavior — including the multipath and NAT
traversal frames. This is the deepest view available and the one to reach for
when a connection misbehaves in a way counters cannot explain.
TLS key log
iroh.WithKeyLogWriter(w) writes TLS traffic secrets for direct peer QUIC
handshakes in NSS SSLKEYLOGFILE format, which lets Wireshark decrypt a
packet capture. Its doc comment carries the warning that matters: it is for
debugging only; writing these secrets compromises connection confidentiality.
Watching connections happen
iroh.WithHooks observes (and can reject) outbound dials before they start
and handshakes as they complete — see
Observing and rejecting connections. For a worked
diagnostic program that prints relay status, net report, latency, and path
information, see example
37-doctor;
the gistat tool prints connection statistics and path information
for a live target.
What go-iroh does not have: a debug log
The transport internals — the magic socket, the relay client, path management
and holepunching — do not log. The only logger in the public API is
RouterConfig.Logger, which covers protocol dispatch, not the transport.
Rust iroh instruments all of this with tracing spans filterable by
RUST_LOG; go-iroh currently has no equivalent, and a live connectivity
problem is debugged from the APIs above (metrics deltas, WatchPaths, hooks)
plus qlog, not from a narrative log. Endpoint-level structured tracing is an
open design item for go-iroh.
Next steps
- Troubleshooting — applying these tools to a connection that will not establish.
- Observing and rejecting connections — the hooks API.
- Testing and interop — evidence gates beyond a single process.