Extract the OTel observability group into a standalone go/observability module¶
- Authors
- Matt Cockayne, Claude Opus 4.8 (AI drafting assistant)
- Date
- 2026-07-16
- Status
- IMPLEMENTED (2026-07-18) — shipped to main; originally APPROVED (O1–O4 resolved in review 2026-07-16)
Summary¶
The pkg/telemetry/otelcore + logs + metrics + tracing group is GTB's
service-observability layer: hardened OpenTelemetry setup for OTLP logs, metrics,
and traces — endpoint/resource resolution, the typed per-signal Settings, and the
signal providers built on the OTel contrib SDK. It has clear reuse value for any Go
service that wants opinionated OTel wiring, independent of the rest of GTB.
This spec extracts it to gitlab.com/phpboyscout/go/observability following the
module-extraction playbook and is the
third and final Phase 2 foundation of the
transport-stack extraction plan
(after tls and authn).
The core is config-agnostic and works from typed Settings values; the single GTB
seam is otelcore's config-key adapter (Resolve / ObserveSettingsFromConfig,
which read config.Containable). Per O1 (resolved: full repoint), that adapter
relocates into root pkg/telemetry (package telemetry) rather than staying as a
facade package — a clean break: the in-tree pkg/telemetry/otelcore is deleted
outright and root-telemetry consumers import go/observability/otelcore directly. The
one material difference from tls/authn: the module carries the OpenTelemetry
SDK, so its depfootprint guard allows go.opentelemetry.io while still forbidding
go-tool-base, Viper/pflag/Cobra, Charm, and the cloud SDKs.
D3 — observability extracts without the telemetry split (resolved: yes)¶
The transport plan D3 and the
telemetry-split spec raised a
concern: root pkg/telemetry mixes product analytics (consent-gated Collector,
spill, deletion, machine identity, PostHog/Datadog backends) with observability,
and the report says "do not extract root telemetry before splitting these."
Measured on the current tree, that concern does not block this extraction:
- The observability subpackages (
otelcore/logs/metrics/tracing) import zero analytics code — no import of the rootpkg/telemetrypackage. Their only GTB coupling ispkg/config, confined tootelcore/config_adapter.go(and its test);viper/aferoappear only in tests. - The dependency runs analytics → observability, not the reverse: root
pkg/telemetry/backend_otel.go(the analytics OTel-log transport) consumesotelcore/logs. After extraction that becomes GTB-analytics →go/observability, a perfectly sound direction.
So the split spec's blocker is about extracting the analytics root, not the
observability group. Extracting observability first is clean and actually
advances the split: the observability concern physically leaves the package, leaving
root pkg/telemetry as analytics-plus-thin-wiring. O4 asks whether to formally
reframe the split spec in light of this.
Target module¶
- Module path:
gitlab.com/phpboyscout/go/observability(per naming convention; the report's proposed name). - Package layout: one module, subpackages
otelcore(core),logs,metrics,tracing— mirroring today's layout (logs/metrics/tracingimportotelcoreinternally). - Docs:
observability.go.phpboyscout.uk(light microsite — O2). - Local dir:
~/workspace/phpboyscout/go/observability. - Reference scaffold:
~/workspace/phpboyscout/go/tls(the facade sibling).
What moves vs. what stays¶
Moves into go/observability (the config-agnostic core + pure tests)¶
otelcore core — everything except the config adapter:
config.go(Config,SignalConfig,Settings,SignalOverrides,ResolveSettings,Root, theSignal*consts),endpoint.go(ParseEndpoint),resource.go(Resource),doc.go, and their pure tests. These import only the OTel SDK +cockroachdb/errors.
The signal subpackages whole: logs/, metrics/, tracing/ (each *.go +
exporter_error_test.go + *_test.go). They import otelcore (intra-module) and the
OTel exporter/SDK packages — no GTB imports.
Stays in GTB (the config-key adapter, relocated — O1: full repoint)¶
There is no in-tree otelcore package after this change. The config-key adapter
moves up into root pkg/telemetry (package telemetry):
otelcore/config_adapter.go→ a newpkg/telemetry/observability_otel_config.go(packagetelemetry) holdingResolve(cfg config.Containable, signal string) SettingsandObserveSettingsFromConfig(...), now referencing the module types viaobs "gitlab.com/phpboyscout/go/observability/otelcore"(obs.Settings,obs.Config,obs.SignalConfig,obs.SignalOverrides,obs.ResolveSettings,obs.Root). Its test moves alongside.- Root-telemetry callers of the adapter are already in package
telemetry, sootelcore.Resolve(...)becomes the localResolve(...)(or a lightly-renamed helper to avoid collisions); every otherotelcore.*reference becomesobs.*against the module.
Consumer surface & cut-over strategy¶
The only GTB consumers of the group are within root pkg/telemetry itself:
observability.go, observability_config_adapter.go, observability_settings.go,
and backend_otel.go. The transport packages do not consume it — they instrument
via the OTel contrib SDK directly (per the transport plan).
Used symbols: otelcore.Settings (10×), otelcore.Resolve (3×, the adapter that
stays), otelcore.Root (2×), otelcore.Resource (2×), otelcore.ParseEndpoint (1×),
otelcore.Signal{Logs,Metrics,Tracing} (1× each); plus logs/metrics/tracing
imports in observability.go (+ backend_otel.go uses logs).
Cut-over shape (O1 — resolved: full repoint, no facade):
- Delete
pkg/telemetry/otelcore/entirely; importgo/observability/otelcoreasobsin rootpkg/telemetry. Repoint the ~18otelcore.*references (Settings×10,Root×2,Resource×2,ParseEndpoint, theSignal*consts) toobs.*; the adapterResolve/ObserveSettingsFromConfigrelocate into packagetelemetry(above) and their call sites become local calls. logs/metrics/tracing→ repoint: repoint the imports inobservability.go/backend_otel.gofrom…/pkg/telemetry/{logs,metrics,tracing}to…/go/observability/{logs,metrics,tracing}and delete the in-tree subpackages.- A clean break (Matt's call): no shim/facade package lingers under
pkg/telemetry.
Repository framework (playbook §3)¶
Bootstrap from the go/tls scaffold, with OTel allowed:
- CI:
go-lint,go-test(enable_e2e: false),go-security,releaser-pleaser,zensical-pages,renovate-self— all@v0.22.0. depfootprint_test.go: allowgo.opentelemetry.io/*(and its transitivegoogle.golang.org/grpc,google.golang.org/protobuf, etc.); forbidgo-tool-base,spf13/viper,spf13/pflag,spf13/cobra,charmbracelet,aws-sdk-go,cloud.google.com/go,Azure/azure-sdk. This is the key guard difference from the leaf/tls/authnmodules.- OTel version lockstep: pin the exact OTel versions GTB uses today
(
otel/[email protected],exporters/otlp/*atv0.20.0/v1.44.0,contrib/bridges/[email protected],semconv v1.26.0). GTB and the module must track OTel together — note this in the compatibility section and let grouped Renovate keep them aligned (O3). .golangci.yamlv2, local prefixgitlab.com/phpboyscout/go/observability; ≥90% coverage;cockroachdb/errors;LICENSE, README,requirements-lock.txt.
Documentation (playbook §4)¶
- Author
observability.go.phpboyscout.uk(light microsite — O2) from the group'sdoc.go+ GTB's observability docs, de-GTB-ed (the config-keyResolvecascade stays in GTB docs; the module docs cover OTel setup, endpoint/resource resolution, and the typedSettings). Reference → pkg.go.dev. - GitLab Pages custom domain + Cloudflare DNS DNS-only +
pages_access_level=public pages_primary_domain=https://…(per the.golearnings).- GTB side: the observability component page keeps the config-adapter +
telemetry.*key-cascade story and links out; migration note indocs/reference/migration/; extraction-report checklist ticked.
Migration procedure (playbook §5)¶
- Bootstrap
phpboyscout/go/observabilityrepo; trivial-commit CI green; Pages domain + DNS. git mvtheotelcorecore (minusconfig_adapter.go) +logs/metrics/tracing- pure tests; add the OTel-allowing
depfootprint_test.go; confirm ≥90%; race/lint/vet/govulncheck green (watch OTel-transitive OSV advisories). - Author the microsite; wire Pages + DNS.
- Cut
v0.1.0via the Release-MR flow (Matt merges). - GTB cut-over (single MR): add
go/observability v0.1.0; reducepkg/telemetry/otelcoreto the facade (config_adapter.go+ areexport.go); delete the moved core + the in-treelogs/metrics/tracing; repoint their imports inobservability.go/backend_otel.go;just cigreen (modulo the knowninternal/agentbuildvcs test); migration note; component-page stub; report tick. - Downstream hand-off via the migration note (we do not edit downstream repos).
Resolved decisions (review 2026-07-16)¶
- O1 — Cut-over shape: full repoint, no facade. Delete
pkg/telemetry/otelcore/outright; rootpkg/telemetryimportsgo/observability/otelcoredirectly and the config-key adapter relocates into packagetelemetry. A clean break — no lingering facade/shim — consistent with GTB's pre-1.0 "prefer a clean break over a shim" stance.logs/metrics/tracinglikewise repoint-and-delete. - O2 — Docs: light microsite at
observability.go.phpboyscout.uk(family consistency withtls/authn): overview + a how-to (wire OTLP logs/metrics/traces) - an explanation of endpoint resolution and the
OTEL_EXPORTER_OTLP_*env fallback. Reference → pkg.go.dev. - O3 — OTel version policy: pin + lockstep. Pin the module to GTB's exact OTel
versions (
otel/[email protected],exporters/otlp/*v0.20.0/v1.44.0,contrib/bridges/[email protected],semconv v1.26.0); grouped Renovate keeps GTB and the module aligned; a README compatibility line documents the coupling. - O4 — Proceed observability-first, then reframe the split spec. This extraction
is the observability half of the analytics/observability split, done physically.
After it lands, revise the DRAFT
telemetry-split spec to
cover only the remaining analytics concern (root
pkg/telemetry→ an analytics module + PostHog/Datadog backend modules); its proposed in-tree split is largely moot once observability physically leaves.
Acceptance criteria¶
go/observabilitybuilds undergitlab.com/phpboyscout/go/observabilitywithotelcore/logs/metrics/tracingsubpackages;depfootprint_test.gopasses (OTel allowed; no go-tool-base/framework/cloud-SDK); ≥90% coverage; race/lint/vet green;v0.1.0published and proxy-resolvable.- GTB consumes
go/observability v0.1.0;pkg/telemetry/otelcoreis the facade;logs/metrics/tracingdeleted and repointed; no root-telemetryotelcore.*reference changed;just cigreen (modulo the knowninternal/agenttest). - Docs live per O2; migration note added; extraction-report checklist ticks
observability; the telemetry-split spec is reframed per O4.