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 DecryptProtectedValue(token string, key ProtectionKey) ([]byte, error)
- func DefaultRedactedHeaders() []string
- func DeflateDecoder(r io.Reader) (io.ReadCloser, error)
- func GzipDecoder(r io.Reader) (io.ReadCloser, error)
- func RequestWithRedaction(req *http.Request, config RedactionConfig) *http.Request
- func TraceContext(ctx context.Context) (context.Context, string)
- func TraceIDFromContext(ctx context.Context) (string, bool)
- func VerifyProtectedToken(token string, value []byte, key ProtectionKey) (bool, error)
- func WithRequestRedaction(ctx context.Context, config RedactionConfig) context.Context
- func WithTraceID(ctx context.Context, id string) context.Context
- type BodyCaptureDecision
- type BodyCaptureMeta
- type BodyCapturePolicy
- type BodyCapturePolicyFunc
- type BodyDirection
- type BodyInfo
- type BodyMetadata
- type BodyRedactionInfo
- type BodyRedactor
- type BodyStore
- type BodyValue
- type BodyValueProtector
- 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 WithBodyCapturePolicy(policy BodyCapturePolicy) 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 WithRedaction(config RedactionConfig) Option
- func WithSensitiveValueProtection(config SensitiveValueProtection) Option
- type Options
- type PostData
- type PostParam
- type ProtectionCounts
- type ProtectionKey
- type ProtectionKeyProvider
- type ProtectionKeyProviderFunc
- type ProtectionMode
- type PutIdleInfo
- type Recorder
- type RecorderFunc
- type RedactionConfig
- type RedactionInfo
- type RedactionRules
- type RedactionScopeInfo
- type Request
- type Response
- type SensitiveValueProtection
- 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 ( BodyRedactionRedacted = "redacted" BodyRedactionUnchanged = "unchanged" BodyRedactionFailed = "failed" )
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 DecryptProtectedValue ¶ added in v0.2.0
func DecryptProtectedValue(token string, key ProtectionKey) ([]byte, error)
DecryptProtectedValue decrypts a REC-ENC-v1 token with the matching key. It is useful for trusted tooling; applications should avoid persisting the returned plaintext.
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 RequestWithRedaction ¶ added in v0.3.0
func RequestWithRedaction(req *http.Request, config RedactionConfig) *http.Request
RequestWithRedaction clones req with a context carrying additional redaction rules. It returns nil when req is nil and never mutates the supplied request.
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 VerifyProtectedToken ¶ added in v0.2.0
func VerifyProtectedToken(token string, value []byte, key ProtectionKey) (bool, error)
VerifyProtectedToken reports whether value produced a REC-TOK-v1 token.
func WithRequestRedaction ¶ added in v0.3.0
func WithRequestRedaction(ctx context.Context, config RedactionConfig) context.Context
WithRequestRedaction returns a context carrying additional redaction rules. Repeated calls merge rules; for duplicate body-redactor MIME registrations, the most recent call wins. Input slices and maps are copied before storage.
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 BodyCaptureDecision ¶ added in v0.2.0
type BodyCaptureDecision struct {
Capture bool
Embed bool
Hash bool
MaxBodyBytes int64
RedactorOverride BodyRedactor
}
BodyCaptureDecision controls capture for one request or response body. RedactorOverride, when non-nil, replaces the redactor selected by the Transport and request-scoped RedactionConfig for this body only. Nil keeps the existing selection; it does not disable redaction.
type BodyCaptureMeta ¶ added in v0.2.0
type BodyCaptureMeta struct {
Direction BodyDirection
Method string
Scheme string
Host string
Path string
StatusCode int
ContentType string
ContentEncoding string
ContentLength int64
TraceID string
RedirectIndex int
}
BodyCaptureMeta is the immutable, non-body metadata supplied to a BodyCapturePolicy. It excludes query values, headers, and cookies. StatusCode is zero for request decisions; Path is URL-escaped.
type BodyCapturePolicy ¶ added in v0.2.0
type BodyCapturePolicy interface {
DecideBodyCapture(context.Context, BodyCaptureMeta, BodyCaptureDecision) (BodyCaptureDecision, error)
}
BodyCapturePolicy decides how one body is recorded. The defaults argument reflects the transport's capture Options; RedactorOverride starts nil because RedactionConfig selection remains active unless explicitly overridden. Implementations may be called concurrently and must not retain or mutate HTTP objects.
type BodyCapturePolicyFunc ¶ added in v0.2.0
type BodyCapturePolicyFunc func(context.Context, BodyCaptureMeta, BodyCaptureDecision) (BodyCaptureDecision, error)
BodyCapturePolicyFunc adapts a function to BodyCapturePolicy.
func (BodyCapturePolicyFunc) DecideBodyCapture ¶ added in v0.2.0
func (f BodyCapturePolicyFunc) DecideBodyCapture(ctx context.Context, meta BodyCaptureMeta, defaults BodyCaptureDecision) (BodyCaptureDecision, error)
type BodyDirection ¶ added in v0.2.0
type BodyDirection string
BodyDirection identifies which side of an exchange a capture decision applies to.
const ( RequestBody BodyDirection = "request" ResponseBody BodyDirection = "response" )
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. The referenced bytes are the captured
// representation, which may already be decoded and/or redacted.
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 BodyRedactionInfo ¶ added in v0.2.0
type BodyRedactionInfo struct {
Kind string `json:"kind"`
Outcome string `json:"outcome"`
Replacements *int64 `json:"replacements,omitempty"`
Protection *ProtectionCounts `json:"protection,omitempty"`
}
BodyRedactionInfo reports which body redactor ran and its outcome.
type BodyRedactor ¶ added in v0.2.0
type BodyRedactor interface {
Redact(dst io.Writer, contentType string, protector BodyValueProtector) (io.WriteCloser, error)
}
BodyRedactor creates a streaming transform for one captured body. Redact may be called concurrently for different bodies; each returned writer is owned by one body and each input byte passes through it once. Close must flush parser state but must not close dst.
type BodyStore ¶
type BodyStore interface {
NewWriter(ctx context.Context, metadata BodyMetadata) (BodyWriter, error)
}
BodyStore creates BodyWriter instances. Implementations receive the capture pipeline's processed representation, while BodyInfo counters and hashes continue to describe the original caller/wire stream. Implementations must be safe for concurrent use.
type BodyValue ¶ added in v0.3.0
BodyValue accepts one selected plaintext value in chunks. Finish returns its protected representation and records exactly one replacement. Encryption buffers only up to the configured value limit, tokenization streams through HMAC, and failures or oversized values fail closed to [REDACTED]. A BodyValue must not be reused after Finish.
type BodyValueProtector ¶ added in v0.3.0
type BodyValueProtector interface {
NewValue() BodyValue
}
BodyValueProtector creates bounded streaming values that apply the transport's configured sensitive-value mode and token format. It is scoped to one body writer and must not be retained after that writer closes.
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 the captured representation of one body stream. When configured, the capture pipeline may decode and/or redact bytes before Write; it never requires a store to rewrite already-persisted content. 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 by the recording pipeline. With body redaction enabled, a captured request or response carrying a registered Content-Encoding is decoded and redacted before bytes reach the BodyStore. Otherwise, a fully captured response may be decoded when embedded in the HAR. Decoded HAR response content is marked "_decoded": true while bodySize, the body hash and body counters keep describing the encoded bytes observed by the caller. The live request and caller-visible response bytes 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. A complete, independently pinned and tested example is under docs/examples/content-decoders:
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"`
Redaction *RedactionInfo `json:"_redaction,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. Structured redaction is applied by the capture pipeline before bytes reach this store. 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, protection/key failures, recorder panics) are reported. Protection failures are aggregated per exchange direction to avoid log storms. 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 WithBodyCapturePolicy ¶ added in v0.2.0
func WithBodyCapturePolicy(policy BodyCapturePolicy) Option
WithBodyCapturePolicy installs a per-request/per-response body capture policy. Nil restores the global Options behavior.
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 WithRedaction ¶ added in v0.3.0
func WithRedaction(config RedactionConfig) Option
WithRedaction adds common and direction-specific rules to the Transport's redaction baseline. Name selectors are additive. A later custom body-redactor registration wins for the same normalized MIME type.
func WithSensitiveValueProtection ¶ added in v0.2.0
func WithSensitiveValueProtection(config SensitiveValueProtection) Option
WithSensitiveValueProtection configures the representation of values selected by built-in redaction rules.
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
// Redaction contains global common and direction-specific selector rules
// plus trusted custom body redactors. WithRedaction adds to this baseline;
// request contexts may add more through WithRequestRedaction.
Redaction RedactionConfig
// SensitiveValueProtection controls whether values selected by built-in
// redaction rules are removed, reversibly encrypted, or deterministically
// tokenized. The zero value redacts with [REDACTED]. Protection failures
// always fail closed to [REDACTED].
SensitiveValueProtection SensitiveValueProtection
// 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 by the capture and HAR-building pipeline. With body redaction
// configured, supported encodings are decoded and redacted
// while streaming into the BodyStore; otherwise a fully captured body
// may be decoded when embedded in the HAR. 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 bytes read by the caller.
ContentDecoders map[string]ContentDecoder
// BodyCapturePolicy optionally overrides capture, embedding, hashing,
// limits, and body-redactor selection for each request and response body.
// Policy errors and panics fail closed to metadata-only recording.
BodyCapturePolicy BodyCapturePolicy
// 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 every optional content/metadata capture; finalized entries still contain the core exchange lifecycle and body byte/completion accounting. 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 ProtectionCounts ¶ added in v0.2.0
type ProtectionCounts struct {
Redacted int64 `json:"redacted,omitempty"`
Encrypted int64 `json:"encrypted,omitempty"`
Tokenized int64 `json:"tokenized,omitempty"`
Fallbacks map[string]int64 `json:"fallbacks,omitempty"`
}
ProtectionCounts summarizes representation modes and fail-closed causes. Fallback keys are fixed recorder-defined codes and never contain errors, field names, key IDs, tokens, or original values.
type ProtectionKey ¶ added in v0.2.0
ProtectionKey is the active key used for a recorded sensitive value. ID is embedded in protected tokens to support rotation; it must not itself be sensitive. Encryption requires exactly 32 key bytes. Tokenization accepts 32 or more bytes.
type ProtectionKeyProvider ¶ added in v0.2.0
type ProtectionKeyProvider interface {
ProtectionKey(mode ProtectionMode) (ProtectionKey, error)
}
ProtectionKeyProvider returns the active key for the requested mode. It may be called concurrently and must not return key material that callers mutate.
type ProtectionKeyProviderFunc ¶ added in v0.2.0
type ProtectionKeyProviderFunc func(ProtectionMode) (ProtectionKey, error)
ProtectionKeyProviderFunc adapts a function to ProtectionKeyProvider.
func (ProtectionKeyProviderFunc) ProtectionKey ¶ added in v0.2.0
func (f ProtectionKeyProviderFunc) ProtectionKey(mode ProtectionMode) (ProtectionKey, error)
type ProtectionMode ¶ added in v0.2.0
type ProtectionMode string
ProtectionMode controls how configured sensitive values are represented in recorded copies. The zero value preserves the traditional [REDACTED] behavior.
const ( ProtectionRedact ProtectionMode = "redact" ProtectionEncrypt ProtectionMode = "encrypt" ProtectionTokenize ProtectionMode = "tokenize" )
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 RedactionConfig ¶ added in v0.3.0
type RedactionConfig struct {
// Common applies to request and response recordings.
Common RedactionRules
// Request adds rules only to request recordings.
Request RedactionRules
// Response adds rules only to response recordings.
Response RedactionRules
}
RedactionConfig contains common and direction-specific redaction rules. Common rules apply to both sides. The same type configures a Transport with WithRedaction and an individual request with WithRequestRedaction.
type RedactionInfo ¶ added in v0.2.0
type RedactionInfo struct {
Request *RedactionScopeInfo `json:"request,omitempty"`
Response *RedactionScopeInfo `json:"response,omitempty"`
Errors int64 `json:"errors,omitempty"`
RawTrace int64 `json:"rawTrace,omitempty"`
}
RedactionInfo summarizes actual redaction work without exposing rule names or original values. Counts describe values changed in the recorded copy.
type RedactionRules ¶ added in v0.3.0
type RedactionRules struct {
// Headers lists header names whose values are protected. A cookie is also
// protected when its Cookie or Set-Cookie carrier header is selected.
Headers []string
// QueryParameters protects URL/query-string values, URL-encoded form
// fields, and matching multipart field/file payloads and filenames.
QueryParameters []string
// Cookies lists parsed cookie names whose values are protected.
Cookies []string
// JSONFields recursively protects matching JSON object-field values while
// preserving unaffected source bytes.
JSONFields []string
// XMLElements protects matching XML element subtrees by local name;
// namespace prefixes are ignored and attributes are not selected.
XMLElements []string
// BodyRedactors registers trusted streaming redactors by exact normalized
// base MIME type. A later merged registration wins for the same type.
BodyRedactors map[string]BodyRedactor
}
RedactionRules adds field selectors and body redactors for one side of an HTTP exchange. Names are matched case-insensitively. Configurations merge additively, so request-scoped selectors cannot remove Transport selectors. A BodyRedactors entry is an explicit trusted override for its MIME type.
type RedactionScopeInfo ¶ added in v0.2.0
type RedactionScopeInfo struct {
URL int64 `json:"url,omitempty"`
Headers int64 `json:"headers,omitempty"`
QueryParameters int64 `json:"queryParameters,omitempty"`
Cookies int64 `json:"cookies,omitempty"`
Body *BodyRedactionInfo `json:"body,omitempty"`
Protection *ProtectionCounts `json:"protection,omitempty"`
}
RedactionScopeInfo summarizes one side of an exchange.
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 SensitiveValueProtection ¶ added in v0.2.0
type SensitiveValueProtection struct {
Mode ProtectionMode
KeyProvider ProtectionKeyProvider
MaxValueBytes int
}
SensitiveValueProtection configures the representation of all values selected by built-in redaction rules. MaxValueBytes bounds a single value buffered for encryption. Tokenization streams values through HMAC without retaining them. Values <= 0 select the safe 64 KiB default; values above 16 MiB are clamped. Failures and oversized values are replaced with [REDACTED].
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 no entries until a Recorder or OnEntryCompleted callback is set, and zero-value Options retain only core lifecycle/body accounting. 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.
Source Files
¶
- body_redactor.go
- body_store.go
- byte_sink.go
- capture_policy.go
- content_decoder.go
- errors.go
- file_recorder.go
- form_stream_redactor.go
- har.go
- json_stream_redactor.go
- memory_recorder.go
- multipart_stream_redactor.go
- options.go
- recorder.go
- redact.go
- redaction_audit.go
- request_body.go
- request_redaction.go
- response_body.go
- sensitive_value.go
- trace.go
- transport.go
- xml_stream_redactor.go
Directories
¶
| Path | Synopsis |
|---|---|
|
docs
|
|
|
examples/csv-redactor
Package csvredactor demonstrates a streaming recorder.BodyRedactor for CSV request and response bodies.
|
Package csvredactor demonstrates a streaming recorder.BodyRedactor for CSV request and response bodies. |
|
otelrecorder
module
|
