HTTP client logging in Go

· 229 words · 2 minute read

So much of what we develop these days ends up using HTTP. Usually when developing an HTTP service it’s quite easy to add logging to aid debugging. But when developing a client, especially one that uses a library which does the actual HTTP calls, it can be harder to insert logging in all the right places.

The GODEBUG environment variable 🔗

GODEBUG is a useful, but somewhat under-documented way of debugging Go programs. HTTP debugging in particular uses the following variables:

GODEBUG=http2server=1 # server logging
GODEBUG=http2client=2 # client verbose logging
GODEBUG=http2debug=1 # log both

Add logging code 🔗

Even with GODEBUG I got no logs from the oauth2 package. I found a solution for injecting my own logging code:

type LoggingRoundTripper struct {
	Wrapped      http.RoundTripper
	DumpRequest  bool
	DumpResponse bool
}

func (lrt LoggingRoundTripper) RoundTrip(r *http.Request) (*http.Response,
	error,
) {
	dump, err := httputil.DumpRequest(r, lrt.DumpRequest)
	if err != nil {
		slog.Error("error", slog.Any("err", err))

		return nil, fmt.Errorf("failed to dump request: %w", err)
	}

	fmt.Println(string(dump))

	res, err := lrt.Wrapped.RoundTrip(r)
	if err != nil {
		slog.Error("error", slog.Any("err", err))

		return nil, fmt.Errorf("failed HTTP request: %w", err)
	}

	dump, err = httputil.DumpResponse(res, lrt.DumpResponse)
	if err != nil {
		slog.Error("error", slog.Any("err", err))

		return nil, fmt.Errorf("failed dumping response: %w", err)
	}

	fmt.Println(string(dump))

	return res, nil
}

// To use with oauth2
ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{Transport:
        LoggingRoundTripper{
                Wrapped: http.DefaultTransport,
                DumpRequest: true,
                DumpResponse: true,
        }})
conf := &oauth2.Config{...}
conf.Exchange(ctx, ...)