o11y

package
v1.801.437 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 48 Imported by: 0

Documentation

Overview

Package o11y is your logs, metrics and traces: ship them in, query them, chart them.

It is the ONE owner of the cloud binary's observability plane — registered as a SINGLE `o11y` subsystem (this file's init) that internally mounts, in the load-bearing order, every part of the concept:

READ/SERVE plane (specific /v1/o11y/* routes, registered BEFORE the
hanzoai/o11y wildcard so Fiber's in-order match gives them precedence):
  - tenant-scoped reads  /v1/o11y/{logs,metrics,status}   (scope.go)
  - fleet availability   /v1/o11y/availability             (availability.go)
  - flat builder query   /v1/o11y/{query,query_range}      (query.go)
  - event ingest         POST /v1/event/ingestion          (event_ingest.go)
  - Sentry-wire ingest   POST /v1/event/{project}/envelope|store (via cloud.ObsErrorIngest)
RUNTIME handler the hanzoai/o11y wildcard (order 70) delegates to via
  o11y.SetHandler — the in-process runtime (embed.go) or a reverse-proxy
  fallback (this file).
WRITE plane (order-independent):
  - ZAP span+log receivers + opt-in in-process trace sink, all writing
    the event plane (planesink.go)

Decomplection (one and one way): these were five separately-registered subsystems (o11yscope 69, o11y-runtime 71, o11y-event-ingest 68, o11y-otlp-ingest 72, o11y-trace-inproc 73) whose names leaked FIVE public concepts into the registry (five config toggles, five /v1/<name>/health routes). The k8s-style ordering was an internal impl detail. They now collapse to ONE registration of the name `o11y` (order 69): mountO11y performs the ordered sub-mounts in-process, so the PUBLIC concept is a single `o11y`. Behavior is preserved EXACTLY — every route registers at the same point relative to the order-70 wildcard as before (all inside the one order-69 mount, so all before 70).

Co-ownership: the upstream github.com/hanzoai/o11y module ALSO registers the name `o11y` (order 70, the wildcard route surface) from its own init. The two entries are co-owners of ONE public concept; this order-69 entry opts out of the generic HIP-0106 health route (cloud.HealthOwner) so /v1/o11y/health is registered EXACTLY once, by the module's order-70 co-entry.

One way, two backings (mountRuntime):

  • PRIMARY: the in-process runtime (buildEmbeddedHandler), enabled by O11Y_TELEMETRYSTORE_DATASTORE_DSN. Serves telemetry from cloud itself.
  • FALLBACK: a reverse proxy to a still-running o11y Deployment, used only when the embed is disabled (no DSN) or fails to init. Fail-soft, zero downtime.

Path is preserved verbatim: /v1/o11y/* reaches the o11y runtime unchanged, which rewrites /v1/o11y/* -> /api/* internally (see o11y app.createPublicServer). The gateway terminates auth and propagates identity as X-* headers; the runtime (embedded) or the proxy (fallback) sees the same request.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MountO11y

func MountO11y(host *zip.App, deps cloud.Deps) error

MountO11y composes the whole observability surface into its host as ONE app, through zip.App.Graft — the same seam apps/iam composes identity through.

Why the surface is an app and not a pile of routes on the host's router

A grafted op arrives carrying Origin = the child's AppName, and zip qualifies every named type that op reaches as "<origin>.<Type>" — unconditionally, not on collision, so a published name is never a function of who else is in the room (zip schemaRegistry.nameFor). That is why identity's 95 schemas are iam.* and have never collided with anything.

Registered straight onto the host, as this was, every one of these ops has an empty Origin and its types are published under their bare Go names. o11y's are ordinary words — Account, Channel, Event, Host, Service, TLSConfig — and so are books', content's, analytics', plugins' and ingress'. Six names, twelve shapes, one components block: a refusal at the weave (openapi/weave.go — "one name, two shapes: every generated SDK would bind whichever it read last"), and had it not been refused, an SDK binding whichever the merge read last. `make -f mk/fleet.mk openapi-weave` could not run at all, so nobody could regenerate openapi.yaml or add an API surface and prove it.

ONE origin for the whole product, not one for the module and none for the rest. The cloud-native reads here and hanzoai/o11y's relay table are two halves of one route table — that is what mountScope's "specific routes before the module's" invariant IS — and splitting the origin down that seam would namespace half of o11y's types and leave the other half bare, which is two conventions for one product.

What the child changes, and what it must not

Nothing on the wire. Graft registers the child's own absolute patterns on the host's router, in the order the child declares them, pointing at one delegate that re-runs the child's router on the SAME fasthttp request. So the host's chain — EdgeCORS, the identity boundary, the abuse gate — still runs first and unchanged, and the ORDER two claimants on one address are resolved in is the order they are written in below, which is the order they were already in. That is load-bearing: cloud's org-pinned GET /v1/o11y/{logs,metrics} and POST /v1/o11y/query_range are the ONE owner of those three addresses (scope.go), the module declares them too, and first-registered is what makes the tenant-pinned handler the one that answers. Inside one app that stays a local fact about two adjacent lines instead of a global fact about the host's mount order.

The child is a NAMESPACE, not a second server: it carries the host's logger and cloud.ErrorHandler and no policy of its own, because everything a request meets before its route belongs to the host. Contrast iam, whose child is a whole service that brings its own Guard, error handler and config.

cloud.Bridge still crosses, because zip.Ctx.SetContext parks on the *fasthttp.RequestCtx and the delegate hands the child that same RequestCtx — so the validated org parked at /v1/o11y is the one every typed op behind it reads. door_test.go is the end-to-end proof, over the real mount.

The one thing a graft cannot carry

zip.App.Declaration drops HEAD and OPTIONS unconditionally — they are the shadows fiber generates for a GET and for CORS, and a host does not route those on their own. A door opened with All therefore cannot cross a graft intact: OPTIONS is a METHOD the /v1/sentry proxy genuinely answers, and it is published as an operation. So that one door is registered on the HOST, at the same point in the same order it always was, and it costs nothing here because a wildcard proxy declares no typed op and contributes no schema. Everything with a shape to name is in the child.

func ShutdownO11y

func ShutdownO11y(ctx context.Context) error

shutdownO11y tears down the write-plane resources that hold process-lifetime connections, in REVERSE mount order — plane ingest (trace sink + receivers), event-ingest Datastore — so buffered spans/logs/rows flush before exit. Best-effort: the first error is returned but every teardown still runs. Idempotent and nil-safe.

Types

type StatusComponent added in v1.801.350

type StatusComponent struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	// CurrentStatus is this component's own condition: "full_outage" for a
	// service that did not answer its health probe at all.
	CurrentStatus string `json:"current_status"`
}

StatusComponent is one piece of the platform an incident affects. Name is the service the fleet prober knows it by.

type StatusIncident added in v1.801.350

type StatusIncident struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Status string `json:"status"`
	URL    string `json:"url"`
	// LastUpdateAt is when the failing measurement this incident reports was
	// read, RFC3339 UTC.
	LastUpdateAt      string `json:"last_update_at"`
	LastUpdateMessage string `json:"last_update_message"`
	// CurrentWorstImpact is the incident's impact on the PLATFORM, which is not
	// the same question as the component's own condition above.
	CurrentWorstImpact string            `json:"current_worst_impact"`
	AffectedComponents []StatusComponent `json:"affected_components"`
}

StatusIncident is one ongoing incident. Every field is measured: an incident exists because a probe failed, and LastUpdateAt is when that measurement was taken.

type StatusMaintenance added in v1.801.350

type StatusMaintenance struct {
	ID                 string            `json:"id"`
	Name               string            `json:"name"`
	Status             string            `json:"status"`
	URL                string            `json:"url"`
	LastUpdateAt       string            `json:"last_update_at"`
	LastUpdateMessage  string            `json:"last_update_message"`
	AffectedComponents []StatusComponent `json:"affected_components"`
	StartsAt           string            `json:"starts_at,omitempty"`
	EndsAt             string            `json:"ends_at,omitempty"`
}

StatusMaintenance is one planned maintenance window. Hanzo has no maintenance scheduling plane, so both maintenance lists below are always empty — which is a true statement ("nothing is scheduled"), not a placeholder. The type is part of the published contract because a client reading the document has to know those fields are arrays of objects.

type StatusSummary added in v1.801.350

type StatusSummary struct {
	PageTitle string `json:"page_title"`
	// PageURL is the HUMAN status page — an HTML page for people, distinct from
	// this JSON endpoint. Every link in this document points there.
	PageURL                string              `json:"page_url"`
	OngoingIncidents       []StatusIncident    `json:"ongoing_incidents"`
	InProgressMaintenances []StatusMaintenance `json:"in_progress_maintenances"`
	ScheduledMaintenances  []StatusMaintenance `json:"scheduled_maintenances"`
	// CheckedAt is when the underlying availability read was taken, RFC3339 UTC.
	// Not part of the status-page schema the panel parses (which ignores unknown
	// fields); it is here because a status document with no timestamp cannot be
	// told apart from a stale one.
	CheckedAt string `json:"checked_at"`
}

StatusSummary is the public platform status document.

Jump to

Keyboard shortcuts

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