Repository: fujiapple852/trippy (language: rust)

Issue:
tracing logs from `trippy-core` are verbose and should use a lowel level than "info"
**Describe the bug**

I am trying out `trippy-core` functionality from an existing application, and the first time I ran it I was surprised by a large number of messages at the INFO level:
```plaintext
INFO [trippy_core::tracer::inner] run;
INFO [trippy_core::tracer::inner] run_internal;
INFO [trippy_core::net::platform::unix::address] discover_local_addr; target_addr=8.8.8.8 port=80
INFO [trippy_core::net::platform::unix::socket] new_udp_dgram_socket_ipv4;
INFO [trippy_core::net::platform::unix::socket] connect; address=8.8.8.8:80
INFO [trippy_core::net::platform::unix::address] return=Ok(192.168.77.237)
INFO [trippy_core::net::channel] connect;
INFO [trippy_core::net::platform::unix::socket] startup;
INFO [trippy_core::net::channel] make_icmp_send_socket; addr=192.168.77.237 raw=true
INFO [trippy_core::net::platform::unix::socket] new_icmp_send_socket_ipv4; raw=true
INFO [trippy_core::net::platform::unix::socket] set_header_included; included=true
INFO [trippy_core::net::channel] make_recv_socket; addr=192.168.77.237 raw=true
INFO [trippy_core::net::platform::unix::socket] new_recv_socket_ipv4; raw=true
INFO [trippy_core::net::platform::unix::socket] set_header_included; included=true
INFO [trippy_core::strategy] new;
INFO [trippy_core::strategy] run;
INFO [trippy_core::strategy] send_request;
INFO [trippy_core::strategy::state] next_probe; sent=SystemTime { tv_sec: 1737575756, tv_nsec: 800926796 }
INFO [trippy_core::net::channel] send_probe; probe=Probe { sequence: Sequence(33434), identifier: TraceId(0), src_port: Port(0), dest_port: Port(0), ttl: TimeToLive(1), round: RoundId(0), sent: SystemTime { tv_sec: 1737575756, tv_nsec: 800926796 }, flags: Flags(0x0) }
INFO [trippy_core::net::channel] dispatch_icmp_probe;
INFO [trippy_core::net::ipv4] dispatch_icmp_probe;
INFO [trippy_core::net::platform::unix::socket] send_to; addr=8.8.8.8:0
INFO [trippy_core::strategy] recv_response;
INFO [trippy_core::net::channel] recv_probe;
INFO [trippy_core::net::channel] recv_icmp_probe;
INFO [trippy_core::net::platform::unix::socket] is_readable; timeout=10ms
INFO [trippy_core::net::ipv4] recv_icmp_probe;
INFO [trippy_core::net::platform::unix::socket] read;
INFO [trippy_core::net::platform::unix::socket] return=Ok(112)

... with a lot of logging on every network request:
NFO [trippy_core::net::platform::unix::socket] is_readable; timeout=10ms
INFO [trippy_core::strategy] update_round;
INFO [trippy_core::strategy] send_request;
INFO [trippy_core::strategy] recv_response;
INFO [trippy_core::net::channel] recv_probe;
INFO [trippy_core::net::channel] recv_icmp_probe;
INFO [trippy_core::net::platform::unix::socket] is_readable; timeout=10ms
INFO [trippy_core::strategy] update_round;
INFO [trippy_core::strategy] send_request;
INFO [trippy_core::strategy] recv_response;
INFO [trippy_core::net::channel] recv_probe;
INFO [trippy_core::net::channel] recv_icmp_probe;
INFO [trippy_core::net::platform::unix::socket] is_readable; timeout=10ms
INFO [trippy_core::strategy] update_round;
INFO [trippy_core::strategy] send_request;
INFO [trippy_core::strategy] recv_response;
INFO [trippy_core::net::channel] recv_probe;
INFO [trippy_core::net::channel] recv_icmp_probe;
```

**To Reproduce**

Any usage of `trippy-core` appears to trigger a lot of tracing events, but I am doing approximately this:
```rust
let tracer = trippy_core::Builder::new(addr)
    .min_round_duration(Duration::from_millis(500))
    .max_round_duration(Duration::from_millis(2000))
    .max_rounds(Some(10))
    .build()?;

tracer.run()?;
let state = tracer.snapshot();
```

**Expected behavior**

I expect this volume of events to be at the debug or trace level so they don't interfere with other application logs. It's a chore to have to filter out `trippy-core` specifically during log initialization because it's so verbose it drowns out all the other messages.

**Environment Info**

- OS: Linux
- Trippy version: `trippy-core v0.12.2`
- Installation method: cargo
- Terminal / Console: n/a



Discussion / hints:
It should be possible to set the tracing level for different targets separately in your subscriber. For example I usually set zbus to warn because they also have a lot of logs at info (which for them makes sense but not so much for me).

```rust
use tracing_subscriber::{
    fmt::{self, format::FmtSpan},
    prelude::*,
    EnvFilter,
};

tracing_subscriber::registry()
    .with(fmt::layer().with_span_events(FmtSpan::ACTIVE))
    .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| {
        EnvFilter::new(if cfg!(debug_assertions) {
            "zbus=warn,info"
        } else {
            "warn"
        })
    }))
    .init();
```
@eric-seppanen I agree that the tracing is overly verbose when Trippy used as a library. It is even more verbose when you enable the span events logging. For background, the tracing was added primarily for the Trippy application as a diagnostic aid, where being overly verbose is useful when people submit issues.

I'll look at downgrading many of these tracing from `info` (which seems to be the [default](https://docs.rs/tracing/latest/tracing/attr.instrument.html)) to `debug` or `trace` in `0.13.x`. There are ~120 `#[instrument]` annotations so it will take a while to review them all. I also need to make sure the more verbose logging level is preserved when used from the Trippy application.
Thanks for making this available as a library! It's incredibly useful.

Fix patch:
diff --git a/crates/trippy-core/src/flows.rs b/crates/trippy-core/src/flows.rs
index 1da1fbdc..d69dbbff 100644
--- a/crates/trippy-core/src/flows.rs
+++ b/crates/trippy-core/src/flows.rs
@@ -2,6 +2,7 @@ use derive_more::{Add, AddAssign, Sub, SubAssign};
 use itertools::{EitherOrBoth, Itertools};
 use std::fmt::{Debug, Display, Formatter};
 use std::net::IpAddr;
+use tracing::instrument;
 
 /// Identifies a tracing `Flow`.
 #[derive(
