libo11y
It provides one-call OpenTelemetry setup and enforces a machine-readable specification at runtime.
Why it exists
Service teams repeatedly write the same hundred-plus lines of OpenTelemetry bootstrap for resource composition, exporter wiring, propagator installation, log correlation, and shutdown ordering. Most implementations get at least one part wrong, and these failures are quiet and expensive. They can sever trace propagation, prevent logs from being joined to traces, drop buffered telemetry during shutdown, or allow a telemetry library to terminate its host process. libo11y packages the correct configuration once per language.
Inconsistent setup also undermines OpenTelemetry itself. Its API, OTLP, and semantic conventions exist so that telemetry from any service means the same thing to any backend, but the standard leaves each application to choose its conventions version, environment names, propagators, and attributes. A service still reporting http.status_code while the rest of the fleet reports http.response.status_code drops out of every error-rate query, dashboard, and SLO built on the current name, and nothing reports an error.
A second, broader motivation is that OpenTelemetry's semantic conventions include no machine-readable notion of sensitivity and no concept of required organizational identity. As a result, every service independently implements redaction through string matching. libo11y records those rules once in a versioned registry and enforces them with generated machinery instead of code review.
What it is
libo11y is a multi-language observability monorepo. A single call to o11y.Setup(ctx, o11y.WithServiceName("checkout"), o11y.WithEnvironment(spec.EnvironmentProduction)) builds tracer, meter, and logger providers over one shared resource. It installs the W3C TraceContext and Baggage propagators, establishes a correlated slog pipeline, enables runtime metrics and trace-based exemplars, and returns an SDK handle with an idempotent, deadline-bounded shutdown that reports per-signal failures through errors.Join. Application code then uses vanilla OTEL and stock slog. The library exports no span, metric, or log wrapper types because it wraps setup rather than instrumentation. Consequently, a consumer's codebase depends on it only through the single setup call.
The distinguishing component is the spec/ directory, which contains a semconv-format YAML registry. The registry defines a tiered resource-attribute taxonomy, including an explicit forbidden tier for attributes such as host.ip and process.command_line. It also defines a registered event catalog, cardinality budgets, and a per-attribute sensitivity class (public, internal, pii, secret) that OpenTelemetry does not provide. A generator validates the registry against a policy suite and produces typed Go constants, internal validation tables, and taxonomy documentation. It also writes an OpenTelemetry schema file, served at https://schemas.o11y.ing/libo11y/1.0.0 and stamped on every resource. A drift gate detects and rejects any inconsistency among them. Only the Go implementation exists so far, released as v0.1.0. A cross-language design contract includes a parity matrix that marks unbuilt items as planned or excluded.
How it works
The generated tables are required at runtime. During initialization, tier-0 identity is enforced with typed errors. Setup returns an error for a blank or unknown_service name or for an unregistered environment. It derives service.instance.id as a UUIDv5 over Kubernetes pod identity, so the value remains stable across restarts within the same pod. On the emit path, a sensitivity layer classifies each attribute by its effective dotted key after any user-supplied replacer runs. This prevents classification from being bypassed by nesting an attribute in a slog.Group or renaming it to a classified key. Secrets are always dropped. PII is dropped by default, or replaced with a keyed HMAC token when hashing is configured with a key of at least 32 bytes, and each drop or rewrite increments a self-metric. The same policy covers every signal: every log sink and log bridge, spans and injected span processors, the resource, and metric streams with their exemplars.
Three mechanisms implement most of the design.
Group resolution is implemented by the fanout
slog.Handler. This keeps trace correlation attributes at the top level inside open groups while providing structurally identical records to every sink. The handler passes the stdlibslogtestsuite.The startup summary also serves as a canary because it passes through the entire log pipeline before any global is installed. A sink that rejects the summary therefore causes
Setupto fail instead of silently dropping records in production. Similarly, a failed shutdown flush emits a registered dropped-telemetry event to the surviving sinks.The capture-time redaction types (
Secret[T],Masked, and tag-drivenStruct) implement six rendering interfaces. This prevents any printf or JSON path from bypassing them, and unknown tags fail closed.
Why it matters
The design assumes that telemetry policy belongs in a specification rather than in convention. A typed API makes invalid states unrepresentable, initialization-time validation enforces identity, an always-on emit layer enforces sensitivity, and CI conformance tests detect code that bypasses the generated constants. Lock-in is deliberately avoided by supporting only standard OTEL_* variables, using no proprietary configuration, and keeping the OpenTelemetry logs SDK out of its public API, which exposes only the logs API interface, so that SDK changes cannot propagate through it. The Go modules are released as v0.1.0 and complete for their scope, and 57 percent of their lines are tests that run under the race detector. A hermetic test harness allows consumers to assert conformance and correlation in their own builds.
Roadmap
Go is the first implementation of a contract planned for Rust, Swift, C++, C, Node.js, Ruby, PHP, Erlang and Elixir, browser JavaScript, and Python, followed by Java, Kotlin, Zig, and .NET with C#. Each library will be generated from the same registry and build on its language's official OpenTelemetry SDK where one exists. C and Zig have none, so those libraries will wrap the C++ SDK or speak OTLP directly. On the JVM, a Java library will work alongside Spring Boot's Micrometer-based observability. A shared conformance suite will compare the OTLP payloads of every implementation.
The aim is one standard for every service in a system, whatever its language. A polyglot request keeps one trace across language boundaries, every query and alert matches the same attribute names, and a small or medium team gets the defaults a platform team would otherwise have to build. The blog post Building libo11y around an OpenTelemetry registry covers the reasoning in detail.