httplog

package
v0.5.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: PostgreSQL Imports: 10 Imported by: 0

Documentation

Overview

Package httplog renders HTTP requests and responses to a diagnostic stream at an ordered level, and is the one place in this CLI that decides what must never be printed.

It exists as a shared package rather than a helper in each module because two of the three rules here are security rules. A masking bug fixed in one copy and not the other is invisible until it isn't, and per-module copies of shared rules have drifted in this repo before — that is what internal/testsupport exists to prevent for env lists.

Index

Constants

View Source
const MaxDumpBytes = 8192

MaxDumpBytes bounds each dumped body.

8 KB rather than the 512 that internal/starfleet/conn uses for error messages: that cap is right for quoting what a proxy said in a one-line error, and wrong here, where it would clip the middle of the cluster payload somebody enabled --debug to read. Unbounded is not an option either — an unbounded echo is a bug this repo has already fixed once, when a 200 KB proxy error page landed whole on stderr.

Variables

This section is empty.

Functions

func HeaderLines

func HeaderLines(h http.Header) []string

HeaderLines renders h as "Name: value" lines in sorted order, with the values of maskedHeaders replaced by the mask. Callers add their own prefix.

It is exported because the dry-run report renders headers too, and this package's whole reason for existing is that a masking rule with two implementations has one that is wrong. maskedHeaders keeps exactly one consumer path: this function.

Sorting is for reproducibility: Go's header map has no order, and an unordered dump is hard to diff between two runs.

func RedactBody

func RedactBody(raw []byte, limit int) string

RedactBody renders a body for a diagnostic stream: bounded at limit characters, with the VALUE of every credential-bearing field masked and everything else passed through byte for byte.

Byte-for-byte outside the masked spans is essential, and it is why this does not decode-and-re-encode. Re-encoding erases the difference between an explicit `"plan_expires_at": null` and an omitted key, which is precisely the distinction a typed parser already collapses and the wire dump exists to reveal. So the decoder here is used only to LOCATE each secret value's byte span; the surrounding bytes are never rewritten, reordered or reformatted.

Masking values rather than replacing the whole body is what keeps --debug useful. A service write reduced to its top-level key names is the single key "services" — enough to know a secret was present, not enough to debug anything. Masking per value keeps allow_writes, embedding_model and the service id readable while the key beside them is not.

Every field in secretFieldNames is masked at any depth: a wrapper object would otherwise smuggle a token past a top-level-only check. A composite value (an object, as with byoc's `credentials`) is masked whole, so nothing nested inside one can survive.

It fails CLOSED. If the spans cannot be computed for any reason, it falls back to replacing the whole body rather than risking an echo.

HTML does not parse as JSON, so the proxy/SSO-interstitial case that motivates dumping bodies at all is untouched.

func Wrap

func Wrap(base http.RoundTripper, out io.Writer,
	lvl Level) http.RoundTripper

Wrap returns base wrapped so that traffic through it is rendered to out at lvl. At Off, or with no writer, it returns base itself rather than a wrapper that decides to print nothing — the quiet path is every normal command, and it should add neither an allocation nor a layer of indirection.

Types

type Level

type Level int

Level is how much of an exchange reaches the diagnostic stream. The values are ordered, so a comparison against Debug is meaningful and callers never have to enumerate.

const (
	// Off prints nothing.
	Off Level = iota
	// Verbose prints the request line, a masked Authorization header
	// when one is present, and the response status with elapsed time.
	// It deliberately carries no bodies: a verbose run that dumped
	// payloads would bury the progress information it exists to show.
	Verbose
	// Debug prints everything Verbose does, plus every request and
	// response header and both bodies.
	Debug
)

func LevelFor

func LevelFor(verbose, debug bool) Level

LevelFor maps the two global flags onto a level. **--debug implies --verbose**: that ordering is the documented contract, and stating it is what stops `--verbose --debug` from being a combination nobody can predict. Two booleans where one is a superset of the other are only coherent if the superset relation is defined somewhere, and this is where.

func (Level) String

func (l Level) String() string

String names the level for diagnostics and test output.

type Transport

type Transport struct {
	Base  http.RoundTripper
	Out   io.Writer
	Level Level
}

Transport renders each exchange to Out. Use Wrap rather than constructing one directly, so the Off case stays uniform.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip renders req, delegates to Base, and renders the response.

It never fails a request for a diagnostic reason: a body it cannot read for the dump is reported as unavailable and the exchange continues.

Jump to

Keyboard shortcuts

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