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 ¶
- func NewClient(opts ...Option) *http.Client
- type NoopSanitizer
- type Option
- func WithBodyLogging() Option
- func WithCallerAfterDo(f func(context.Context, *http.Response)) Option
- func WithCallerBeforeDo(f func(context.Context, *http.Request)) Option
- func WithCustomRedirectFlow(redirectFlow func(*http.Request, []*http.Request) error) Option
- func WithSanitizer(s Sanitizer) Option
- func WithTimeout(timeout time.Duration) Option
- func WithTransport(transport *http.Transport) Option
- func WithoutRedirect() Option
- type Sanitizer
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
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 ¶
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 ¶
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 ¶
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 ¶
WithCustomRedirectFlow installs a custom http.Client.CheckRedirect policy.
func WithSanitizer ¶
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 ¶
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 ¶
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.