go 24 lines · 1 tab

HTTPtrace to debug DNS/connect/TLS timing in production-like runs

Leah Thompson Jan 2026
1 tab
package debughttp

import (
  "context"
  "crypto/tls"
  "net/http"
  "net/http/httptrace"
  "time"
)

func WithTrace(ctx context.Context, log func(string, time.Duration)) context.Context {
  start := time.Now()
  tr := &httptrace.ClientTrace{
    DNSDone: func(_ httptrace.DNSDoneInfo) { log("dns", time.Since(start)) },
    ConnectDone: func(_, _ string, _ error) { log("connect", time.Since(start)) },
    TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { log("tls", time.Since(start)) },
    GotConn: func(info httptrace.GotConnInfo) {
      if info.Reused {
        log("conn_reused", time.Since(start))
      }
    },
  }
  return httptrace.WithClientTrace(ctx, tr)
}
1 file · go Explain with highlit

When an outbound call is slow, it’s not always the server—it can be DNS, connection reuse, or TLS handshakes. net/http/httptrace lets you instrument a single request and see exactly where time is going. I attach a trace to the request context and record timestamps for events like DNSStart, ConnectStart, and TLSHandshakeDone. The key is that this is meant for targeted debugging, not always-on instrumentation; logs can get noisy fast. But when you’re chasing a latency regression or a weird network issue in staging, this is invaluable. I also like to print whether the connection was reused and which remote address was selected. Pair this with your standard request ID and you can correlate client-side phases with server-side logs.


Related snips

Share this code

Here's the card — post it anywhere.

HTTPtrace to debug DNS/connect/TLS timing in production-like runs — share card
Link copied