zeekayandHanzo Dev 2d4b46d8ee mount: the wildcard is gone — every one of 367 routes is named
The delegation catch-all made three facts indistinguishable: a route that was
converted, a route that was not, and a route that did not exist. That is not a
theoretical cost — it is exactly how 83 typed ops shipped in files nothing
called, passing a full green suite while every one of those routes fell through
the wildcard.

So: derive the gap precisely, close it, and delete the catch-all.

THE GAP, re-derived. Extract every path the runtime registers (134 in
pkg/query-service/app/routes_*.go, 233 in pkg/apiserver/o11yapiserver/*.go — the
134 matching the count routes.go documents), normalise :params on both sides,
and diff against what Mount installs. 367 runtime routes, 43 unnamed. Not the
four hatch classes the plan expected: eleven were genuine hatches and
THIRTY-TWO were simply never converted.

CONVERTED (32 new typed ops, three new slices):
  * traces.go       — a trace's spans, the trace field catalog and its tuning
                      write, waterfall, flamegraph, span aggregations
  * tracefunnels.go — funnel CRUD plus twelve analytics reads, six over a saved
                      funnel and six over one described inline
  * spanmappers.go  — the ingest-time attribute mapping groups and their mappers

THE ELEVEN HATCHES, each its own route literal with its reason beside it
(mount.go mountHatches): four streams with no single answer to name (livetail,
query_progress long-poll, its websocket, the chunked export), three 303
redirects whose answer is a Location and not a body, and four Sentry-SDK ingest
doors — a foreign protocol we RECEIVE, whose /api/ segment the SDK hardcodes and
which is not ours to rename.

TWO OF THOSE ELEVEN WERE UNREACHABLE. /ws/query_progress and the two
/v1/sentry/<project>/{envelope,store}/ doors sit outside /v1/o11y, so the old
catch-all never saw them: a Sentry SDK pointed at the clean root got a 404 in
the composed binary, and telemetry_test.go passed only because the test
registered the wildcard the deployment did not have. Naming every route is what
surfaced it.

ONE SEAM, TWO ROOTS. Five copies of the relay function (relay, relayAt,
relayO11y, relayAPM, send) differing only in a prefix and whether they took
query parameters, and NINE names for two values — identityPrefix, infraPrefix,
integrationsPrefix, metricsRoot, apmPrefix, accessPrefix, rulesAlertsPrefix and
o11yRoot were all "/v1/o11y". Ops now pass the whole public path and relay
concatenates nothing: 193 call sites, one implementation, -520 lines.

Also: the per-slice *_zipdoc.go files fold back into zipdoc_gen.go as their own
headers instructed; the health probes' fall-through names its destination
instead of relying on a catch-all being registered after it (they would have
started 404ing the moment SetHealth was late — precisely when liveness matters);
O11yLogField/O11yLogFieldsOut/O11yLogFieldSetting become
O11yTelemetryField/O11yFieldCatalogOut/O11yFieldSetting because logs and traces
tune ONE catalog through one runtime type.

PAYOFF, counted off the document rather than the router: 353 operations in the
published OpenAPI spec and 353 MCP tools, from 0 for this surface before the
conversion began. 353 typed + 11 hatches + 3 probes = 367.

RECORDED, NOT FIXED: the runtime renders with jsoniter and zip with the stdlib
encoder, and jsoniter does not implement `,omitzero` — so for the seventeen
fields across pkg/types that use it the runtime writes the zero value where a
typed op omits the key. Pre-existing on every shipped op reaching
TelemetryFieldKey. Pinned by traces_test.go rather than hidden.

gorilla/mux and gin stay: mux is the runtime's own router in 100 files, and gin
is imported only for its form binder by pkg/valuer/valuer.go
(ginbinding.BindUnmarshaler) and pkg/http/binding/query.go
(ginbinding.MapFormWithTag). go mod tidy dropped 687 lines of go.mod/go.sum.

Build and full suite green: 133 packages ok, 0 fail.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 17:15:37 -07:00
2021-01-24 13:09:49 +05:30
2026-04-23 19:24:27 -07:00
2021-01-03 18:15:44 +05:30
2026-04-23 19:24:27 -07:00

o11y

o11y

Metrics, traces and logs for the Hanzo platform, in one place. OpenTelemetry on the wire, Hanzo Datastore — an OLAP column store — on disk.

License

Run it

cd deploy/docker
docker compose up -d

Open http://localhost:8080. deploy/install.sh does the same thing with a few more checks, and deploy/docker/docker-compose.ha.yaml is the multi-replica variant.

Send it data

The collector listens for OTLP on 4317 (gRPC) and 4318 (HTTP). Point any OpenTelemetry SDK or the OTel Collector at it:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

Traces, metrics and logs land in Datastore and show up under APM, Logs, Traces, Metrics and Exceptions in the UI. Dashboards and alerting are built on the same queries.

What it is for

Every Hanzo service emits here. It is multi-tenant by design: queries scope by org from the X-Org-Id claim in the JWT, on every panel. It sits behind gateway like the other Hanzo subsystems, and mounts under the unified cloud binary per HIP-0106.

   instrumented app  ->  OTLP  ->  o11y collector
                                       |
                                 Hanzo Datastore
                                       |
                             APM | logs | traces | metrics
                                       |
                          queries scoped by X-Org-Id from JWT
                                       |
                          dashboards | alerting | exceptions

Docs

Lineage

Forked from SigNoz (MIT Expat, copyright 2020–present SigNoz Inc.), synced to upstream main at commit 3e6339019. The import history was squashed, so the root commit here is a Hanzo branding commit rather than upstream's history. See NOTICE.

License

MIT — see LICENSE.

S
Description
Hanzo o11y — OpenTelemetry-native observability (metrics, traces, logs) for the Hanzo platform, backed by Hanzo Datastore.
Readme MIT
128 MiB
Languages
TypeScript 49.3%
Go 42.3%
SCSS 4%
Python 3.4%
JavaScript 0.6%
Other 0.2%