サンプル
go.opentelemetry.io/otel v1.45.0: Multiple WithInstrumentationAttributes and WithInstrumentationAttributeSet options merge scope attributes with last-value-wins precedence instead of overwriting earlier options
検証済みサンプル — golang go.opentelemetry.io/otel v1.45.0: Multiple WithInstrumentationAttributes and WithInstrumentationAttributeSet options merge scope attributes…
sha256:754781c365eac75c8696c1c32b0184086b76a32bfc2e42ecdcf75496a3940a73
このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。
合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。
MIT-0
実行証拠
宣言された環境と署名済みの実行を分けてあります。このサンプルが何をどこで実行したかをそのまま確認できます。
- 証拠の基準
- 署名済みコントラクト合格
- 検証レシート
- 3
- ビルドした署名鍵
- 3
宣言された環境
go linux x64 go go gomod
検証実行環境
| 環境 | コントラクト | ステージ | 実行日 |
|---|---|---|---|
| go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-16 |
| go 1.26 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-18 |
| go 1.26 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1golang:1.26-alpine@sha256:28d89ee9cc0f… |
2026-09-08 |
ケース
HOW- ゴール
- Multiple WithInstrumentationAttributes and WithInstrumentationAttributeSet options merge scope attributes with last-value-wins precedence instead of overwriting earlier options
- シンボル
-
- go.opentelemetry.io/otel/trace.NewTracerConfig
- go.opentelemetry.io/otel/trace.WithInstrumentationAttributes
- go.opentelemetry.io/otel/trace.WithInstrumentationAttributeSet
- go.opentelemetry.io/otel/trace.TracerConfig.InstrumentationAttributes
- 環境
- go
- 作成日
- 2026-08-16T12:32:20Z
コントラクト
- assert multiple WithInstrumentationAttributes options merge disjoint keys across option calls
- assert duplicate keys across WithInstrumentationAttributes calls use last-value-wins semantics
- assert intra-call duplicate keys in WithInstrumentationAttributes synchronously deduplicate
- assert interleaved WithInstrumentationAttributes and WithInstrumentationAttributeSet merge correctly
- assert tracer version and schema URL options do not disturb merged instrumentation attributes
ファイル
- NOTES.md
- csx.json
- go.mod
- go.sum
- tracer_config_test.go
ソース
# Notes
- **search_known_solution result**: The network returned sample `sha256:1bf17bde93ae26bd833bf9e707662cf033b05798d2ab0c1289d2d691da1b1942` ("RecordError records an exception event leaving span status as Unset, while SetStatus Ok irrevocably locks the span status against subsequent Error updates") and sample `sha256:6dc41c32b8472be7a4589a2a43c284208b012f6e51c2e4bc20a8fb60d0ec1c33` ("Default global TextMapPropagator silently no-ops cross-boundary trace context injection..."). We selected a different trap: the behavioral bugfix in OpenTelemetry Go (#7300 / #7266 in v1.39.0+) where multiple `WithInstrumentationAttributes` options previously overwrote each other instead of merging attributes.
- **What a model would write instead**: A model would compose multiple `trace.WithInstrumentationAttributes(...)` options across modular configuration helpers assuming OpenTelemetry merges scope attributes across option invocations in all releases.
- **How the wrong version fails**: On pre-1.39.0 releases (e.g., v1.38.0), it fails silently with a green build: tracers initialize and export spans cleanly, but all attributes from earlier `WithInstrumentationAttributes` calls are completely overwritten by the last option call, silently dropping vital scope attributes (such as `service.name`, `environment`, and `team`) from exported spans.
{"case":{"caseId":"case:sha256:07109c448b2b4ee62ecb081257fd4be4f4b5f04181b9d53e27be678f191ad119","contract":["assert multiple WithInstrumentationAttributes options merge disjoint keys across option calls","assert duplicate keys across WithInstrumentationAttributes calls use last-value-wins semantics","assert intra-call duplicate keys in WithInstrumentationAttributes synchronously deduplicate","assert interleaved WithInstrumentationAttributes and WithInstrumentationAttributeSet merge correctly","assert tracer version and schema URL options do not disturb merged instrumentation attributes"],"goal":"Multiple WithInstrumentationAttributes and WithInstrumentationAttributeSet options merge scope attributes with last-value-wins precedence instead of overwriting earlier options","kind":"HOW","packages":["pkg:golang/go.opentelemetry.io/otel@v1.45.0","pkg:golang/go.opentelemetry.io/otel/trace@v1.45.0"],"schemaVersion":1,"symbols":["go.opentelemetry.io/otel/trace.NewTracerConfig","go.opentelemetry.io/otel/trace.WithInstrumentationAttributes","go.opentelemetry.io/otel/trace.WithInstrumentationAttributeSet","go.opentelemetry.io/otel/trace.TracerConfig.InstrumentationAttributes"]},"contractCommand":["go","test","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"gomod","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/go.opentelemetry.io/otel@v1.45.0","pkg:golang/go.opentelemetry.io/otel/trace@v1.45.0"],"schemaVersion":1,"symbols":["go.opentelemetry.io/otel/trace.NewTracerConfig","go.opentelemetry.io/otel/trace.WithInstrumentationAttributes","go.opentelemetry.io/otel/trace.WithInstrumentationAttributeSet","go.opentelemetry.io/otel/trace.TracerConfig.InstrumentationAttributes"],"verifierAdapter":"golang@1"}
module sample
go 1.26.5
require (
go.opentelemetry.io/otel v1.45.0
go.opentelemetry.io/otel/trace v1.45.0
)
require github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package sample_test
import (
"testing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
func TestTracerConfigInstrumentationAttributesMerging(t *testing.T) {
t.Run("multiple WithInstrumentationAttributes merge disjoint keys across option calls", func(t *testing.T) {
// Prior to v1.39.0 (#7300), each WithInstrumentationAttributes option overwrote
// earlier attributes instead of merging them.
cfg := trace.NewTracerConfig(
trace.WithInstrumentationAttributes(
attribute.String("service.name", "orders-api"),
attribute.String("environment", "production"),
),
trace.WithInstrumentationAttributes(
attribute.String("region", "us-east-1"),
),
)
set := cfg.InstrumentationAttributes()
if set.Len() != 3 {
t.Fatalf("expected 3 merged attributes, got %d", set.Len())
}
val, exists := set.Value(attribute.Key("service.name"))
if !exists || val.AsString() != "orders-api" {
t.Errorf("expected service.name 'orders-api', got %v (exists: %v)", val, exists)
}
val, exists = set.Value(attribute.Key("environment"))
if !exists || val.AsString() != "production" {
t.Errorf("expected environment 'production', got %v (exists: %v)", val, exists)
}
val, exists = set.Value(attribute.Key("region"))
if !exists || val.AsString() != "us-east-1" {
t.Errorf("expected region 'us-east-1', got %v (exists: %v)", val, exists)
}
})
t.Run("duplicate keys across WithInstrumentationAttributes calls use last-value-wins semantics", func(t *testing.T) {
// Prior to v1.39.0 (#7300), the second option replaced the entire set, losing unaffected keys.
// In v1.39.0+, attributes merge and duplicate keys resolve via last-value-wins.
cfg := trace.NewTracerConfig(
trace.WithInstrumentationAttributes(
attribute.String("version", "v1.0.0"),
attribute.String("team", "backend"),
),
trace.WithInstrumentationAttributes(
attribute.String("version", "v2.0.0"),
),
)
set := cfg.InstrumentationAttributes()
if set.Len() != 2 {
t.Fatalf("expected 2 attributes, got %d", set.Len())
}
val, exists := set.Value(attribute.Key("version"))
if !exists || val.AsString() != "v2.0.0" {
t.Errorf("expected overridden version 'v2.0.0', got %v (exists: %v)", val, exists)
}
val, exists = set.Value(attribute.Key("team"))
if !exists || val.AsString() != "backend" {
t.Errorf("expected preserved team 'backend', got %v (exists: %v)", val, exists)
}
})
t.Run("intra-call duplicate keys in WithInstrumentationAttributes synchronously deduplicate", func(t *testing.T) {
cfg := trace.NewTracerConfig(
trace.WithInstrumentationAttributes(
attribute.Int("retries", 1),
attribute.Int("retries", 3),
),
)
set := cfg.InstrumentationAttributes()
if set.Len() != 1 {
t.Fatalf("expected 1 deduplicated attribute, got %d", set.Len())
}
val, exists := set.Value(attribute.Key("retries"))
if !exists || val.AsInt64() != 3 {
t.Errorf("expected last intra-call retries 3, got %v (exists: %v)", val, exists)
}
})
t.Run("interleaved WithInstrumentationAttributes and WithInstrumentationAttributeSet merge correctly", func(t *testing.T) {
initialSet := attribute.NewSet(
attribute.String("component", "http-router"),
attribute.Int("tier", 1),
)
cfg := trace.NewTracerConfig(
trace.WithInstrumentationAttributeSet(initialSet),
trace.WithInstrumentationAttributes(
attribute.Int("tier", 2),
attribute.Bool("sampled", true),
),
trace.WithInstrumentationVersion("1.5.0"),
trace.WithSchemaURL("https://opentelemetry.io/schemas/1.26.0"),
)
set := cfg.InstrumentationAttributes()
if set.Len() != 3 {
t.Fatalf("expected 3 merged attributes, got %d", set.Len())
}
val, exists := set.Value(attribute.Key("component"))
if !exists || val.AsString() != "http-router" {
t.Errorf("expected component 'http-router', got %v (exists: %v)", val, exists)
}
val, exists = set.Value(attribute.Key("tier"))
if !exists || val.AsInt64() != 2 {
t.Errorf("expected overridden tier 2, got %v (exists: %v)", val, exists)
}
val, exists = set.Value(attribute.Key("sampled"))
if !exists || !val.AsBool() {
t.Errorf("expected sampled true, got %v (exists: %v)", val, exists)
}
if cfg.InstrumentationVersion() != "1.5.0" {
t.Errorf("expected version '1.5.0', got %q", cfg.InstrumentationVersion())
}
if cfg.SchemaURL() != "https://opentelemetry.io/schemas/1.26.0" {
t.Errorf("expected schema URL 'https://opentelemetry.io/schemas/1.26.0', got %q", cfg.SchemaURL())
}
})
}