Documentation
¶
Overview ¶
Package recorder records the full life cycle of net/http client calls — including calls that fail at the DNS, TCP, TLS or HTTP layer — and exports them as standard HAR 1.2 documents enriched with "_"-prefixed extension fields ("_error", "_network", "_tls", "_requestBody", "_responseBody", "_trace", ...). Stripping every extension field leaves a valid plain HAR 1.2 document.
The guiding principle: record what was actually observable during the call, as accurately and structurally as possible, without ever changing the behavior the caller sees. Values that cannot be measured reliably (wire header sizes, compressed body sizes after transparent gzip decoding, timing phases that did not happen) are reported as -1 or omitted — never invented.
Usage:
rec := recorder.NewMemoryRecorder()
client := &http.Client{
Transport: recorder.NewTransport(http.DefaultTransport, rec),
}
resp, err := client.Get("https://example.com/")
// ... consume resp.Body; the entry is finalized on EOF/Close ...
_ = rec.WriteHAR(os.Stdout)
A response entry is not complete when RoundTrip returns: the body has not been read yet. The entry reaches the Recorder when the body hits EOF, is closed early, or fails — or immediately, when RoundTrip itself returns an error. A response body that is neither fully read nor closed (a caller bug) never finalizes; the library deliberately uses no finalizers.
Index ¶
- Constants
- func DefaultRedactedHeaders() []string
- func DeflateDecoder(r io.Reader) (io.ReadCloser, error)
- func GzipDecoder(r io.Reader) (io.ReadCloser, error)
- func TraceContext(ctx context.Context) (context.Context, string)
- func TraceIDFromContext(ctx context.Context) (string, bool)
- func WithTraceID(ctx context.Context, id string) context.Context
- type BodyInfo
- type BodyMetadata
- type BodyStore
- type BodyWriter
- type Cache
- type CertInfo
- type Content
- type ContentDecoder
- type Cookie
- type Creator
- type Entry
- type ErrorInfo
- type Expect100Info
- type FileBodyStore
- type HAR
- type HARFileRecorder
- func (r *HARFileRecorder) Close() error
- func (r *HARFileRecorder) EntriesByTrace(traceID string) []*Entry
- func (r *HARFileRecorder) Flush() error
- func (r *HARFileRecorder) Record(e *Entry)
- func (r *HARFileRecorder) RemoveTrace(traceID string) int
- func (r *HARFileRecorder) TakeTrace(traceID string) []*Entry
- type InformationalResponse
- type InternalErrorMode
- type JSONStreamRecorder
- type Log
- type MemoryBodyStore
- type MemoryRecorder
- func (r *MemoryRecorder) Entries() []*Entry
- func (r *MemoryRecorder) EntriesByTrace(traceID string) []*Entry
- func (r *MemoryRecorder) HAR() *HAR
- func (r *MemoryRecorder) HARForTrace(traceID string) *HAR
- func (r *MemoryRecorder) Len() int
- func (r *MemoryRecorder) Record(e *Entry)
- func (r *MemoryRecorder) RemoveTrace(traceID string) int
- func (r *MemoryRecorder) Reset()
- func (r *MemoryRecorder) TakeTrace(traceID string) []*Entry
- func (r *MemoryRecorder) WriteHAR(w io.Writer) error
- type NameValuePair
- type NetworkInfo
- type OnEntryCompleted
- type Option
- func WithBodyStore(s BodyStore) Option
- func WithCaptureCertificates(v, raw bool) Option
- func WithCaptureCookies(v bool) Option
- func WithCaptureHeaders(v bool) Option
- func WithCaptureRawTrace(v bool) Option
- func WithCaptureRequestBody(v bool) Option
- func WithCaptureResponseBody(v bool) Option
- func WithCaptureTLS(v bool) Option
- func WithContentDecoder(encoding string, dec ContentDecoder) Option
- func WithEmbedBodies(v bool) Option
- func WithErrorRedactor(fn func(string) string) Option
- func WithHashBodies(enabled bool, algorithm string) Option
- func WithInternalErrorMode(m InternalErrorMode) Option
- func WithLogf(fn func(format string, args ...any)) Option
- func WithMaxRequestBodyBytes(n int64) Option
- func WithMaxResponseBodyBytes(n int64) Option
- func WithOnEntryCompleted(fn OnEntryCompleted) Option
- func WithOnInternalError(fn func(error)) Option
- func WithOptions(o Options) Option
- func WithRedactCookies(names ...string) Option
- func WithRedactHeaders(names ...string) Option
- func WithRedactJSONFields(names ...string) Option
- func WithRedactQueryParameters(names ...string) Option
- func WithRedactXMLElements(names ...string) Option
- type Options
- type PostData
- type PostParam
- type PutIdleInfo
- type Recorder
- type RecorderFunc
- type Request
- type Response
- type TLSInfo
- type Timings
- type TraceEvent
- type TraceStore
- type Transport
Constants ¶
const ( PhaseRequestSetup = "request_setup" PhaseDNS = "dns" PhaseConnect = "connect" PhaseProxy = "proxy" PhaseTLS = "tls" PhaseWriteRequest = "write_request" PhaseWriteRequestBody = "write_request_body" PhaseWaitResponse = "wait_response" PhaseReadResponseHeaders = "read_response_headers" PhaseReadResponseBody = "read_response_body" PhaseRedirect = "redirect" PhaseContext = "context" PhaseUnknown = "unknown" )
Error phases. The phase names the step of the HTTP exchange in which the failure was observed.
const ( StateCreated = "created" StateRequestStarted = "request_started" StateRequestHeadersWritten = "request_headers_written" StateRequestBodyStreaming = "request_body_streaming" StateResponseHeadersReceived = "response_headers_received" StateResponseBodyStreaming = "response_body_streaming" StateCompleted = "completed" StateFailed = "failed" StateClosedEarly = "closed_early" )
Exchange life cycle states, recorded under "_state". Only terminal states (completed, failed, closed_early) ever appear in exported entries, because entries are emitted exclusively at finalization.
Variables ¶
This section is empty.
Functions ¶
func DefaultRedactedHeaders ¶
func DefaultRedactedHeaders() []string
DefaultRedactedHeaders returns the headers redacted by default.
func DeflateDecoder ¶
func DeflateDecoder(r io.Reader) (io.ReadCloser, error)
DeflateDecoder decodes "deflate" content using the standard library. Registered by default. HTTP's deflate is formally zlib-wrapped (RFC 9110), but a number of servers send raw DEFLATE streams; the decoder sniffs the zlib header and handles both, like browsers do.
func GzipDecoder ¶
func GzipDecoder(r io.Reader) (io.ReadCloser, error)
GzipDecoder decodes gzip content using the standard library. Registered by default for "gzip" and "x-gzip".
func TraceContext ¶
TraceContext is a convenience wrapper around WithTraceID that generates a fresh random trace ID and returns it alongside the derived context.
func TraceIDFromContext ¶
TraceIDFromContext returns the correlation ID installed by WithTraceID.
func WithTraceID ¶
WithTraceID returns a context carrying the given correlation ID. Every exchange started under this context records the ID as "_traceId" and an incrementing "_redirectIndex", which links redirect chains followed by http.Client into one logical trace.
Types ¶
type BodyInfo ¶
type BodyInfo struct {
Present bool `json:"present"`
Complete bool `json:"complete"`
ClosedEarly bool `json:"closedEarly,omitempty"`
Truncated bool `json:"truncated,omitempty"`
// CapturedBytes counts bytes stored for the HAR document.
CapturedBytes int64 `json:"capturedBytes"`
// TotalBytes counts every byte that actually flowed through the stream,
// including bytes past the capture limit.
TotalBytes int64 `json:"totalBytes"`
// Hash covers TotalBytes worth of data and is only emitted when the
// stream completed (a partial-stream hash would be misleading).
Hash string `json:"hash,omitempty"`
HashAlgorithm string `json:"hashAlgorithm,omitempty"`
ReadError string `json:"readError,omitempty"`
CloseError string `json:"closeError,omitempty"`
// Store is an external reference (e.g. a file path) when the BodyStore
// keeps content out of memory.
Store string `json:"store,omitempty"`
}
BodyInfo is the "_requestBody" / "_responseBody" extension describing what happened to a body stream.
type BodyMetadata ¶
type BodyMetadata struct {
ExchangeID string
Direction string // "request" or "response"
ContentType string
// SizeHint is the expected number of bytes this writer will receive
// (derived from Content-Length, already clamped to the capture limit),
// or 0 when unknown. Stores may use it to pre-allocate, but must treat
// it as untrusted: the actual stream may be shorter or longer, and a
// hostile peer can announce an absurd Content-Length.
SizeHint int64
}
BodyMetadata describes the body stream a BodyWriter is created for.
type BodyStore ¶
type BodyStore interface {
NewWriter(ctx context.Context, metadata BodyMetadata) (BodyWriter, error)
}
BodyStore creates BodyWriter instances. Implementations must be safe for concurrent use.
type BodyWriter ¶
type BodyWriter interface {
io.Writer
Close() error
// Bytes returns the bytes captured so far (used when embedding content
// into the HAR document).
Bytes() ([]byte, error)
// Ref returns an external reference to the stored content (e.g. a file
// path), or "" when the content lives inline in memory.
Ref() string
}
BodyWriter receives captured body bytes for one stream. Implementations must be safe for concurrent use of Write with Bytes.
type Cache ¶
type Cache struct{}
Cache is the HAR cache record. This library performs no caching, so the object is intentionally empty (allowed by HAR 1.2).
type CertInfo ¶
type CertInfo struct {
Subject string `json:"subject"`
Issuer string `json:"issuer"`
SerialNumber string `json:"serialNumber"`
DNSNames []string `json:"dnsNames,omitempty"`
IPAddresses []string `json:"ipAddresses,omitempty"`
NotBefore string `json:"notBefore"`
NotAfter string `json:"notAfter"`
PublicKeyAlgorithm string `json:"publicKeyAlgorithm"`
SignatureAlgorithm string `json:"signatureAlgorithm"`
SHA256Fingerprint string `json:"sha256Fingerprint"`
RawDER string `json:"rawDER,omitempty"`
}
CertInfo describes one peer certificate.
type Content ¶
type Content struct {
Size int64 `json:"size"`
Compression int64 `json:"compression,omitempty"`
MimeType string `json:"mimeType"`
Text string `json:"text,omitempty"`
Encoding string `json:"encoding,omitempty"`
Comment string `json:"comment,omitempty"`
Decoded bool `json:"_decoded,omitempty"`
}
Content is the HAR content record. Size is the decoded content length when the decoded form is known (transparent gzip by http.Transport, or a configured ContentDecoder), otherwise the bytes the caller actually read. "_decoded" marks that Text/Size describe the decoded form rather than the raw wire bytes.
type ContentDecoder ¶
type ContentDecoder func(io.Reader) (io.ReadCloser, error)
ContentDecoder turns a compressed body stream into its decoded form. It is used only while building the HAR record: when a captured response body carries a Content-Encoding with a registered decoder, content.text/size are stored in decoded form (marked "_decoded": true) while bodySize, the body hash and the "_responseBody" counters keep describing the real wire bytes. The bytes handed to the caller are never touched.
Decoders for encodings outside the standard library (brotli, zstd) are deliberately not bundled — the module stays dependency-free. Registering one is a few lines with the de-facto standard implementations:
import "github.com/andybalholm/brotli"
recorder.WithContentDecoder("br", func(r io.Reader) (io.ReadCloser, error) {
return io.NopCloser(brotli.NewReader(r)), nil
})
import "github.com/klauspost/compress/zstd"
recorder.WithContentDecoder("zstd", func(r io.Reader) (io.ReadCloser, error) {
zr, err := zstd.NewReader(r)
if err != nil {
return nil, err
}
return zr.IOReadCloser(), nil
})
type Cookie ¶
type Cookie struct {
Name string `json:"name"`
Value string `json:"value"`
Path string `json:"path,omitempty"`
Domain string `json:"domain,omitempty"`
Expires string `json:"expires,omitempty"`
HTTPOnly bool `json:"httpOnly,omitempty"`
Secure bool `json:"secure,omitempty"`
}
Cookie is the HAR cookie record.
type Entry ¶
type Entry struct {
StartedDateTime string `json:"startedDateTime"`
Time float64 `json:"time"`
Request *Request `json:"request"`
Response *Response `json:"response"`
Cache *Cache `json:"cache"`
Timings *Timings `json:"timings"`
ServerIPAddress string `json:"serverIPAddress,omitempty"`
Connection string `json:"connection,omitempty"`
Comment string `json:"comment,omitempty"`
// Extensions.
TraceID string `json:"_traceId,omitempty"`
ExchangeID string `json:"_exchangeId,omitempty"`
RedirectIndex *int `json:"_redirectIndex,omitempty"`
State string `json:"_state,omitempty"`
Error *ErrorInfo `json:"_error,omitempty"`
Network *NetworkInfo `json:"_network,omitempty"`
TLS *TLSInfo `json:"_tls,omitempty"`
Expect100 *Expect100Info `json:"_expect100,omitempty"`
Informational []InformationalResponse `json:"_informational,omitempty"`
RequestBody *BodyInfo `json:"_requestBody,omitempty"`
ResponseBody *BodyInfo `json:"_responseBody,omitempty"`
RequestTrailers []NameValuePair `json:"_requestTrailers,omitempty"`
ResponseTrailers []NameValuePair `json:"_responseTrailers,omitempty"`
RequestTransferEncoding []string `json:"_requestTransferEncoding,omitempty"`
ResponseTransferEncoding []string `json:"_responseTransferEncoding,omitempty"`
RawTrace []TraceEvent `json:"_trace,omitempty"`
// contains filtered or unexported fields
}
Entry is a single HAR entry: one physical HTTP exchange. Fields whose JSON name starts with "_" are application extensions per HAR 1.2; removing every "_" field leaves a valid plain HAR 1.2 entry.
Entries produced by Transport are immutable snapshots: neither the Transport nor the built-in recorders mutate an Entry after it has been handed to Recorder.Record.
type ErrorInfo ¶
type ErrorInfo struct {
Phase string `json:"phase"`
Type string `json:"type"`
Message string `json:"message"`
Timeout bool `json:"timeout"`
Temporary bool `json:"temporary"`
ContextCanceled bool `json:"contextCanceled"`
ContextDeadlineExceeded bool `json:"contextDeadlineExceeded"`
Cause string `json:"cause,omitempty"`
UnwrapChain []string `json:"unwrapChain,omitempty"`
}
ErrorInfo is the structured "_error" extension describing a transport or body-stream failure.
type Expect100Info ¶
type Expect100Info struct {
// Waited reports that the transport paused before sending the body.
Waited bool `json:"waited"`
// ContinueReceived reports that the server sent 100 Continue.
ContinueReceived bool `json:"continueReceived"`
// WaitMS is the pause between waiting and the 100 arriving; omitted when
// either side of the interval was not observed.
WaitMS float64 `json:"waitMs,omitempty"`
}
Expect100Info describes an "Expect: 100-continue" handshake observed on this exchange.
type FileBodyStore ¶
type FileBodyStore struct {
// Dir is the directory temp files are created in; empty means the system
// temp directory.
Dir string
}
FileBodyStore spools captured bodies to temporary files so large bodies do not have to live in memory. Files are NOT deleted automatically; their paths are exposed via BodyInfo.Store and cleanup is the caller's responsibility.
func (FileBodyStore) NewWriter ¶
func (s FileBodyStore) NewWriter(_ context.Context, meta BodyMetadata) (BodyWriter, error)
NewWriter implements BodyStore.
type HAR ¶
type HAR struct {
Log *Log `json:"log"`
}
HAR is the top-level HAR 1.2 document.
type HARFileRecorder ¶
type HARFileRecorder struct {
// contains filtered or unexported fields
}
HARFileRecorder buffers finalized entries and writes a complete HAR document to a file on Flush (atomically, via a temp file + rename). It is safe for concurrent use.
func NewHARFileRecorder ¶
func NewHARFileRecorder(path string) *HARFileRecorder
NewHARFileRecorder creates a recorder that will write to path on Flush.
func (*HARFileRecorder) Close ¶
func (r *HARFileRecorder) Close() error
Close flushes the document; it satisfies io.Closer for defer-friendly use.
func (*HARFileRecorder) EntriesByTrace ¶
func (r *HARFileRecorder) EntriesByTrace(traceID string) []*Entry
EntriesByTrace implements TraceStore: entries are buffered until Flush, so they remain queryable by trace ID.
func (*HARFileRecorder) Flush ¶
func (r *HARFileRecorder) Flush() error
Flush writes the full HAR document collected so far. It can be called any number of times; each call rewrites the file atomically.
func (*HARFileRecorder) Record ¶
func (r *HARFileRecorder) Record(e *Entry)
Record implements Recorder.
func (*HARFileRecorder) RemoveTrace ¶
func (r *HARFileRecorder) RemoveTrace(traceID string) int
RemoveTrace implements TraceStore. Removed entries are excluded from every subsequent Flush.
func (*HARFileRecorder) TakeTrace ¶
func (r *HARFileRecorder) TakeTrace(traceID string) []*Entry
TakeTrace implements TraceStore: it atomically removes and returns the trace's entries. The taken entries are excluded from every subsequent Flush — use this to split one shared recording into per-call HAR files (recorder.NewHAR(taken).Write(...)).
type InformationalResponse ¶
type InformationalResponse struct {
Status int `json:"status"`
Headers []NameValuePair `json:"headers,omitempty"`
}
InformationalResponse is one 1xx interim response (100 Continue, 103 Early Hints, ...) observed before the final response. Headers are redacted with the same rules as final response headers.
type InternalErrorMode ¶
type InternalErrorMode int
InternalErrorMode controls how recorder-internal failures (body store errors, recorder panics) are reported. The wrapped HTTP call is never retried or altered by an internal error.
const ( // InternalErrorIgnore drops internal errors after reporting them through // Options.OnInternalError (when set). The HTTP call is never affected. // This is the default. InternalErrorIgnore InternalErrorMode = iota // InternalErrorLog behaves like InternalErrorIgnore but additionally // writes the error using Options.Logf (or the standard log package when // Logf is nil). InternalErrorLog )
type JSONStreamRecorder ¶
type JSONStreamRecorder struct {
// contains filtered or unexported fields
}
JSONStreamRecorder writes every finalized entry immediately as one JSON document per line (NDJSON) to the underlying writer. This is the streaming export path: it deliberately does not emit the enclosing HAR wrapper, so the top-level HAR JSON structure is never left half-written; consumers can wrap the lines into a log object themselves. Safe for concurrent use.
JSONStreamRecorder intentionally does not implement TraceStore: entries leave the process the moment they are recorded, so there is nothing left to query or remove. Group downstream by each line's "_traceId" field, or use a retaining recorder (MemoryRecorder, HARFileRecorder) when per-trace access is needed.
func NewJSONStreamRecorder ¶
func NewJSONStreamRecorder(w io.Writer) *JSONStreamRecorder
NewJSONStreamRecorder creates a streaming recorder writing to w.
func (*JSONStreamRecorder) Err ¶
func (r *JSONStreamRecorder) Err() error
Err returns the first encoding error encountered, if any.
func (*JSONStreamRecorder) Record ¶
func (r *JSONStreamRecorder) Record(e *Entry)
Record implements Recorder. Encoding errors are retained and observable via Err; recording never panics.
type Log ¶
type Log struct {
Version string `json:"version"`
Creator *Creator `json:"creator"`
Entries []*Entry `json:"entries"`
Comment string `json:"comment,omitempty"`
}
Log is the HAR 1.2 log object.
type MemoryBodyStore ¶
type MemoryBodyStore struct{}
MemoryBodyStore keeps captured bodies in memory. It is the default store.
func (MemoryBodyStore) NewWriter ¶
func (MemoryBodyStore) NewWriter(_ context.Context, meta BodyMetadata) (BodyWriter, error)
NewWriter implements BodyStore. A positive SizeHint pre-sizes the buffer (bounded by maxPreallocBytes) so growth re-copies are avoided for bodies with a truthful Content-Length; a wrong hint costs at most one bounded allocation and never breaks the capture.
type MemoryRecorder ¶
type MemoryRecorder struct {
// contains filtered or unexported fields
}
MemoryRecorder collects finalized entries in memory. It is safe for concurrent use. Entries handed to it are immutable snapshots, so the copies returned by Entries can be shared freely.
func NewMemoryRecorder ¶
func NewMemoryRecorder() *MemoryRecorder
NewMemoryRecorder returns an empty in-memory recorder.
func (*MemoryRecorder) Entries ¶
func (r *MemoryRecorder) Entries() []*Entry
Entries returns a copy of the recorded entries in recording order.
func (*MemoryRecorder) EntriesByTrace ¶
func (r *MemoryRecorder) EntriesByTrace(traceID string) []*Entry
EntriesByTrace returns a copy of the entries belonging to the given trace ID (see WithTraceID), in recording order. The recorder keeps the entries.
func (*MemoryRecorder) HAR ¶
func (r *MemoryRecorder) HAR() *HAR
HAR builds a HAR 1.2 document from the entries recorded so far, ordered by request start time. In-flight exchanges are absent by definition: entries only reach the recorder once finalized.
func (*MemoryRecorder) HARForTrace ¶
func (r *MemoryRecorder) HARForTrace(traceID string) *HAR
HARForTrace builds a HAR 1.2 document containing only the entries of the given trace ID (the recorder keeps them; combine with TakeTrace + NewHAR to export-and-drop in one step).
func (*MemoryRecorder) Len ¶
func (r *MemoryRecorder) Len() int
Len returns the number of recorded entries.
func (*MemoryRecorder) Record ¶
func (r *MemoryRecorder) Record(e *Entry)
Record implements Recorder.
func (*MemoryRecorder) RemoveTrace ¶
func (r *MemoryRecorder) RemoveTrace(traceID string) int
RemoveTrace deletes every entry belonging to the given trace ID and returns how many were removed.
func (*MemoryRecorder) Reset ¶
func (r *MemoryRecorder) Reset()
Reset discards all recorded entries.
func (*MemoryRecorder) TakeTrace ¶
func (r *MemoryRecorder) TakeTrace(traceID string) []*Entry
TakeTrace atomically removes and returns the entries belonging to the given trace ID, in recording order. Atomicity matters on a shared client: an entry finalized concurrently can never be lost between a query and a separate delete.
type NameValuePair ¶
type NameValuePair struct {
Name string `json:"name"`
Value string `json:"value"`
Comment string `json:"comment,omitempty"`
}
NameValuePair is the HAR record for headers and query parameters.
type NetworkInfo ¶
type NetworkInfo struct {
DNSAddresses []string `json:"dnsAddresses,omitempty"`
// DNSCoalesced marks that this exchange's DNS answer was shared with a
// concurrent lookup for the same host (singleflight) rather than issued
// on its own.
DNSCoalesced bool `json:"dnsCoalesced,omitempty"`
Network string `json:"network,omitempty"`
LocalAddress string `json:"localAddress,omitempty"`
RemoteAddress string `json:"remoteAddress,omitempty"`
IPVersion string `json:"ipVersion,omitempty"`
ConnectionReused bool `json:"connectionReused"`
WasIdle bool `json:"wasIdle"`
IdleTimeMS float64 `json:"idleTimeMs,omitempty"`
// Proxy is the redacted proxy URL selected by a standard http.Transport.
// Custom RoundTrippers may only expose the dialed host:port.
Proxy string `json:"proxy,omitempty"`
HTTP2 bool `json:"http2"`
// PutIdle reports whether the connection went back to the idle pool
// after this exchange. Best effort: the pool return can race with entry
// finalization, so absence means "not observed", not "did not happen".
PutIdle *PutIdleInfo `json:"putIdle,omitempty"`
}
NetworkInfo is the "_network" extension: connection-level facts observed through httptrace. These describe the physical connection this exchange used — when a proxy is in play (Proxy != ""), RemoteAddress, IPVersion and DNSAddresses therefore refer to the proxy, not the origin server: the origin is never observable client-side through a proxy. HTTP2 is true only when HTTP/2 was positively observed.
type OnEntryCompleted ¶
OnEntryCompleted is invoked with the request context and the finished HAR entry every time an exchange is finalized.
type Option ¶
type Option func(*Options)
Option mutates Options during NewTransport.
func WithBodyStore ¶
WithBodyStore sets the storage backend for captured body bytes.
func WithCaptureCertificates ¶
WithCaptureCertificates toggles certificate detail capture; raw controls embedding of raw DER bytes.
func WithCaptureCookies ¶
WithCaptureCookies toggles cookie capture.
func WithCaptureHeaders ¶
WithCaptureHeaders toggles header capture.
func WithCaptureRawTrace ¶
WithCaptureRawTrace toggles the "_trace" raw event extension. Details are sanitized by RedactErrorMessage before export.
func WithCaptureRequestBody ¶
WithCaptureRequestBody toggles request body content capture.
func WithCaptureResponseBody ¶
WithCaptureResponseBody toggles response body content capture.
func WithCaptureTLS ¶
WithCaptureTLS toggles the "_tls" extension.
func WithContentDecoder ¶
func WithContentDecoder(encoding string, dec ContentDecoder) Option
WithContentDecoder registers a decoder for a Content-Encoding token (case-insensitive), e.g. "br" or "zstd". See ContentDecoder for recipes.
func WithEmbedBodies ¶
WithEmbedBodies toggles embedding captured body content into the HAR document. See Options.EmbedBodies.
func WithErrorRedactor ¶
WithErrorRedactor sets the error message redaction function.
func WithHashBodies ¶
WithHashBodies configures body hashing.
func WithInternalErrorMode ¶
func WithInternalErrorMode(m InternalErrorMode) Option
WithInternalErrorMode sets the internal error policy.
func WithMaxRequestBodyBytes ¶
WithMaxRequestBodyBytes sets the request body capture limit (<= 0: unlimited).
func WithMaxResponseBodyBytes ¶
WithMaxResponseBodyBytes sets the response body capture limit (<= 0: unlimited).
func WithOnEntryCompleted ¶
func WithOnEntryCompleted(fn OnEntryCompleted) Option
WithOnEntryCompleted sets the per-entry completion callback.
func WithOnInternalError ¶
WithOnInternalError sets the internal error callback.
func WithOptions ¶
WithOptions replaces the whole Options value. Apply it first when combining with other Option values.
func WithRedactCookies ¶
WithRedactCookies appends cookie names to the redaction list.
func WithRedactHeaders ¶
WithRedactHeaders appends header names to the redaction list.
func WithRedactJSONFields ¶
WithRedactJSONFields appends JSON field names to the redaction list.
func WithRedactQueryParameters ¶
WithRedactQueryParameters appends query parameter names to the redaction list.
func WithRedactXMLElements ¶
WithRedactXMLElements appends XML element local names to the redaction list (SOAP bodies included).
type Options ¶
type Options struct {
// CaptureRequestBody enables storing request body content. Body size and
// completion state are always tracked, even when this is false.
CaptureRequestBody bool
// CaptureResponseBody enables storing response body content.
CaptureResponseBody bool
// EmbedBodies controls whether captured body content is embedded into
// the HAR document (postData.text / content.text). When false, bodies
// are still captured into the BodyStore and the record keeps sizes,
// hashes, truncation state and the store reference
// ("_requestBody"/"_responseBody".store) — the intended production
// setting together with FileBodyStore, keeping HAR documents small
// while body content stays retrievable. Like the Capture* flags, the
// zero value disables embedding; WithEmbedBodies enables it explicitly.
EmbedBodies bool
// MaxRequestBodyBytes limits how many request body bytes are stored.
// Values <= 0 mean unlimited. The total size keeps being counted after
// the limit is reached; only content capture stops.
MaxRequestBodyBytes int64
// MaxResponseBodyBytes is the response-side equivalent of
// MaxRequestBodyBytes.
MaxResponseBodyBytes int64
// CaptureTLS enables the "_tls" extension built from tls.ConnectionState.
CaptureTLS bool
// CaptureCertificates includes peer certificate details in "_tls".
CaptureCertificates bool
// CaptureRawCertificates additionally embeds each certificate's raw DER
// bytes as Base64. Off by default because of size.
CaptureRawCertificates bool
// CaptureHeaders enables recording request/response headers and trailers.
CaptureHeaders bool
// CaptureCookies enables recording parsed cookies.
CaptureCookies bool
// RedactHeaders lists header names (case-insensitive) whose values are
// replaced with "[REDACTED]". See DefaultRedactedHeaders.
RedactHeaders []string
// RedactQueryParameters lists query parameter names (case-insensitive)
// to redact, both in request.url and in request.queryString. The same
// list is applied to application/x-www-form-urlencoded body params.
RedactQueryParameters []string
// RedactCookies lists cookie names (case-insensitive) to redact. A cookie
// is also redacted when its carrying header (Cookie / Set-Cookie) is in
// RedactHeaders.
RedactCookies []string
// RedactJSONFields lists JSON object field names (case-insensitive) whose
// values are replaced recursively in captured JSON bodies. Only applied
// to fully captured (complete, non-truncated) bodies.
RedactJSONFields []string
// RedactXMLElements lists XML element local names (case-insensitive,
// namespace prefixes ignored) whose text content is replaced in captured
// XML/SOAP bodies — e.g. "Password" covers <wsse:Password> in a
// WS-Security UsernameToken. The matched element's whole subtree is
// redacted; the rest of the document is preserved byte-for-byte.
// Attribute values are not redacted. Only applied to fully captured
// (complete, non-truncated) bodies.
RedactXMLElements []string
// HashBodies enables hashing of body streams. The hash covers every byte
// that actually flowed, including bytes beyond the capture limit.
HashBodies bool
// BodyHashAlgorithm selects the hash: "sha256" (default), "sha1" or
// "md5". Unknown values fall back to sha256.
BodyHashAlgorithm string
// CaptureRawTrace stores every raw httptrace event under "_trace". Event
// details pass through RedactErrorMessage before export.
CaptureRawTrace bool
// ContentDecoders maps Content-Encoding tokens (lower-case) to decoders
// used when building the HAR record, so compressed captured bodies can
// be stored in readable, decoded form. DefaultOptions installs the
// stdlib-only set (gzip, x-gzip, deflate); WithContentDecoder registers
// additional ones such as brotli or zstd — see ContentDecoder for
// ready-to-paste recipes. Decoding never touches the bytes the caller
// reads and never runs on partial or truncated captures.
ContentDecoders map[string]ContentDecoder
// BodyStore provides storage for captured body bytes. Nil means
// MemoryBodyStore.
BodyStore BodyStore
// InternalErrorMode selects the internal error policy. See the constants.
InternalErrorMode InternalErrorMode
// OnInternalError, when set, receives every recorder-internal error.
OnInternalError func(error)
// Logf is used by InternalErrorLog. Nil falls back to log.Printf.
Logf func(format string, args ...any)
// OnEntryCompleted, when set, is invoked with the request context and the
// finished entry every time an exchange is finalized (after
// Transport.Recorder.Record).
OnEntryCompleted OnEntryCompleted
// RedactErrorMessage, when set, is applied to every error message and raw
// trace detail before export (they can contain URLs or credentials).
RedactErrorMessage func(string) string
}
Options configures a Transport. The zero value disables all capturing; use DefaultOptions (applied automatically by NewTransport) for sensible production defaults.
Options must not be mutated after the Transport has served its first request.
func DefaultOptions ¶
func DefaultOptions() Options
DefaultOptions returns the production-safe options NewTransport starts from. Body streams are counted for lifecycle and size metadata, but their content is neither stored, embedded nor hashed by default. TLS metadata, headers and cookies remain enabled, with DefaultRedactedHeaders applied.
type PostData ¶
type PostData struct {
MimeType string `json:"mimeType"`
Params []PostParam `json:"params,omitempty"`
Text string `json:"text,omitempty"`
Encoding string `json:"_encoding,omitempty"`
Comment string `json:"comment,omitempty"`
}
PostData is the HAR postData record. "_encoding" is an extension marking Base64-encoded binary request bodies (plain HAR has no encoding field on postData).
type PostParam ¶
type PostParam struct {
Name string `json:"name"`
Value string `json:"value,omitempty"`
FileName string `json:"fileName,omitempty"`
ContentType string `json:"contentType,omitempty"`
}
PostParam is a single posted form parameter.
type PutIdleInfo ¶
PutIdleInfo is the structured outcome of the connection's return to the keep-alive pool.
type Recorder ¶
type Recorder interface {
Record(entry *Entry)
}
Recorder receives finalized, immutable entries. Implementations must be safe for concurrent use: entries arrive from whichever goroutine finishes reading a response body.
type RecorderFunc ¶
type RecorderFunc func(*Entry)
RecorderFunc adapts a function into a Recorder (the "callback recorder"). The function must be safe for concurrent use.
type Request ¶
type Request struct {
Method string `json:"method"`
URL string `json:"url"`
HTTPVersion string `json:"httpVersion"`
Cookies []Cookie `json:"cookies"`
Headers []NameValuePair `json:"headers"`
QueryString []NameValuePair `json:"queryString"`
PostData *PostData `json:"postData,omitempty"`
// HeadersSize is -1: the exact serialized header bytes written to the
// wire (including headers added internally by http.Transport) are not
// observable at the RoundTripper layer.
HeadersSize int64 `json:"headersSize"`
BodySize int64 `json:"bodySize"`
Comment string `json:"comment,omitempty"`
}
Request is the HAR request record.
type Response ¶
type Response struct {
Status int `json:"status"`
StatusText string `json:"statusText"`
HTTPVersion string `json:"httpVersion"`
Cookies []Cookie `json:"cookies"`
Headers []NameValuePair `json:"headers"`
Content *Content `json:"content"`
RedirectURL string `json:"redirectURL"`
// HeadersSize is -1: wire-level header size is not observable here.
HeadersSize int64 `json:"headersSize"`
// BodySize is the payload size received on the wire, or -1 when unknown
// (body not fully read, or transparently decompressed by http.Transport
// so the compressed wire size is no longer observable).
BodySize int64 `json:"bodySize"`
Comment string `json:"comment,omitempty"`
}
Response is the HAR response record. For exchanges that failed before an HTTP response existed, Status is 0 and "_error" on the entry carries the failure detail.
type TLSInfo ¶
type TLSInfo struct {
Version string `json:"version"`
CipherSuite string `json:"cipherSuite"`
NegotiatedProtocol string `json:"negotiatedProtocol,omitempty"`
ServerName string `json:"serverName,omitempty"`
HandshakeComplete bool `json:"handshakeComplete"`
DidResume bool `json:"didResume"`
OCSPStapled bool `json:"ocspStapled"`
SCTCount int `json:"sctCount"`
VerifiedChains int `json:"verifiedChains"`
PeerCertificates []CertInfo `json:"peerCertificates,omitempty"`
}
TLSInfo is the "_tls" extension built from tls.ConnectionState.
type Timings ¶
type Timings struct {
Blocked float64 `json:"blocked"`
DNS float64 `json:"dns"`
Connect float64 `json:"connect"`
Send float64 `json:"send"`
Wait float64 `json:"wait"`
Receive float64 `json:"receive"`
SSL float64 `json:"ssl"`
Comment string `json:"comment,omitempty"`
}
Timings is the HAR timings record. All values are milliseconds; -1 means the phase did not apply or could not be measured (per HAR 1.2).
type TraceEvent ¶
type TraceEvent struct {
Name string `json:"name"`
Time string `json:"time"`
Detail string `json:"detail,omitempty"`
}
TraceEvent is one raw httptrace event, stored under the "_trace" extension when Options.CaptureRawTrace is enabled.
type TraceStore ¶
type TraceStore interface {
// EntriesByTrace returns the entries belonging to the trace ID, in
// recording order, without removing them.
EntriesByTrace(traceID string) []*Entry
// RemoveTrace deletes the trace's entries and returns how many were
// removed.
RemoveTrace(traceID string) int
// TakeTrace atomically removes and returns the trace's entries, in
// recording order.
TakeTrace(traceID string) []*Entry
}
TraceStore is an optional capability for recorders that retain entries and can query or remove them by trace ID (see WithTraceID). The Recorder interface itself stays minimal — a custom recorder is still just one method — and callers discover the capability by type assertion:
if ts, ok := rec.(TraceStore); ok {
entries := ts.TakeTrace(id)
}
MemoryRecorder and HARFileRecorder implement TraceStore. JSONStreamRecorder cannot: its entries leave the process the moment they are recorded, so there is nothing left to query or remove.
type Transport ¶
type Transport struct {
// Base is the wrapped RoundTripper; nil means http.DefaultTransport.
Base http.RoundTripper
// Recorder receives finalized entries; nil disables recording (the
// OnEntryCompleted callback still fires).
Recorder Recorder
// Options configures capturing and redaction.
Options Options
// contains filtered or unexported fields
}
Transport is an http.RoundTripper that records every exchange passing through it. It wraps a base RoundTripper (http.DefaultTransport when Base is nil) and never alters the request, response, or error the caller sees.
Transport is safe for concurrent use by multiple goroutines provided its fields are not mutated after the first request. Prefer NewTransport, which also applies DefaultOptions; a zero-value literal works but captures nothing until Options are set. When a standard *http.Transport has a Proxy callback, NewTransport clones it once so the selected proxy URL can be observed without invoking that callback twice; configure the base before passing it in and close idle connections through this Transport or its http.Client.
func NewTransport ¶
func NewTransport(base http.RoundTripper, rec Recorder, opts ...Option) *Transport
NewTransport builds a Transport wrapping base. rec may be nil, in which case a fresh MemoryRecorder is installed (accessible via the Recorder field). Options start from DefaultOptions and are adjusted by opts.
func (*Transport) CloseIdleConnections ¶
func (t *Transport) CloseIdleConnections()
CloseIdleConnections forwards to the wrapped transport when it supports it, keeping http.Client.CloseIdleConnections working through the wrapper.