@@ -57,6 +58,7 @@ impl FlowRegistry {
     ///
     /// If a flow matches more than one existing flow then only the first
     /// matching flow will be updated.
+    #[instrument(skip(self), level = "trace")]
     pub fn register(&mut self, flow: Flow) -> FlowId {
         for (entry, id) in &mut self.flows {
             let status = entry.check(&flow);
@@ -123,6 +125,7 @@ impl Flow {
     ///
     /// This will also be the case if the flow being checked matches and is
     /// longer than the existing flow.
+    #[instrument(skip(self), level = "trace")]
     pub fn check(&self, flow: &Self) -> CheckStatus {
         let mut additions = 0;
         for (old, new) in self.entries.iter().zip(&flow.entries) {
@@ -142,6 +145,7 @@ impl Flow {
     }
 
     /// Marge the entries from the given `Flow` into our `Flow`.
+    #[instrument(skip(self), level = "trace")]
     fn merge(&mut self, flow: &Self) {
         self.entries = self
             .entries
diff --git a/crates/trippy-core/src/net/channel.rs b/crates/trippy-core/src/net/channel.rs
index 9578f3a3..028799ab 100644
--- a/crates/trippy-core/src/net/channel.rs
+++ b/crates/trippy-core/src/net/channel.rs
@@ -36,7 +36,7 @@ impl<S: Socket> Channel<S> {
     /// Create an `IcmpChannel`.
     ///
     /// This operation requires the `CAP_NET_RAW` capability on Linux.
-    #[instrument(skip_all)]
+    #[instrument(skip_all, level = "trace")]
     pub fn connect(config: &ChannelConfig) -> Result<Self> {
         tracing::debug!(?config);
         if usize::from(config.packet_size.0) > MAX_PACKET_SIZE {
@@ -88,15 +88,16 @@ impl<S: Socket> Channel<S> {
 }
 
 impl<S: Socket> Network for Channel<S> {
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn send_probe(&mut self, probe: Probe) -> Result<()> {
+        tracing::debug!(?probe);
         match self.protocol {
             Protocol::Icmp => self.dispatch_icmp_probe(probe),
             Protocol::Udp => self.dispatch_udp_probe(probe),
             Protocol::Tcp => self.dispatch_tcp_probe(probe),
         }
     }
-    #[instrument(skip_all)]
+    #[instrument(skip_all, level = "trace")]
     fn recv_probe(&mut self) -> Result<Option<Response>> {
         let prob_response = match self.protocol {
             Protocol::Icmp | Protocol::Udp => self.recv_icmp_probe(),
@@ -114,7 +115,7 @@ impl<S: Socket> Network for Channel<S> {
 
 impl<S: Socket> Channel<S> {
     /// Dispatch a ICMP probe.
-    #[instrument(skip_all)]
+    #[instrument(skip_all, level = "trace")]
     fn dispatch_icmp_probe(&mut self, probe: Probe) -> Result<()> {
         match (&self.family_config, self.send_socket.as_mut()) {
             (FamilyConfig::V4(ipv4), Some(socket)) => ipv4.dispatch_icmp_probe(socket, probe),
@@ -124,7 +125,7 @@ impl<S: Socket> Channel<S> {
     }
 
     /// Dispatch a UDP probe.
-    #[instrument(skip_all)]
+    #[instrument(skip_all, level = "trace")]
     fn dispatch_udp_probe(&mut self, probe: Probe) -> Result<()> {
         match (&self.family_config, self.send_socket.as_mut()) {
             (FamilyConfig::V4(ipv4), Some(socket)) => ipv4.dispatch_udp_probe(socket, probe),
@@ -134,7 +135,7 @@ impl<S: Socket> Channel<S> {
     }
 
     /// Dispatch a TCP probe.
-    #[instrument(skip_all)]
+    #[instrument(skip_all, level = "trace")]
     fn dispatch_tcp_probe(&mut self, probe: Probe) -> Result<()> {
         let socket = match &self.family_config {
             FamilyConfig::V4(ipv4) => ipv4.dispatch_tcp_probe(&probe),
@@ -150,7 +151,7 @@ impl<S: Socket> Channel<S> {
     }
 
     /// Generate a `ProbeResponse` for the next available ICMP packet, if any
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn recv_icmp_probe(&mut self) -> Result<Option<Response>> {
         if self.recv_socket.is_readable(self.read_timeout)? {
             match &self.family_config {
@@ -166,7 +167,7 @@ impl<S: Socket> Channel<S> {
     /// refused.
     ///
     /// Any TCP socket which has not connected or failed after a timeout will be removed.
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn recv_tcp_sockets(&mut self) -> Result<Option<Response>> {
         self.tcp_probes
             .retain(|probe| probe.start.elapsed().unwrap_or_default() < self.tcp_connect_timeout);
@@ -217,7 +218,7 @@ impl<S: Socket> TcpProbe<S> {
 }
 
 /// Make a socket for sending raw `ICMP` packets.
-#[instrument]
+#[instrument(level = "trace")]
 fn make_icmp_send_socket<S: Socket>(addr: IpAddr, raw: bool) -> Result<S> {
     Ok(match addr {
         IpAddr::V4(_) => S::new_icmp_send_socket_ipv4(raw),
@@ -226,7 +227,7 @@ fn make_icmp_send_socket<S: Socket>(addr: IpAddr, raw: bool) -> Result<S> {
 }
 
 /// Make a socket for sending `UDP` packets.
-#[instrument]
+#[instrument(level = "trace")]
 fn make_udp_send_socket<S: Socket>(addr: IpAddr, raw: bool) -> Result<S> {
     Ok(match addr {
         IpAddr::V4(_) => S::new_udp_send_socket_ipv4(raw),
@@ -235,7 +236,7 @@ fn make_udp_send_socket<S: Socket>(addr: IpAddr, raw: bool) -> Result<S> {
 }
 
 /// Make a socket for receiving raw `ICMP` packets.
-#[instrument]
+#[instrument(level = "trace")]
 fn make_recv_socket<S: Socket>(addr: IpAddr, raw: bool) -> Result<S> {
     Ok(match addr {
         IpAddr::V4(ipv4addr) => S::new_recv_socket_ipv4(ipv4addr, raw),
diff --git a/crates/trippy-core/src/net/ipv4.rs b/crates/trippy-core/src/net/ipv4.rs
index 304c895a..512d1f97 100644
--- a/crates/trippy-core/src/net/ipv4.rs
+++ b/crates/trippy-core/src/net/ipv4.rs
@@ -82,7 +82,7 @@ impl Default for Ipv4 {
 
 impl Ipv4 {
     /// Dispatch an ICMP probe.
-    #[instrument(skip(self, icmp_send_socket, probe))]
+    #[instrument(skip(self, icmp_send_socket), level = "trace")]
     pub fn dispatch_icmp_probe<S: Socket>(
         &self,
         icmp_send_socket: &mut S,
@@ -118,7 +118,7 @@ impl Ipv4 {
     }
 
     /// Dispatch a UDP probe.
-    #[instrument(skip(self, raw_send_socket, probe))]
+    #[instrument(skip(self, raw_send_socket), level = "trace")]
     pub fn dispatch_udp_probe<S: Socket>(
         &self,
         raw_send_socket: &mut S,
@@ -142,7 +142,7 @@ impl Ipv4 {
     ///
     /// As `IP_HDRINCL` is set we must supply the IP and UDP headers which allows us to set custom
     /// values for certain fields such as the checksum as required by the Paris tracing strategy.
-    #[instrument(skip(self, raw_send_socket, probe))]
+    #[instrument(skip(self, raw_send_socket), level = "trace")]
     fn dispatch_udp_probe_raw<S: Socket>(
         &self,
         raw_send_socket: &mut S,
@@ -182,7 +182,7 @@ impl Ipv4 {
     }
 
     /// Dispatch a UDP probe using a new UDP datagram socket.
-    #[instrument(skip(self, probe))]
+    #[instrument(skip(self), level = "trace")]
     fn dispatch_udp_probe_non_raw<S: Socket>(&self, probe: Probe, payload: &[u8]) -> Result<()> {
         let local_addr = SocketAddr::new(IpAddr::V4(self.src_addr), probe.src_port.0);
         let remote_addr = SocketAddr::new(IpAddr::V4(self.dest_addr), probe.dest_port.0);
@@ -199,7 +199,7 @@ impl Ipv4 {
     }
 
     /// Dispatch a TCP probe.
-    #[instrument(skip(self, probe))]
+    #[instrument(skip(self), level = "trace")]
     pub fn dispatch_tcp_probe<S: Socket>(&self, probe: &Probe) -> Result<S> {
         let mut socket = S::new_stream_socket_ipv4()?;
         let local_addr = SocketAddr::new(IpAddr::V4(self.src_addr), probe.src_port.0);
@@ -222,7 +222,7 @@ impl Ipv4 {
     }
 
     /// Receive an ICMP probe response.
-    #[instrument(skip(self, recv_socket))]
+    #[instrument(skip(self, recv_socket), level = "trace")]
     pub fn recv_icmp_probe<S: Socket>(&self, recv_socket: &mut S) -> Result<Option<Response>> {
         let mut buf = [0_u8; MAX_PACKET_SIZE];
         match recv_socket.read(&mut buf) {
@@ -238,7 +238,7 @@ impl Ipv4 {
     }
 
     /// Receive a TCP probe response.
-    #[instrument(skip(self, tcp_socket))]
+    #[instrument(skip(self, tcp_socket), level = "trace")]
     pub fn recv_tcp_socket<S: Socket>(
         &self,
         tcp_socket: &mut S,
@@ -282,7 +282,7 @@ impl Ipv4 {
         Ok(None)
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn extract_probe_resp(&self, ipv4: &Ipv4Packet<'_>) -> Result<Option<Response>> {
         let recv = SystemTime::now();
         let src = IpAddr::V4(ipv4.get_source());
@@ -349,7 +349,7 @@ impl Ipv4 {
         })
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn extract_probe_resp_seq(&self, ipv4: &Ipv4Packet<'_>) -> Result<Option<ResponseSeq>> {
         Ok(match (self.protocol, ipv4.get_protocol()) {
             (Protocol::Icmp, IpProtocol::Icmp) => {
@@ -490,13 +490,13 @@ const fn udp_payload_size(packet_size: usize) -> usize {
     packet_size - udp_header_size - ip_header_size
 }
 
-#[instrument]
+#[instrument(level = "trace")]
 fn extract_echo_request<'a>(ipv4: &'a Ipv4Packet<'a>) -> Result<EchoRequestPacket<'a>> {
     Ok(EchoRequestPacket::new_view(ipv4.payload())?)
 }
 
 /// Get the src and dest ports from the original `UdpPacket` packet embedded in the payload.
-#[instrument]
+#[instrument(level = "trace")]
 fn extract_udp_packet(ipv4: &Ipv4Packet<'_>) -> Result<(u16, u16, u16, u16, u16)> {
     let nested = UdpPacket::new_view(ipv4.payload())?;
     Ok((
@@ -519,7 +519,7 @@ fn extract_udp_packet(ipv4: &Ipv4Packet<'_>) -> Result<(u16, u16, u16, u16, u16)
 ///
 /// We therefore have to detect this situation and ensure we provide buffer a large enough for a
 /// complete TCP packet header.
-#[instrument]
+#[instrument(level = "trace")]
 fn extract_tcp_packet(ipv4: &Ipv4Packet<'_>) -> Result<(u16, u16)> {
     let nested_tcp = ipv4.payload();
     if nested_tcp.len() < TcpPacket::minimum_packet_size() {
diff --git a/crates/trippy-core/src/net/ipv6.rs b/crates/trippy-core/src/net/ipv6.rs
index d2d86fc6..9f27debf 100644
--- a/crates/trippy-core/src/net/ipv6.rs
+++ b/crates/trippy-core/src/net/ipv6.rs
@@ -77,7 +77,7 @@ impl Default for Ipv6 {
 
 impl Ipv6 {
     /// Dispatch an ICMP probe.
-    #[instrument(skip(self, icmp_send_socket, probe))]
+    #[instrument(skip(self, icmp_send_socket), level = "trace")]
     pub fn dispatch_icmp_probe<S: Socket>(
         &self,
         icmp_send_socket: &mut S,
@@ -101,7 +101,7 @@ impl Ipv6 {
     }
 
     /// Dispatch a UDP probe.
-    #[instrument(skip(self, raw_send_socket, probe))]
+    #[instrument(skip(self, raw_send_socket), level = "trace")]
     pub fn dispatch_udp_probe<S: Socket>(
         &self,
         raw_send_socket: &mut S,
@@ -121,7 +121,7 @@ impl Ipv6 {
         }
     }
 
-    #[instrument(skip(self, udp_send_socket, probe))]
+    #[instrument(skip(self, udp_send_socket), level = "trace")]
     fn dispatch_udp_probe_raw<S: Socket>(
         &self,
         udp_send_socket: &mut S,
@@ -157,7 +157,7 @@ impl Ipv6 {
         Ok(())
     }
 
-    #[instrument(skip(self, probe))]
+    #[instrument(skip(self), level = "trace")]
     fn dispatch_udp_probe_non_raw<S: Socket>(&self, probe: Probe, payload: &[u8]) -> Result<()> {
         let local_addr = SocketAddr::new(IpAddr::V6(self.src_addr), probe.src_port.0);
         let remote_addr = SocketAddr::new(IpAddr::V6(self.dest_addr), probe.dest_port.0);
@@ -173,7 +173,7 @@ impl Ipv6 {
     }
 
     /// Dispatch a TCP probe.
-    #[instrument(skip(self, probe))]
+    #[instrument(skip(self), level = "trace")]
     pub fn dispatch_tcp_probe<S: Socket>(&self, probe: &Probe) -> Result<S> {
         let mut socket = S::new_stream_socket_ipv6()?;
         let local_addr = SocketAddr::new(IpAddr::V6(self.src_addr), probe.src_port.0);
@@ -193,7 +193,7 @@ impl Ipv6 {
     }
 
     /// Receive an ICMP probe.
-    #[instrument(skip(self, recv_socket))]
+    #[instrument(skip(self, recv_socket), level = "trace")]
     pub fn recv_icmp_probe<S: Socket>(&self, recv_socket: &mut S) -> Result<Option<Response>> {
         let mut buf = [0_u8; MAX_PACKET_SIZE];
         match recv_socket.recv_from(&mut buf) {
@@ -213,7 +213,7 @@ impl Ipv6 {
     }
 
     /// Receive a TCP probe.
-    #[instrument(skip(self, tcp_socket))]
+    #[instrument(skip(self, tcp_socket), level = "trace")]
     pub fn recv_tcp_socket<S: Socket>(
         &self,
         tcp_socket: &mut S,
diff --git a/crates/trippy-core/src/net/platform/unix.rs b/crates/trippy-core/src/net/platform/unix.rs
index 70eee69e..ff55f0de 100644
--- a/crates/trippy-core/src/net/platform/unix.rs
+++ b/crates/trippy-core/src/net/platform/unix.rs
@@ -44,7 +44,7 @@ mod address {
     }
 
     #[cfg(not(target_os = "linux"))]
-    #[instrument(ret)]
+    #[instrument(ret, level = "trace")]
     pub fn for_address(addr: IpAddr) -> Result<Ipv4ByteOrder> {
         let addr = match addr {
             IpAddr::V4(addr) => addr,
@@ -72,7 +72,7 @@ mod address {
     /// Note that this implementation will try to create an `IPPROTO_ICMP` socket and if that fails
     /// it will fall back to creating an `IPPROTO_RAW` socket.
     #[cfg(not(target_os = "linux"))]
-    #[instrument(ret)]
+    #[instrument(ret, level = "trace")]
     fn test_send_local_ip4_packet(src_addr: Ipv4Addr, total_length: u16) -> Result<()> {
         use socket2::Protocol;
         let mut icmp_buf = [0_u8; trippy_packet::icmpv4::IcmpPacket::minimum_packet_size()];
@@ -107,7 +107,7 @@ mod address {
         }
     }
 
-    #[instrument(ret)]
+    #[instrument(ret, level = "trace")]
     fn lookup_interface_addr_ipv4(name: &str) -> Result<IpAddr> {
         nix::ifaddrs::getifaddrs()
             .map_err(|_| Error::UnknownInterface(name.to_string()))?
@@ -122,7 +122,7 @@ mod address {
             .ok_or_else(|| Error::UnknownInterface(name.to_string()))
     }
 
-    #[instrument(ret)]
+    #[instrument(ret, level = "trace")]
     fn lookup_interface_addr_ipv6(name: &str) -> Result<IpAddr> {
         nix::ifaddrs::getifaddrs()
             .map_err(|_| Error::UnknownInterface(name.to_string()))?
@@ -138,7 +138,7 @@ mod address {
     }
 
     // Note that no packets are transmitted by this method.
-    #[instrument(ret)]
+    #[instrument(ret, level = "trace")]
     pub fn discover_local_addr(target_addr: IpAddr, port: u16) -> Result<IpAddr> {
         let mut socket = match target_addr {
             IpAddr::V4(_) => SocketImpl::new_udp_dgram_socket_ipv4(),
@@ -169,7 +169,7 @@ mod socket {
     use tracing::instrument;
 
     #[allow(clippy::unnecessary_wraps)]
-    #[instrument]
+    #[instrument(level = "trace")]
     pub fn startup() -> Result<()> {
         Ok(())
     }
@@ -231,7 +231,7 @@ mod socket {
     }
 
     impl Socket for SocketImpl {
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_icmp_send_socket_ipv4(raw: bool) -> IoResult<Self> {
             if raw {
                 let mut socket = Self::new_raw_ipv4(Protocol::from(nix::libc::IPPROTO_RAW))?;
@@ -245,7 +245,7 @@ mod socket {
                 Ok(socket)
             }
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_icmp_send_socket_ipv6(raw: bool) -> IoResult<Self> {
             if raw {
                 let socket = Self::new_raw_ipv6(Protocol::ICMPV6)?;
@@ -257,7 +257,7 @@ mod socket {
                 Ok(socket)
             }
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_udp_send_socket_ipv4(raw: bool) -> IoResult<Self> {
             if raw {
                 let mut socket = Self::new_raw_ipv4(Protocol::from(nix::libc::IPPROTO_RAW))?;
@@ -270,7 +270,7 @@ mod socket {
                 Ok(socket)
             }
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_udp_send_socket_ipv6(raw: bool) -> IoResult<Self> {
             if raw {
                 let socket = Self::new_raw_ipv6(Protocol::UDP)?;
@@ -282,7 +282,7 @@ mod socket {
                 Ok(socket)
             }
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_recv_socket_ipv4(_: Ipv4Addr, raw: bool) -> IoResult<Self> {
             if raw {
                 let mut socket = Self::new_raw_ipv4(Protocol::ICMPV4)?;
@@ -295,7 +295,7 @@ mod socket {
                 Ok(socket)
             }
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_recv_socket_ipv6(_: Ipv6Addr, raw: bool) -> IoResult<Self> {
             if raw {
                 let socket = Self::new_raw_ipv6(Protocol::ICMPV6)?;
@@ -307,80 +307,80 @@ mod socket {
                 Ok(socket)
             }
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_stream_socket_ipv4() -> IoResult<Self> {
             let mut socket = Self::new(Domain::IPV4, Type::STREAM, Protocol::TCP)?;
             socket.set_nonblocking(true)?;
             socket.set_reuse_port(true)?;
             Ok(socket)
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_stream_socket_ipv6() -> IoResult<Self> {
             let mut socket = Self::new(Domain::IPV6, Type::STREAM, Protocol::TCP)?;
             socket.set_nonblocking(true)?;
             socket.set_reuse_port(true)?;
             Ok(socket)
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_udp_dgram_socket_ipv4() -> IoResult<Self> {
             Self::new_dgram_ipv4(Protocol::UDP)
         }
-        #[instrument]
+        #[instrument(level = "trace")]
         fn new_udp_dgram_socket_ipv6() -> IoResult<Self> {
             Self::new_dgram_ipv6(Protocol::UDP)
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn bind(&mut self, address: SocketAddr) -> IoResult<()> {
             self.inner
                 .bind(&SockAddr::from(address))
                 .map_err(|err| IoError::Bind(err, address))
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn set_tos(&mut self, tos: u32) -> IoResult<()> {
             self.inner
                 .set_tos(tos)
                 .map_err(|err| IoError::Other(err, IoOperation::SetTos))
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn set_ttl(&mut self, ttl: u32) -> IoResult<()> {
             self.inner
                 .set_ttl(ttl)
                 .map_err(|err| IoError::Other(err, IoOperation::SetTtl))
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn set_reuse_port(&mut self, reuse: bool) -> IoResult<()> {
             self.inner
                 .set_reuse_port(reuse)
                 .map_err(|err| IoError::Other(err, IoOperation::SetReusePort))
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn set_header_included(&mut self, included: bool) -> IoResult<()> {
             self.inner
                 .set_header_included_v4(included)
                 .map_err(|err| IoError::Other(err, IoOperation::SetHeaderIncluded))
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn set_unicast_hops_v6(&mut self, hops: u8) -> IoResult<()> {
             self.inner
                 .set_unicast_hops_v6(u32::from(hops))
                 .map_err(|err| IoError::Other(err, IoOperation::SetUnicastHopsV6))
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn connect(&mut self, address: SocketAddr) -> IoResult<()> {
-            tracing::debug!(?address);
+            tracing::trace!(?address);
             self.inner
                 .connect(&SockAddr::from(address))
                 .map_err(|err| IoError::Connect(err, address))
         }
-        #[instrument(skip(self, buf))]
+        #[instrument(skip(self, buf), level = "trace")]
         fn send_to(&mut self, buf: &[u8], addr: SocketAddr) -> IoResult<()> {
-            tracing::debug!(buf = format!("{:02x?}", buf.iter().format(" ")), ?addr);
+            tracing::trace!(buf = format!("{:02x?}", buf.iter().format(" ")), ?addr);
             self.inner
                 .send_to(buf, &SockAddr::from(addr))
                 .map_err(|err| IoError::SendTo(err, addr))?;
             Ok(())
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn is_readable(&mut self, timeout: Duration) -> IoResult<bool> {
             let mut read = FdSet::new();
             read.insert(self.inner.as_fd());
@@ -400,7 +400,7 @@ mod socket {
                 )),
             }
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn is_writable(&mut self) -> IoResult<bool> {
             let mut write = FdSet::new();
             write.insert(self.inner.as_fd());
@@ -420,48 +420,48 @@ mod socket {
                 )),
             }
         }
-        #[instrument(skip(self, buf), ret)]
+        #[instrument(skip(self, buf), level = "trace")]
         fn recv_from(&mut self, buf: &mut [u8]) -> IoResult<(usize, Option<SocketAddr>)> {
             let (bytes_read, addr) = self
                 .inner
                 .recv_from_into_buf(buf)
                 .map_err(|err| IoError::Other(err, IoOperation::RecvFrom))?;
-            tracing::debug!(
+            tracing::trace!(
                 buf = format!("{:02x?}", buf[..bytes_read].iter().format(" ")),
                 bytes_read,
                 ?addr
             );
             Ok((bytes_read, addr))
         }
-        #[instrument(skip(self, buf), ret)]
+        #[instrument(skip(self, buf), level = "trace")]
         fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
             let bytes_read = self
                 .inner
                 .read(buf)
                 .map_err(|err| IoError::Other(err, IoOperation::Read))?;
-            tracing::debug!(
+            tracing::trace!(
                 buf = format!("{:02x?}", buf[..bytes_read].iter().format(" ")),
                 bytes_read
             );
             Ok(bytes_read)
         }
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn shutdown(&mut self) -> IoResult<()> {
             self.inner
                 .shutdown(Shutdown::Both)
                 .map_err(|err| IoError::Other(err, IoOperation::Shutdown))
         }
-        #[instrument(skip(self), ret)]
+        #[instrument(skip(self), level = "trace")]
         fn peer_addr(&mut self) -> IoResult<Option<SocketAddr>> {
             let addr = self
                 .inner
                 .peer_addr()
                 .map_err(|err| IoError::Other(err, IoOperation::PeerAddr))?
                 .as_socket();
-            tracing::debug!(?addr);
+            tracing::trace!(?addr);
             Ok(addr)
         }
-        #[instrument(skip(self), ret)]
+        #[instrument(skip(self), ret, level = "trace")]
         fn take_error(&mut self) -> IoResult<Option<SocketError>> {
             self.inner
                 .take_error()
@@ -476,12 +476,12 @@ mod socket {
                 .map_err(|err| IoError::Other(err, IoOperation::TakeError))
         }
         #[allow(clippy::unused_self, clippy::unnecessary_wraps)]
-        #[instrument(skip(self), ret)]
+        #[instrument(skip(self), ret, level = "trace")]
         fn icmp_error_info(&mut self) -> IoResult<IpAddr> {
             Ok(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
         }
         #[allow(clippy::unused_self, clippy::unnecessary_wraps)]
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         fn close(&mut self) -> IoResult<()> {
             Ok(())
         }
diff --git a/crates/trippy-core/src/net/platform/windows.rs b/crates/trippy-core/src/net/platform/windows.rs
index 0cc5a2c0..45cf64d6 100644
--- a/crates/trippy-core/src/net/platform/windows.rs
+++ b/crates/trippy-core/src/net/platform/windows.rs
@@ -82,7 +82,7 @@ impl Platform for PlatformImpl {
     }
 }
 
-#[instrument]
+#[instrument(level = "trace")]
 pub fn startup() -> Result<()> {
     SocketImpl::startup().map_err(Error::IoError)
 }
@@ -125,14 +125,14 @@ impl SocketImpl {
         })
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn create_event(&mut self) -> IoResult<()> {
         self.ol.hEvent = syscall!(WSACreateEvent(), |res| { res == 0 || res == -1 })
             .map_err(|err| IoError::Other(err, IoOperation::WSACreateEvent))?;
         Ok(())
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn wait_for_event(&self, timeout: Duration) -> IoResult<bool> {
         let millis = timeout.as_millis() as u32;
         let rc = syscall_threading!(WaitForSingleObject(self.ol.hEvent, millis));
@@ -147,14 +147,14 @@ impl SocketImpl {
         Ok(true)
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn reset_event(&self) -> IoResult<()> {
         syscall!(WSAResetEvent(self.ol.hEvent), |res| { res == 0 })
             .map_err(|err| IoError::Other(err, IoOperation::WSAResetEvent))
             .map(|_| ())
     }
 
-    #[instrument(skip(self, optval))]
+    #[instrument(skip(self, optval), level = "trace")]
     fn getsockopt<T>(&self, level: i32, optname: i32, mut optval: T) -> StdIoResult<T> {
         let mut optlen = size_of::<T>() as i32;
         syscall!(
@@ -170,7 +170,7 @@ impl SocketImpl {
         Ok(optval)
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn setsockopt_u32(&self, level: i32, optname: i32, optval: u32) -> StdIoResult<()> {
         let bytes = optval.to_ne_bytes();
         let optval = addr_of!(bytes).cast();
@@ -187,18 +187,18 @@ impl SocketImpl {
         .map(|_| ())
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn setsockopt_bool(&self, level: i32, optname: i32, optval: bool) -> StdIoResult<()> {
         self.setsockopt_u32(level, optname, u32::from(optval))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn set_fail_connect_on_icmp_error(&self, enabled: bool) -> IoResult<()> {
         self.setsockopt_bool(IPPROTO_TCP, TCP_FAIL_CONNECT_ON_ICMP_ERROR as _, enabled)
             .map_err(|err| IoError::Other(err, IoOperation::SetTcpFailConnectOnIcmpError))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn set_non_blocking(&self, is_non_blocking: bool) -> IoResult<()> {
         self.inner
             .set_nonblocking(is_non_blocking)
@@ -206,7 +206,7 @@ impl SocketImpl {
     }
 
     // TODO handle case where `WSARecvFrom` succeeded immediately.
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn post_recv_from(&mut self) -> IoResult<()> {
         fn is_err(res: i32) -> bool {
             res == SOCKET_ERROR
@@ -235,7 +235,7 @@ impl SocketImpl {
         Ok(())
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn get_overlapped_result(&mut self) -> IoResult<()> {
         let mut bytes_read = 0;
         let mut flags = 0;
@@ -292,7 +292,7 @@ impl Drop for SocketImpl {
 
 #[allow(clippy::cast_possible_wrap, clippy::redundant_closure_call)]
 impl Socket for SocketImpl {
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_icmp_send_socket_ipv4(raw: bool) -> IoResult<Self> {
         if raw {
             let mut sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::from(IPPROTO_RAW)))?;
@@ -304,7 +304,7 @@ impl Socket for SocketImpl {
         }
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_icmp_send_socket_ipv6(raw: bool) -> IoResult<Self> {
         if raw {
             let sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?;
@@ -315,7 +315,7 @@ impl Socket for SocketImpl {
         }
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_udp_send_socket_ipv4(raw: bool) -> IoResult<Self> {
         if raw {
             let mut sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::from(IPPROTO_RAW)))?;
@@ -327,7 +327,7 @@ impl Socket for SocketImpl {
         }
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_udp_send_socket_ipv6(raw: bool) -> IoResult<Self> {
         if raw {
             let sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::UDP))?;
@@ -338,7 +338,7 @@ impl Socket for SocketImpl {
         }
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_recv_socket_ipv4(src_addr: Ipv4Addr, raw: bool) -> IoResult<Self> {
         if raw {
             let mut sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?;
@@ -352,7 +352,7 @@ impl Socket for SocketImpl {
         }
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_recv_socket_ipv6(src_addr: Ipv6Addr, raw: bool) -> IoResult<Self> {
         if raw {
             let mut sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?;
@@ -365,7 +365,7 @@ impl Socket for SocketImpl {
         }
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_stream_socket_ipv4() -> IoResult<Self> {
         let mut sock = Self::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?;
         sock.set_non_blocking(true)?;
@@ -373,7 +373,7 @@ impl Socket for SocketImpl {
         Ok(sock)
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_stream_socket_ipv6() -> IoResult<Self> {
         let mut sock = Self::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?;
         sock.set_non_blocking(true)?;
@@ -381,17 +381,17 @@ impl Socket for SocketImpl {
         Ok(sock)
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_udp_dgram_socket_ipv4() -> IoResult<Self> {
         Self::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))
     }
 
-    #[instrument]
+    #[instrument(level = "trace")]
     fn new_udp_dgram_socket_ipv6() -> IoResult<Self> {
         Self::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn bind(&mut self, addr: SocketAddr) -> IoResult<()> {
         self.inner
             .bind(&SockAddr::from(addr))
@@ -407,21 +407,21 @@ impl Socket for SocketImpl {
         Ok(())
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn set_tos(&mut self, tos: u32) -> IoResult<()> {
         self.inner
             .set_tos(tos)
             .map_err(|err| IoError::Other(err, IoOperation::SetTos))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn set_ttl(&mut self, ttl: u32) -> IoResult<()> {
         self.inner
             .set_ttl(ttl)
             .map_err(|err| IoError::Other(err, IoOperation::SetTtl))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn set_reuse_port(&mut self, is_reuse_port: bool) -> IoResult<()> {
         self.setsockopt_bool(SOL_SOCKET as _, SO_REUSE_UNICASTPORT as _, is_reuse_port)
             .or_else(|_| {
@@ -430,21 +430,21 @@ impl Socket for SocketImpl {
             .map_err(|err| IoError::Other(err, IoOperation::SetReusePort))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn set_header_included(&mut self, is_header_included: bool) -> IoResult<()> {
         self.inner
             .set_header_included_v4(is_header_included)
             .map_err(|err| IoError::Other(err, IoOperation::SetHeaderIncluded))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn set_unicast_hops_v6(&mut self, max_hops: u8) -> IoResult<()> {
         self.inner
             .set_unicast_hops_v6(max_hops.into())
             .map_err(|err| IoError::Other(err, IoOperation::SetUnicastHopsV6))
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn connect(&mut self, addr: SocketAddr) -> IoResult<()> {
         self.set_fail_connect_on_icmp_error(true)?;
         syscall!(
@@ -464,16 +464,16 @@ impl Socket for SocketImpl {
         }
     }
 
-    #[instrument(skip(self, buf))]
+    #[instrument(skip(self, buf), level = "trace")]
     fn send_to(&mut self, buf: &[u8], addr: SocketAddr) -> IoResult<()> {
-        tracing::debug!(buf = format!("{:02x?}", buf.iter().format(" ")), ?addr);
+        tracing::trace!(buf = format!("{:02x?}", buf.iter().format(" ")), ?addr);
         self.inner
             .send_to(buf, &SockAddr::from(addr))
             .map_err(|err| IoError::SendTo(err, addr))?;
         Ok(())
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn is_readable(&mut self, timeout: Duration) -> IoResult<bool> {
         if !self.wait_for_event(timeout)? {
             return Ok(false);
@@ -488,7 +488,7 @@ impl Socket for SocketImpl {
         Ok(true)
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn is_writable(&mut self) -> IoResult<bool> {
         if !self.wait_for_event(Duration::ZERO)? {
             return Ok(false);
@@ -503,12 +503,12 @@ impl Socket for SocketImpl {
         Ok(true)
     }
 
-    #[instrument(skip(self, buf), ret)]
+    #[instrument(skip(self, buf), level = "trace")]
     fn recv_from(&mut self, buf: &mut [u8]) -> IoResult<(usize, Option<SocketAddr>)> {
         let addr = sockaddrptr_to_ipaddr(addr_of_mut!(*self.from))
             .map_err(|err| IoError::Other(err, IoOperation::RecvFrom))?;
         let len = self.read(buf)?;
-        tracing::debug!(
+        tracing::trace!(
             buf = format!("{:02x?}", buf[..len].iter().format(" ")),
             len,
             ?addr
@@ -516,23 +516,23 @@ impl Socket for SocketImpl {
         Ok((len, Some(SocketAddr::new(addr, 0))))
     }
 
-    #[instrument(skip(self, buf), ret)]
+    #[instrument(skip(self, buf), ret, level = "trace")]
     fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
         buf.copy_from_slice(self.buf.as_slice());
         let bytes_read = self.bytes_read as usize;
-        tracing::debug!(buf = format!("{:02x?}", buf[..bytes_read].iter().format(" ")));
+        tracing::trace!(buf = format!("{:02x?}", buf[..bytes_read].iter().format(" ")));
         self.post_recv_from()?;
         Ok(bytes_read)
     }
 
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn shutdown(&mut self) -> IoResult<()> {
         self.inner
             .shutdown(std::net::Shutdown::Both)
             .map_err(|err| IoError::Other(err, IoOperation::Shutdown))
     }
 
-    #[instrument(skip(self), ret)]
+    #[instrument(skip(self), ret, level = "trace")]
     fn peer_addr(&mut self) -> IoResult<Option<SocketAddr>> {
         Ok(self
             .inner
@@ -541,7 +541,7 @@ impl Socket for SocketImpl {
             .as_socket())
     }
 
-    #[instrument(skip(self), ret)]
+    #[instrument(skip(self), ret, level = "trace")]
     fn take_error(&mut self) -> IoResult<Option<SocketError>> {
         match self.getsockopt(SOL_SOCKET as _, SO_ERROR as _, 0) {
             Ok(0) => Ok(None),
@@ -555,7 +555,7 @@ impl Socket for SocketImpl {
         .map_err(|err| IoError::Other(err, IoOperation::TakeError))
     }
 
-    #[instrument(skip(self), ret)]
+    #[instrument(skip(self), ret, level = "trace")]
     #[allow(unsafe_code)]
     fn icmp_error_info(&mut self) -> IoResult<IpAddr> {
         let icmp_error_info = self
@@ -581,7 +581,7 @@ impl Socket for SocketImpl {
     }
 
     // Interestingly, `Socket2` sockets don't seem to call `closesocket` on drop??
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn close(&mut self) -> IoResult<()> {
         syscall!(closesocket(self.inner.as_raw_socket() as _), |res| res
             == SOCKET_ERROR)
@@ -627,7 +627,7 @@ impl From<ErrorKind> for StdIoError {
 /// is using a connectionless protocol, the address may not be available until I/O
 /// occurs on the socket."  We use `SIO_ROUTING_INTERFACE_QUERY` instead.
 #[allow(clippy::cast_sign_loss, clippy::redundant_closure_call)]
-#[instrument]
+#[instrument(level = "trace")]
 fn routing_interface_query(target: IpAddr) -> Result<IpAddr> {
     let src: *mut c_void = [0; 1024].as_mut_ptr().cast();
     let mut bytes = 0;
@@ -754,7 +754,7 @@ fn socketaddr_to_sockaddr(socketaddr: SocketAddr) -> (SOCKADDR_STORAGE, i32) {
     (unsafe { sockaddr.storage }, size_of::<SockAddr>() as i32)
 }
 
-#[instrument(skip(adapters), ret)]
+#[instrument(skip(adapters), ret, level = "trace")]
 fn lookup_interface_addr(adapters: &Adapters, name: &str) -> Result<IpAddr> {
     adapters
         .iter()
diff --git a/crates/trippy-core/src/state.rs b/crates/trippy-core/src/state.rs
index 6b296fd8..f27b4524 100644
--- a/crates/trippy-core/src/state.rs
+++ b/crates/trippy-core/src/state.rs
@@ -7,6 +7,7 @@ use std::collections::HashMap;
 use std::iter::once;
 use std::net::IpAddr;
 use std::time::Duration;
+use tracing::instrument;
 
 /// The state of a trace.
 #[derive(Debug, Clone, Default)]
@@ -128,6 +129,7 @@ impl State {
     }
 
     /// Update the tracing state from a `TracerRound`.
+    #[instrument(skip(self, round), level = "trace")]
     pub fn update_from_round(&mut self, round: &Round<'_>) {
         let flow = Flow::from_hops(
             round
@@ -148,6 +150,7 @@ impl State {
         }
     }
 
+    #[instrument(skip(self, round), level = "trace")]
     fn update_trace_flow(&mut self, flow_id: FlowId, round: &Round<'_>) {
         let flow_trace = self
             .state
@@ -530,6 +533,7 @@ mod state_updater {
     use crate::types::Checksum;
     use crate::{NatStatus, ProbeStatus, Round, TimeToLive};
     use std::time::Duration;
+    use tracing::instrument;
 
     /// Update the state of a `FlowState` from a `Round`.
     pub(super) struct StateUpdater<'a> {
@@ -552,6 +556,7 @@ mod state_updater {
             }
         }
 
+        #[instrument(skip(self), level = "trace")]
         pub(super) fn apply(&mut self) {
             self.state.round_count += 1;
             self.state.highest_ttl =
@@ -562,6 +567,7 @@ mod state_updater {
             }
         }
 
+        #[instrument(skip(self), level = "trace")]
         fn update_for_probe(&mut self, probe: &ProbeStatus) {
             let state = &mut *self.state;
             match probe {
diff --git a/crates/trippy-core/src/strategy.rs b/crates/trippy-core/src/strategy.rs
index e42d76df..c51df227 100644
--- a/crates/trippy-core/src/strategy.rs
+++ b/crates/trippy-core/src/strategy.rs
@@ -55,7 +55,7 @@ pub struct Strategy<F> {
 }
 
 impl<F: Fn(&Round<'_>)> Strategy<F> {
-    #[instrument(skip_all)]
+    #[instrument(skip_all, level = "trace")]
     pub fn new(config: &StrategyConfig, publish: F) -> Self {
         tracing::debug!(?config);
         Self {
@@ -65,7 +65,7 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     }
 
     /// Run a continuous trace and publish results.
-    #[instrument(skip(self, network))]
+    #[instrument(skip(self, network), level = "trace")]
     pub fn run<N: Network>(self, mut network: N) -> Result<()> {
         let mut state = TracerState::new(self.config);
         while !state.finished(self.config.max_rounds) {
@@ -87,7 +87,6 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     ///         round
     ///     otherwise:
     ///       - the number of unknown-in-flight probes is lower than the maximum allowed
-    #[instrument(skip(self, network, st))]
     fn send_request<N: Network>(&self, network: &mut N, st: &mut TracerState) -> Result<()> {
         let can_send_ttl = if let Some(target_ttl) = st.target_ttl() {
             st.ttl() <= target_ttl
@@ -134,6 +133,7 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     ///
     /// Some errors are transient and should not be considered fatal.  In these cases we mark the
     /// probe as failed and continue.
+    #[instrument(skip(network, st), level = "trace")]
     fn do_send<N: Network>(network: &mut N, st: &mut TracerState, probe: Probe) -> Result<()> {
         match network.send_probe(probe) {
             Ok(()) => Ok(()),
@@ -164,7 +164,6 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     /// corresponding original `EchoRequest`.  Note that this may not be the greatest
     /// time-to-live that was sent in the round as the algorithm will send `EchoRequest` with
     /// larger time-to-live values before the `EchoReply` is received.
-    #[instrument(skip(self, network, st))]
     fn recv_response<N: Network>(&self, network: &mut N, st: &mut TracerState) -> Result<()> {
         let next = network.recv_probe()?;
         if let Some(resp) = next {
@@ -187,7 +186,6 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     /// 3 - either:
     ///     A - the target has been found OR
     ///     B - the target has not been found and the round has exceeded the maximum round duration
-    #[instrument(skip(self, st))]
     fn update_round(&self, st: &mut TracerState) {
         let now = SystemTime::now();
         let round_duration = now.duration_since(st.round_start()).unwrap_or_default();
@@ -205,7 +203,7 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     ///
     /// If the round completed without receiving an `EchoReply` from the target host then we also
     /// publish the next `ProbeStatus` which is assumed to represent the TTL of the target host.
-    #[instrument(skip(self, state))]
+    #[instrument(skip(self, state), level = "trace")]
     fn publish_trace(&self, state: &TracerState) {
         let max_received_ttl = if let Some(target_ttl) = state.target_ttl() {
             target_ttl
@@ -230,7 +228,7 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     /// Check if the `TraceId` matches the expected value for this tracer.
     ///
     /// A special value of `0` is accepted for `udp` and `tcp` which do not have an identifier.
-    #[instrument(skip(self))]
+    #[instrument(skip(self), level = "trace")]
     fn check_trace_id(&self, trace_id: TraceId) -> bool {
         self.config.trace_identifier == trace_id || trace_id == TraceId(0)
     }
@@ -247,6 +245,7 @@ impl<F: Fn(&Round<'_>)> Strategy<F> {
     /// dest address match the expected values.
     ///
     /// For ICMP probe responses no additional checks are required.
+    #[instrument(skip(self), level = "trace")]
     fn validate(&self, resp: &ResponseData) -> bool {
         const fn validate_ports(
             port_direction: PortDirection,
@@ -977,7 +976,7 @@ mod state {
         ///
         /// We post-increment `ttl` here and so in practice we only allow `ttl` values in the range
         /// `1..254` to allow us to use a `u8`.
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         pub fn next_probe(&mut self, sent: SystemTime) -> Probe {
             let (src_port, dest_port, identifier, flags) = self.probe_data();
             let probe = Probe::new(
@@ -1008,7 +1007,7 @@ mod state {
         /// then afterward:
         /// - The `ProbeStatus` at sequence `3` will be set to `Skipped` state
         /// - A new `ProbeStatus` will be created at sequence `4` with a `ttl` of `5`
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         pub fn reissue_probe(&mut self, sent: SystemTime) -> Probe {
             let probe_index = usize::from(self.sequence - self.round_sequence);
             self.buffer[probe_index - 1] = ProbeStatus::Skipped;
@@ -1030,7 +1029,7 @@ mod state {
         }
 
         /// Mark the `ProbeStatus` at the current `sequence` as failed.
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         pub fn fail_probe(&mut self) {
             let probe_index = usize::from(self.sequence - self.round_sequence);
             let probe = self.buffer[probe_index - 1].clone();
@@ -1160,7 +1159,7 @@ mod state {
         /// overwriting the state with stale values.  We may also receive multiple replies
         /// from the target host with differing time-to-live values and so must ensure we
         /// use the time-to-live with the lowest sequence number.
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         pub fn complete_probe(&mut self, resp: StrategyResponse) {
             // Retrieve and update the `ProbeStatus` at `sequence`.
             let probe = self.probe_at(resp.sequence);
@@ -1226,7 +1225,7 @@ mod state {
         /// If, during the round which just completed, we went above the max sequence number then we
         /// reset it here. We do this here to avoid having to deal with the sequence number
         /// wrapping during a round, which is more problematic.
-        #[instrument(skip(self))]
+        #[instrument(skip(self), level = "trace")]
         pub fn advance_round(&mut self, first_ttl: TimeToLive) {
             if self.sequence >= self.max_sequence() {
                 self.sequence = self.config.initial_sequence;
diff --git a/crates/trippy-core/src/tracer.rs b/crates/trippy-core/src/tracer.rs
index 2e40f313..cc0d1483 100644
--- a/crates/trippy-core/src/tracer.rs
+++ b/crates/trippy-core/src/tracer.rs
@@ -528,13 +528,13 @@ mod inner {
             }
         }
 
-        #[instrument(skip_all)]
+        #[instrument(skip_all, level = "trace")]
         pub(super) fn run(&self) -> Result<()> {
             self.run_internal(|_| ())
                 .map_err(|err| self.handle_error(err))
         }
 
-        #[instrument(skip_all)]
+        #[instrument(skip_all, level = "trace")]
         pub(super) fn run_with<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
             self.run_internal(func)
                 .map_err(|err| self.handle_error(err))
@@ -645,7 +645,7 @@ mod inner {
             self.max_round_duration
         }
 
-        #[instrument(skip_all)]
+        #[instrument(skip_all, level = "trace")]
         fn run_internal<F: Fn(&Round<'_>)>(&self, func: F) -> Result<()> {
             // if we are given a source address, validate it otherwise
             // discover it based on the target address and interface.
diff --git a/crates/trippy-tui/src/app.rs b/crates/trippy-tui/src/app.rs
index 315f9a11..7c01ba63 100644
--- a/crates/trippy-tui/src/app.rs
+++ b/crates/trippy-tui/src/app.rs
@@ -5,6 +5,7 @@ use crate::locale;
 use crate::{frontend, report};
 use anyhow::{anyhow, Error};
 use std::net::IpAddr;
+use tracing::instrument;
 use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
 use tracing_subscriber::fmt::format::FmtSpan;
 use tracing_subscriber::layer::SubscriberExt;
@@ -17,6 +18,7 @@ use trippy_privilege::Privilege;
 pub fn run_trippy(cfg: &TrippyConfig, pid: u16) -> anyhow::Result<()> {
     let locale = locale::set_locale(cfg.tui_locale.as_deref());
     let _guard = configure_logging(cfg);
+    tracing::debug!(?cfg);
     let resolver = start_dns_resolver(cfg)?;
     let geoip_lookup = create_geoip_lookup(cfg, &locale)?;
     let addrs = resolve_targets(cfg, &resolver)?;
@@ -33,6 +35,7 @@ pub fn run_trippy(cfg: &TrippyConfig, pid: u16) -> anyhow::Result<()> {
 }
 
 /// Start all tracers.
+#[instrument(skip(cfg), level = "trace")]
 fn start_tracers(
     cfg: &TrippyConfig,
     addrs: &[TargetInfo],
@@ -48,6 +51,7 @@ fn start_tracers(
 }
 
 /// Start a tracer to a given target.
+#[instrument(skip(cfg), level = "trace")]
 fn start_tracer(
     cfg: &TrippyConfig,
     target_host: &str,
@@ -85,6 +89,7 @@ fn start_tracer(
 }
 
 /// Run the TUI, stream or report.
+#[instrument(skip_all, level = "trace")]
 fn run_frontend(
     args: &TrippyConfig,
     locale: &str,
@@ -112,6 +117,7 @@ fn run_frontend(
 }
 
 /// Resolve targets.
+#[instrument(skip_all, level = "trace")]
 fn resolve_targets(cfg: &TrippyConfig, resolver: &DnsResolver) -> anyhow::Result<Vec<TargetInfo>> {
     cfg.targets
         .iter()
@@ -138,6 +144,7 @@ fn resolve_targets(cfg: &TrippyConfig, resolver: &DnsResolver) -> anyhow::Result
 }
 
 /// Start the DNS resolver.
+#[instrument(skip_all, level = "trace")]
 fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
     Ok(DnsResolver::start(trippy_dns::Config::new(
         cfg.dns_resolve_method,
@@ -147,6 +154,7 @@ fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
     ))?)
 }
 
+#[instrument(skip_all, level = "trace")]
 fn create_geoip_lookup(cfg: &TrippyConfig, locale: &str) -> anyhow::Result<GeoIpLookup> {
     if let Some(path) = cfg.geoip_mmdb_file.as_ref() {
         GeoIpLookup::from_file(path, String::from(locale))


Test patch: