The ZAP exporter speaks MetricFamily, the shape a Prometheus gatherer yields. A
service instrumented with the OTel metric SDK produces ResourceMetrics, so it
had no way onto this wire and reached for an OTLP exporter — which pulls
protobuf and gRPC whether you pick the http flavour or the grpc one, since they
share otlpconfig.
OTelZAPExporter is that seam: it satisfies sdkmetric.Exporter and installs on a
periodic reader exactly where an OTLP exporter would.
Histograms decompose into classic cumulative buckets rather than a sketch,
because the wire carries Prometheus shapes and the o11y query plane already
reads them. Temporality is cumulative for every instrument for the same reason —
a delta sum means nothing to a Prometheus reader. An aggregation this wire
cannot express drops at one place, visibly, instead of arriving empty.
Several luxfi versions carry TWO different contents across this workspace,
because published tags were moved instead of a new patch being cut:
age@v1.5.0, pq@v1.0.3, threshold@v1.9.4, zap@v0.6.0, zap@v0.8.1.
Adjudicated before editing, since "checksum mismatch / SECURITY ERROR" is also
what a real supply-chain attack looks like. It is not one here: sum.golang.org
holds the OLD hash while proxy.golang.org and a direct fetch BOTH serve the same
NEW bytes. Two independent transports agreeing means nothing is rewriting
content in flight — the tag moved at source. The sumdb entry is a fossil:
GOPRIVATE covers github.com/luxfi/* with GOSUMDB=off, so our own modules never
consult the checksum DB and a moved tag splits consumers silently.
The old bytes are served by nothing now, so a stale pin can never build.
Corrected to the only content that exists, in BOTH line forms (h1: and
/go.mod h1:) — Go reports these one at a time, so a partial fix just relocates
the error.
Deliberately no `go mod tidy`: this changes no selected version, only the
recorded hash of versions already chosen.
Verified: `go list -m all` resolves with no checksum error.
The durable fix is upstream: never move a published tag, cut x.y.z+1 instead.
Add the prometheus-compatible symbols CoreDNS (and the kubernetes client)
reference so a mechanical prometheus -> luxfi/metric migration compiles and
behaves unchanged:
- HistogramOpts native (sparse) histogram fields (NativeHistogramBucketFactor et al)
- package MustRegister/Unregister; AlreadyRegisteredError (returned on duplicate
registration so re-registration on reload is tolerated)
- With/AutoFactory (promauto-style registerer-scoped constructors)
- GoCollectorOption/WithGoCollectorRuntimeMetrics/MetricsAll; NewGoCollector(opts...)
- {Counter,Gauge,Histogram,Summary}Vec.Delete(Labels)
- Register treats the no-op Go/process collectors as no-ops rather than errors
The ZAP MsgMetricBatch transport keys dispatch on Flags()>>8 and the message
wire format must match between exporter and receiver. The hanzo cloud binary
resolves zap v0.7.2; pinning v0.6.0 made standalone exporters wire-incompatible.
No code changes — exporter_zap.go builds + the round-trip test passes on v0.7.2.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
n.value++ on the noop counter was unguarded; concurrent workers in
luxfi/threshold/pkg/protocol/handler.go triggered -race failures
across protocols/{lss,frost} and pkg/thresholdd/TestCGGMP21RoundTrip.
Switch to atomic CAS over float64 bits stored in a uint64 — mirrors
metricCounter / metricGauge in metrics_impl.go so the noop and real
impls share the same internal representation (one and only one way).
Same pattern fixed in noopGauge (Set/Inc/Dec/Add/Sub/Get). noopHistogram,
noopSummary, and the *Vec wrappers are stateless / per-call fresh values
so they are race-free by construction and untouched.
Added TestNoop{Counter,Gauge}Concurrent regression guards (build-tag-free
so go test -race ./... continues to verify lock-free correctness).
Flagged by 2026-05-31 cross-repo audit.
Adds a fire-and-forget metric exporter that ships MetricFamily
snapshots over a luxfi/zap envelope to the o11y collector. Mirrors
luxfi/trace's ZAP exporter (MsgSpanBatch=1) — zero protobuf, zero
OTLP, zero gRPC. Wire format:
zap envelope, MsgType=MsgMetricBatch
└─ root object
└─ FieldPayload (bytes): JSON-encoded MetricBatch
MetricBatch carries app/version/resource metadata + a list of
MetricFamily rows (Gatherer.Gather() output translated to a
JSON-stable wire shape). +Inf histogram bucket dropped on encode
(JSON can't represent +Inf and SampleCount already conveys total).
NewZAPExporter creates the exporter; ExportGatherer(ctx, reg) drives
it from a scrape loop. Connect failures are silent — exporter retries
on the next call so the host never blocks on collector reachability.
Test is gated on -tags metrics (matches the rest of the test suite;
the package's no-op default-build skips Gather population entirely).
Receiver side lands in hanzoai/o11y/pkg/zapmetricreceiver.
Gosec G103 (unsafe) is expected for atomic operations. Nancy gets
401 from OSS Index. Codecov rate limits. Windows go.sum differs
across Go patch versions.
- Add native Go structs with JSON encoding (default, //go:build !grpc)
- Move protobuf types to //go:build grpc
- Add convert.go bridge to grpc build only
- Add client package tests for native types
Users can now build without protobuf dependency by default.
Use -tags=grpc to enable protobuf support.