mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
The request boundary already holds everything a server span and a request record contain, so both are structs filled from data it has: the trace, span and parent ids it minted or joined, the route template as the name, start and duration, an outcome-derived status, and the caller's org when a gateway parked one. They are appended under one lock, drained once per tick and at shutdown, and shipped as JSON in a ZAP envelope — internal/o11y declares the batch shapes itself, no SDK, no protobuf, nothing a consumer inherits. The keys are the collector's vocabulary, not ours. It reads a path out of url.path / http.target / http.route / path in that order, so those are the keys this fills; http.path, which an earlier draft shipped, fills nothing. The contract/ module proves it the only way that stays true: it starts the collector's real receivers, serves one real request, and asserts on the structs those receivers produced. It is its own module because the collector already requires zip — the proof would otherwise be a cycle. The address is the switch, spelled O11Y_SPANS_ADDR / O11Y_LOGS_ADDR / O11Y_METRICS_ADDR — three names because the collector binds one listener per signal and promises no adjacency between them. The metrics name replaces O11Y_METRICS_ZAP_ENDPOINT, which deployments had already stopped matching; the socket preference is deleted because the collector only ever binds TCP, so that branch could not run. Nothing stated means nothing starts: no exporter, no node, no dial, no goroutine. An app's own logging stays its own. Only zip's request line ships to the log ear; teeing a program's every log line off the box is one decision for luxfi/log to make once, for everything that logs. Co-authored-by: Hanzo Dev <[email protected]>
31 lines
1.5 KiB
Go
31 lines
1.5 KiB
Go
// Package contract holds one proof: what a zip app exports is what the
|
|
// collector decodes.
|
|
//
|
|
// # Why it is a module of its own
|
|
//
|
|
// The proof needs hanzoai/o11y, and zip must not require it. Two reasons, both
|
|
// measured rather than assumed. It is circular — o11y already requires zip, so
|
|
// zip requiring o11y leaves neither end able to release without the other. And
|
|
// it is expensive for everyone else: adding o11y to zip's own module takes its
|
|
// go.sum from 147 lines to about 720 and its module graph past a thousand
|
|
// modules, a cost paid by every program built on zip in order to run a test that
|
|
// belongs to zip alone.
|
|
//
|
|
// A module here pays neither. Nothing imports this package, `go build ./...` and
|
|
// `go test ./...` in zip do not descend into it, and no consumer of zip inherits
|
|
// a line of it. CI runs it as its own step, which is where the cost belongs.
|
|
//
|
|
// # What it proves
|
|
//
|
|
// The two ends re-declare one contract: zip's internal/o11y package and the
|
|
// collector's pkg/zapreceiver and pkg/zaplogreceiver each state the batch shapes
|
|
// in their own types, so that a collector can be deployed without rebuilding
|
|
// every service that reports to it. Two declarations need something holding them
|
|
// together, and transcribing field names into a test only proves that a
|
|
// transcription matches itself.
|
|
//
|
|
// So this test runs the real receivers, has a real zip app answer a real
|
|
// request, and reads what the receivers decoded. A field renamed on either side
|
|
// fails it, and a field zip invents fails it too — it arrives as nothing.
|
|
package contract
|