Documentation
¶
Overview ¶
HIP-0106 native observability subsystem — New/Mount composition-root form.
import "github.com/hanzoai/metrics"
metrics.Mount(app, metrics.Deps{Logger: log, DataDir: dir, Brand: brand, Org: principal.Org})
One subsystem serves all three signals — metrics, logs, traces — under /v1/{metrics,logs,traces}/* on the shared zip.App. Storage is native and WAL-durable; ingest for metrics is luxfi/metric.MetricBatch (the ZAP MsgMetricBatch payload). Every request is scoped to a tenant the AUTHENTICATING boundary resolved — Deps.Org, never a header this package reads itself — so the same binary serves any tenant with hard data isolation. There is no prometheus, no Grafana, no scrape endpoint, no /api/ path.
This package imports ONLY zap-proto/zip + luxfi (no hanzoai/cloud): it depends on what it uses — a logger, a data dir, a brand label and the tenant decision — which it declares in its own Deps. The composition root (cmd/cloud) constructs those and calls Mount explicitly; there is no global registry and no init() side effect.
Package metrics is the native, ZAP-native, prometheus-free time-series store for the Hanzo cloud. It replaces the vendored Grafana/Prometheus observability backends with a small in-process store that ingests luxfi/metric.MetricBatch (the same wire shape the ZAP MsgMetricBatch transport carries) and serves range queries under /v1/metrics/*.
The storage API is deliberately tiny (Append + Query + SeriesCount) so a durable per-tenant backend (DataDir-backed, columnar) can replace the in-memory map without changing mount.go or ingest.go. There is ZERO prometheus here.
Index ¶
- Constants
- func Mount(app *zip.App, deps Deps) error
- type Deps
- type LogRecord
- type LogStore
- type Registry
- type Sample
- type Series
- type Span
- type Store
- func (s *Store) Append(name string, labels map[string]string, smp Sample)
- func (s *Store) EnableDurability(path string) error
- func (s *Store) IngestBatch(b *metric.MetricBatch) int
- func (s *Store) Query(name string, matchers map[string]string, startNs, endNs int64) []Series
- func (s *Store) SeriesCount() int
- type TraceStore
- type WAL
Constants ¶
const MsgMetricBatch uint16 = 2
MsgMetricBatch is the canonical ZAP MsgType for metric batches — it matches luxfi/metric.MsgMetricBatch and o11y/pkg/zapmetricreceiver so any luxfi/metric ZAP exporter ingests here unchanged.
const Version = "0.4.0"
Version is surfaced on the /health routes.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Deps ¶ added in v1.110.2
type Deps struct {
// Logger is the canonical Hanzo logger; Mount derives a scoped child.
Logger luxlog.Logger
// DataDir is the per-deployment data root; per-org WALs land under it.
DataDir string
// Brand is the deployment's own label. It is NOT a tenant on the HTTP
// surface — see Org — and it stopped being one there. It remains the
// default on the ZAP receiver, which binds only when O11Y_ZAP_PORT is set,
// is not internet-reachable, and admits only peers already on the cluster
// network; a batch from such a peer that names no org is this deployment's
// own telemetry. An anonymous HTTP caller is not that, which is the whole
// difference.
Brand string
// Org is THE tenant decision, and it is not ours to make.
//
// This subsystem used to read X-Org-Id itself and fall back to the brand.
// That is a header a client sends, so the tenant boundary was whatever the
// caller typed: an anonymous request could name any org and read — or write
// — that org's metrics, logs and traces. Measured against production before
// the fix: POST /v1/logs/write with an invented X-Org-Id answered
// {"written":1}, and GET /v1/logs/query with the same header read it back.
//
// The rule the rest of the fleet applies is that an org is trustworthy only
// alongside a VALIDATED principal, and it lives in exactly one place —
// cloud's principal.OrgOf, whose own doc warns that a second hand-rolled
// check is drift waiting to happen. This field is how that one rule reaches
// here without this module importing cloud: the boundary that authenticates
// hands down the predicate, and every route asks it.
//
// It returns ok=false for an unauthenticated or org-less caller, and the
// route answers 403. There is no brand fallback: a default tenant for
// callers who proved nothing is the hole itself.
Org func(*zip.Ctx) (string, bool)
}
Deps is the NARROW dependency surface this subsystem declares — only what it uses. The composition root builds it from Config and passes it to Mount. No hanzoai/cloud import, no god-struct: a subsystem depends on what it needs, nothing more.
type LogRecord ¶ added in v0.2.0
type LogRecord struct {
TsNs int64 `json:"t"`
Level string `json:"level,omitempty"`
Body string `json:"body"`
Labels map[string]string `json:"labels,omitempty"`
}
LogRecord is one structured log line — the native, prometheus-free, Loki-free log signal. Bodies are stored verbatim; labels are the indexed dimensions.
type LogStore ¶ added in v0.2.0
type LogStore struct {
// contains filtered or unexported fields
}
LogStore is the native log store: an append-only bounded ring with label + time-range + case-insensitive substring query. Durable via the shared WAL.
func NewLogStore ¶ added in v0.2.0
func NewLogStore() *LogStore
NewLogStore returns an empty store retaining up to 1Mi records in memory.
func (*LogStore) Append ¶ added in v0.2.0
Append stores one log record (and durably logs it when durability is on).
func (*LogStore) EnableDurability ¶ added in v0.2.0
EnableDurability opens and replays a WAL at path (e.g. <DataDir>/logs/logs.wal).
func (*LogStore) Query ¶ added in v0.2.0
func (s *LogStore) Query(matchers map[string]string, startNs, endNs int64, contains string, limit int) []LogRecord
Query returns up to limit records (newest first) matching labels, the [startNs,endNs] range, and an optional case-insensitive substring of Body.
type Registry ¶ added in v0.3.0
type Registry struct {
// contains filtered or unexported fields
}
Registry lazily creates per-tenant store sets. Each tenant's WALs live under <DataDir>/orgs/<org>/o11y/ (the HIP-0302 per-org convention), so one tenant's metrics/logs/traces never mingle with another's — the same binary serves lux.cloud and zoo.cloud with hard data isolation. A single-tenant deployment simply uses one org (the deployment brand).
func NewRegistry ¶ added in v0.3.0
NewRegistry returns a registry rooted at dataDir ("" = in-memory, no durability).
type Sample ¶
Sample is a single timestamped value. Ts is nanoseconds since the Unix epoch to match luxfi/metric.MetricBatch.TimestampNs.
type Series ¶
type Series struct {
Name string `json:"name"`
Labels map[string]string `json:"labels,omitempty"`
Samples []Sample `json:"samples"`
}
Series is a named, labeled append-only stream of samples.
type Span ¶ added in v0.2.0
type Span struct {
TraceID string `json:"traceId"`
SpanID string `json:"spanId"`
Parent string `json:"parentId,omitempty"`
Name string `json:"name"`
StartNs int64 `json:"startNs"`
EndNs int64 `json:"endNs"`
Attrs map[string]string `json:"attrs,omitempty"`
}
Span is one unit of a distributed trace — the native, prometheus-free, Tempo-free trace signal. Times are nanoseconds since the Unix epoch.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the native in-memory time-series store. Each series is bounded to maxPerSeries samples (oldest evicted first). Safe for concurrent use.
func NewStore ¶
func NewStore() *Store
NewStore returns an empty store with a default per-series retention of 64Ki samples (a real deployment sets this from config / per-tenant quota).
func (*Store) Append ¶
Append adds one sample to the series identified by (name, labels), creating the series on first write and evicting the oldest sample past retention. When durability is enabled the sample is also written to the WAL.
func (*Store) EnableDurability ¶ added in v0.2.0
EnableDurability opens a write-ahead log at path and replays it into the store so samples survive restart. Replayed samples are not re-logged. Pass a per-deployment path (e.g. <DataDir>/metrics/metrics.wal).
func (*Store) IngestBatch ¶
func (s *Store) IngestBatch(b *metric.MetricBatch) int
IngestBatch writes every sample in a luxfi/metric.MetricBatch into the store. This is the exact wire type the ZAP MsgMetricBatch transport carries, so the same code path serves both the HTTP /v1/metrics/batch endpoint and a future ZAP receiver. Counter/gauge values land directly; histogram/summary families contribute derived <name>_sum and <name>_count series. Returns samples written.
func (*Store) Query ¶
Query returns copies of every series whose Name equals name (or all, if name is "") and whose labels are a superset of matchers, with samples restricted to [startNs, endNs] (a zero bound is treated as unbounded).
func (*Store) SeriesCount ¶
SeriesCount reports the number of distinct series held (surfaced on /health).
type TraceStore ¶ added in v0.2.0
type TraceStore struct {
// contains filtered or unexported fields
}
TraceStore is the native span store: an append-only bounded ring with a trace-id index for waterfall lookup and time-range listing. Durable via WAL.
func NewTraceStore ¶ added in v0.2.0
func NewTraceStore() *TraceStore
NewTraceStore returns an empty store retaining up to 1Mi spans in memory.
func (*TraceStore) Append ¶ added in v0.2.0
func (s *TraceStore) Append(sp Span)
Append stores one span (and durably logs it when durability is on).
func (*TraceStore) ByTrace ¶ added in v0.2.0
func (s *TraceStore) ByTrace(traceID string) []Span
ByTrace returns every span belonging to a trace id (the waterfall).
func (*TraceStore) Count ¶ added in v0.2.0
func (s *TraceStore) Count() int
Count reports the number of spans held.
func (*TraceStore) EnableDurability ¶ added in v0.2.0
func (s *TraceStore) EnableDurability(path string) error
EnableDurability opens and replays a WAL at path (e.g. <DataDir>/traces/traces.wal).
type WAL ¶ added in v0.2.0
type WAL struct {
// contains filtered or unexported fields
}
WAL is a simple append-only write-ahead log of length-prefixed records. It gives the in-memory stores durability: every write is appended here and the log is replayed on startup. This is intentionally a single flat segment — a production deployment adds rotation/compaction behind the same Append/Replay API without touching callers.
func OpenWAL ¶ added in v0.2.0
OpenWAL opens (creating parent dirs and the file as needed) an append log.