feat(observability): DEPL-006 — OpenTelemetry seed (surface only; no spans yet)

Acquisition-audit DEPL-006 closure (Sprint 6 ACQ, 2026-05-16).

Pre-2026-05-16, go.mod listed go.opentelemetry.io/otel,
otel/metric, otel/trace, otelhttp, and auto/sdk all as indirect
deps (pulled transitively by AWS / Azure SDKs at v1.41.0). The
SDK was never initialized — the global otel.GetTracerProvider()
returned the SDK noop provider, and certctl emitted zero spans.

This commit stands up the surface so operators with an OTel
collector can opt in via CERTCTL_OTEL_ENABLED=true without code
changes. It does NOT add per-handler / per-query / per-connector
span instrumentation — that's a v2.3 roadmap follow-up. The
DEPL-006 audit finding is closed by the surface being present.

Transport choice: OTLP/HTTP (proto-binary over HTTPS), NOT
OTLP/gRPC. Both are valid OTel transports; downstream collectors
accept either. HTTP keeps certctl's dep surface narrow — gRPC
pulls in google.golang.org/grpc + the full genproto stack, which
would expand binary size + supply-chain attack surface for a
feature that today emits zero spans. Operators with gRPC-only
collectors can run an OTel-collector tee. Swapping to gRPC later
is a single-import change.

Files
=====
- internal/observability/otel.go: new Init function. Gated by
  CERTCTL_OTEL_ENABLED. Builds an OTLP/HTTP exporter, wraps in
  a BatchSpanProcessor, installs as the otel global tracer
  provider, returns shutdown. Disabled-mode returns a no-op
  shutdown so callers defer unconditionally.
- internal/observability/otel_test.go: 3 tests — disabled-mode
  no-op (global tracer provider unchanged), enabled-mode
  registers an SDK tracer provider, OTEL_SERVICE_NAME flows
  through resource.WithFromEnv.
- internal/config/config.go: new ObservabilityConfig sub-config
  with a single OTelEnabled bool. Single env var
  (CERTCTL_OTEL_ENABLED); everything else flows through the
  standard OTEL_* env vars the OTel SDK honors directly via
  resource.WithFromEnv + otlptracehttp.New. Deliberately no
  CERTCTL_OTEL_SERVICE_NAME / CERTCTL_OTEL_ENDPOINT etc. —
  avoids the lying-field footgun where an env var exists in
  config but doesn't reach the consumer.
- cmd/server/main.go: wire observability.Init unconditionally
  near the existing demo / RFC1918 startup banners. The defer'd
  shutdown gets a 5-second timeout so an unreachable collector
  doesn't hang process exit.
- go.mod: promote go.opentelemetry.io/otel + otel/sdk +
  otlptracehttp from indirect → direct (the four pre-existing
  otel deps stay where go mod resolution puts them).
- go.sum: refreshed deps.

The genproto split (newer genproto/googleapis/{api,rpc} submodules
vs the old monolithic genproto module) needed an explicit
google.golang.org/genproto pin to a post-split pseudo-version to
resolve cleanly — included in this commit's go.mod.

Verified locally: gofmt clean, go vet clean, staticcheck clean
across internal/observability + internal/config + cmd/server;
go test -short -count=1 green on all three; `go build ./cmd/server`
produces a 30.9MB binary that boots; targeted tests
(TestInit_Disabled_NoOp / TestInit_Enabled_RegistersTracerProvider /
TestInit_Enabled_RespectsOTEL_SERVICE_NAME) all PASS.
This commit is contained in:
shankar0123
2026-05-16 19:45:42 +00:00
parent 5c5bbedc7e
commit 35277c0f2c
6 changed files with 383 additions and 3 deletions
+17 -3
View File
@@ -23,12 +23,25 @@ require (
github.com/leanovate/gopter v0.2.11
github.com/masterzen/winrm v0.0.0-20250927112105-5f8e6c707321
github.com/pkg/sftp v1.13.10
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0
golang.org/x/crypto v0.50.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0
software.sslmate.com/src/go-pkcs12 v0.7.0
)
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
@@ -110,11 +123,12 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
google.golang.org/genproto v0.0.0-20260511170946-3700d4141b60
gopkg.in/yaml.v3 v3.0.1 // indirect
)