client

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package client provides an *http.Client with structured logging and tracing built in.

Every exchange is logged through xlog and wrapped in a span, so outgoing calls show up in traces alongside the operation that made them. What may be written to those logs is the caller's decision: see Sanitizer.

The zero-configuration client is not redaction-safe. NewClient() with no options logs URLs and headers verbatim, so any service whose requests carry credentials should pass WithSanitizer.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewClient

func NewClient(opts ...Option) *http.Client

NewClient returns an *http.Client that logs and traces every exchange.

The returned client is an ordinary *http.Client: it can be handed to any library that accepts one, and it is safe for concurrent use.

Types

type NoopSanitizer

type NoopSanitizer struct{}

NoopSanitizer returns everything it is given, unchanged.

It is the default, which means a client built without WithSanitizer logs URLs, headers and bodies verbatim — Authorization headers, cookies, session tokens and any credential carried in a URL path or query included. That is a deliberate choice to leave redaction policy entirely to the caller, but it makes WithSanitizer the first option to reach for in any service whose requests carry credentials.

func NewNoopSanitizer

func NewNoopSanitizer() *NoopSanitizer

NewNoopSanitizer returns a Sanitizer that redacts nothing.

func (*NoopSanitizer) SanitizeBody

func (s *NoopSanitizer) SanitizeBody(b []byte) []byte

SanitizeBody returns b unchanged.

func (*NoopSanitizer) SanitizeHeaders

func (s *NoopSanitizer) SanitizeHeaders(h http.Header) http.Header

SanitizeHeaders returns h unchanged.

func (*NoopSanitizer) SanitizeURL

func (s *NoopSanitizer) SanitizeURL(u string) string

SanitizeURL returns u unchanged.

type Option

type Option func(*http.Client)

Option configures the client returned by NewClient.

func WithBodyLogging

func WithBodyLogging() Option

WithBodyLogging enables request and response body dumps at debug level, truncated to maxLoggedBodyBytes.

This is a local debugging tool and is deliberately awkward to reach for. Bodies are the one thing a sanitizer struggles to make safe: their shape is arbitrary, so a secret inside one is unrecognizable. Enabling this in code that ships means every payload the service exchanges lands in the log.

Consider forbidding it outside tests with a lint rule, e.g. golangci-lint's forbidigo.

func WithCallerAfterDo

func WithCallerAfterDo(f func(context.Context, *http.Response)) Option

WithCallerAfterDo registers a hook run after a successful round-trip. It is not called when the round-trip fails.

A hook that reads resp.Body must restore it, or the caller will read an empty body.

func WithCallerBeforeDo

func WithCallerBeforeDo(f func(context.Context, *http.Request)) Option

WithCallerBeforeDo registers a hook run just before the request goes out. Use it to attach credentials or correlation headers:

WithCallerBeforeDo(func(_ context.Context, r *http.Request) {
    r.Header.Set("Authorization", "Bearer "+token)
})

Hooks run before the request is logged, so anything they add is visible to the sanitizer — and therefore actually subject to redaction.

func WithCustomRedirectFlow

func WithCustomRedirectFlow(redirectFlow func(*http.Request, []*http.Request) error) Option

WithCustomRedirectFlow installs a custom http.Client.CheckRedirect policy.

func WithSanitizer

func WithSanitizer(s Sanitizer) Option

WithSanitizer installs the redaction policy used for every logged URL, header set and body. Without it the client uses NoopSanitizer and logs everything verbatim, secrets included.

A nil sanitizer is ignored rather than installed: a client that logs raw credentials because a constructor argument was nil is worse than one that keeps whatever policy it already had.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout overrides the total request timeout. A non-positive value is ignored so a caller can pass an unset config field without accidentally disabling the default ceiling.

func WithTransport

func WithTransport(transport *http.Transport) Option

WithTransport replaces the underlying *http.Transport, keeping the logging round-tripper that wraps it. A nil value is ignored.

Note that options which reach into the logging round-tripper — WithCallerBeforeDo, WithCallerAfterDo, WithSanitizer, WithBodyLogging — look up that wrapper on the client. Replacing the whole c.Transport from outside (rather than through this option) removes it, and those options then become silent no-ops.

func WithoutRedirect

func WithoutRedirect() Option

WithoutRedirect stops the client from following redirects, returning the redirect response itself instead.

type Sanitizer

type Sanitizer interface {
	// SanitizeURL renders a URL for logging. The input is the full URL string,
	// credentials and query included.
	SanitizeURL(u string) string
	// SanitizeHeaders renders a header set for logging.
	SanitizeHeaders(h http.Header) http.Header
	// SanitizeBody renders a body for logging. It is only called when body
	// logging is enabled with WithBodyLogging; the returned bytes are truncated
	// afterwards, so an implementation need not bound its own output.
	SanitizeBody(b []byte) []byte
}

Sanitizer decides what a request or response may look like in a log record.

The transport calls it on every URL, header set and body it is about to log, and uses whatever comes back. What counts as a secret is caller knowledge — one service treats X-Api-Key as a credential, another uses it as a routing hint — so the library provides the seam and the caller provides the policy.

Implementations MUST NOT mutate their argument. The header map and the body belong to a live request: http.Client re-enters RoundTrip with the same *http.Request on retries and redirects, so a mutating sanitizer corrupts the second attempt. Return a copy instead.

Implementations should also be cheap and total. They run on every logged exchange, and a panic inside one propagates out of RoundTrip — logging is a side effect, and it should never be the reason a request fails.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL