DEV Community

Cover image for Go's httptrace: debugging HTTP request pipelines without leaving the standard library
Schiff Heimlich
Schiff Heimlich

Posted on

Go's httptrace: debugging HTTP request pipelines without leaving the standard library

httptrace is one of those packages that ships with Go that more people should know about. It's in net/http/httptrace and it gives you visibility into every phase of an HTTP request — DNS lookup, TCP connection, TLS handshake, and the actual request — without adding any external dependencies.

The setup

You attach a *httptrace.ClientTrace to a request context. Go calls the relevant hook as each phase completes. Here's a minimal example that just prints timestamps:

package main

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

var start time.Time

func trace() *httptrace.ClientTrace {
    return &httptrace.ClientTrace{
        DNSStart: func(info httptrace.DNSStartInfo) {
            fmt.Printf("DNS lookup started: %s\n", info.Host)
        },
        DNSDone: func(info httptrace.DNSDoneInfo) {
            fmt.Printf("DNS resolved: %v (duration: %s)\n", info.Addrs, time.Since(start))
        },
        ConnectStart: func(network, addr string) {
            fmt.Printf("Connecting to %s...\n", addr)
        },
        ConnectDone: func(network, addr string, err error) {
            if err != nil {
                fmt.Printf("Connection error: %v\n", err)
            } else {
                fmt.Printf("Connected to %s\n", addr)
            }
        },
        TLSHandshakeStart: func() {
            fmt.Printf("TLS handshake starting\n")
        },
        TLSHandshakeDone: func(state tls.ConnectionState, err error) {
            fmt.Printf("TLS handshake done, version: %x\n", state.Version)
        },
        WroteRequest: func(reqInfo httptrace.WroteRequestInfo) {
            if reqInfo.Err != nil {
                fmt.Printf("Request write error: %v\n", reqInfo.Err)
            }
        },
        GotConn: func(info httptrace.GotConnInfo) {
            if info.Reused {
                fmt.Printf("Connection reused (idle: %s)\n", time.Since(info.LastUsed))
            } else {
                fmt.Printf("New connection established\n")
            }
        },
    }
}

func main() {
    start = time.Now()
    req, _ := http.NewRequest("GET", "https://example.com", nil)
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace()))

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Printf("Request failed: %v\n", err)
        return
    }
    defer resp.Body.Close()
    fmt.Printf("Response status: %s (total time: %s)\n", resp.Status, time.Since(start))
}
Enter fullscreen mode Exit fullscreen mode

Where this actually helps

The most common use is diagnosing unexpected latency in an HTTP client. If your service calls an upstream API and responses are slower than expected, httptrace tells you whether the delay is in DNS, the TCP handshake, TLS negotiation, or something else.

A pattern I use: wrap httptrace in a small helper that collects timings into a struct and logs them if a request exceeds a threshold. Something like:

type requestTimings struct {
    DNS       time.Duration
    Connect   time.Duration
    TLS       time.Duration
    Total     time.Duration
}
Enter fullscreen mode Exit fullscreen mode

The hooks give you time.Time values for each event, so arithmetic is straightforward.

Connection reuse tracking

One underappreciated feature: GotConn fires when a connection is either reused or freshly created. You can tell whether your client is keeping connections alive or spinning up new ones for every request — which matters a lot for high-volume clients hitting the same host repeatedly.

One thing to watch

httptrace hooks fire synchronously on the goroutine managing the connection. Keep them fast — don't do I/O or acquire locks in a hook, or you'll distort your own timings.

That's it. No external packages, no magic. If you're debugging an HTTP client and want to know where time is going, httptrace is worth knowing about.

Top comments (0)