Skip to content

Why the OpenTelemetry versions are pinned in lockstep

The problem the pins solve

A Go build resolves one version of each module for the whole program. A service built on go-tool-base that also uses this module gets one go.opentelemetry.io/otel, whichever of the two asked for the higher version.

That is usually fine, and occasionally not. The OpenTelemetry Go modules do not all share a version line — the stable API and SDK are on v1.x while the log SDK, the log exporter and the contrib bridges are on v0.x, and a v0.x release is only compatible with a particular v1.x. Upgrading one module of the set on its own can produce a build that compiles and a runtime that does not behave, or a compile error some distance from the change that caused it.

So the versions here are pinned to match go-tool-base's, and moved together:

require (
    go.opentelemetry.io/contrib/bridges/otelslog v0.19.0
    go.opentelemetry.io/otel v1.44.0
    go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0
    go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0
    go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0
    go.opentelemetry.io/otel/sdk v1.44.0
    // ...
)

Renovate updates them as a group, so a bump arrives as one merge request covering the whole set rather than eleven separate ones landing in an order nobody chose.

Why gRPC and genproto are pinned as well

The OTLP/HTTP exporters pull gRPC in transitively, and google.golang.org/genproto has existed both as one monolithic module and as split per-API modules. When a build graph contains both shapes, the same Go package is reachable by two paths and the build fails with an ambiguous import — a failure that names packages nobody in either repository imported directly.

Pinning google.golang.org/grpc and the split genproto/googleapis/{api,rpc} modules to go-tool-base's versions keeps one shape in the graph. The pins are documented in go.mod next to the requires, because the reason is not inferable from the versions themselves.

What this costs you

A consumer cannot pick its own OTel version freely. If your service needs a newer otel/sdk than the pin, Go's minimal version selection will give it to you — but you are then running a combination nobody has tested, and the pin exists precisely because that combination is where the trouble lives. The supported move is to bump go-tool-base and this module together.

The SDK defaults documented here can move under you. Batch sizes, timeouts, the retry budget and the environment variables the SDK honours are the SDK's, not this module's. The reference pages name the version they were read from for that reason.

An urgent OTel fix is a cross-repository change. Getting one in means moving go-tool-base as well, which is deliberate — the alternative is a service resolving two different OTel generations depending on which dependency won.

What is not pinned for this reason

github.com/cockroachdb/errors is the module's only non-OTel runtime dependency and has nothing to do with the lockstep. stretchr/testify is in the importable graph because mocks/ is published as a normal package rather than a test-only one, so a consumer wiring SettingsSource into their own service can mock it without hand-rolling a double.

The depfootprint_test.go guard is what keeps that list short: it allows go.opentelemetry.io and fails the build on go-tool-base, Viper, Cobra, pflag, Charm and the AWS, GCP and Azure SDKs. A regression that reintroduces framework coupling fails a test rather than quietly enlarging every downstream binary.