CodeSampleX

Пример

go.opentelemetry.io/otel v1.35.0: Baggage validates RFC 7230 token keys and delimiter-free values while resolving duplicate keys with last-value-wins semantics, and CompositeTextMapPropagator coordinates W3C TraceContext and Baggage header extraction and injection

Проверенный пример — golang go.opentelemetry.io/otel v1.35.0: Baggage validates RFC 7230 token keys and delimiter-free values while resolving duplicate keys…

sha256:7d96ce8c19aad54d6646ca788af8f4fcadf1aeda6a24ca303cfabacc5991341e

Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла. Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди. MIT-0

Свидетельства выполнения

Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.

Основа свидетельства
Подписанный контракт пройден
Квитанции проверки
2
Ключи подписи, собравшие его
2
Заявленная среда 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-17
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

Кейс

HOW
Цель
Baggage validates RFC 7230 token keys and delimiter-free values while resolving duplicate keys with last-value-wins semantics, and CompositeTextMapPropagator coordinates W3C TraceContext and Baggage header extraction and injection
Пакеты
Символы
  • go.opentelemetry.io/otel/baggage.New
  • go.opentelemetry.io/otel/baggage.NewMember
  • go.opentelemetry.io/otel/baggage.FromContext
  • go.opentelemetry.io/otel/baggage.Baggage.DeleteMember
  • go.opentelemetry.io/otel/baggage.Baggage.Member
  • go.opentelemetry.io/otel/baggage.Baggage.Len
  • go.opentelemetry.io/otel/propagation.NewCompositeTextMapPropagator
  • go.opentelemetry.io/otel/propagation.TraceContext
  • go.opentelemetry.io/otel/propagation.Baggage
  • go.opentelemetry.io/otel/propagation.HeaderCarrier
Окружение
go
Создан
2026-08-17T06:53:08Z

Контракт

  1. Baggage.New applies last-value-wins deduplication when duplicate member keys are provided, while NewMember strictly enforces RFC 7230 token syntax on keys and rejects unencoded delimiters in values
  2. DeleteMember produces a new immutable Baggage with the target key removed without mutating the original Baggage
  3. CompositeTextMapPropagator combines TraceContext and Baggage to extract valid SpanContext and percent-decoded Baggage from W3C HTTP headers
  4. CompositeTextMapPropagator injects traceparent, tracestate, and percent-encoded baggage headers into a carrier
  5. Extracting a malformed traceparent header yields an invalid SpanContext without panicking

Файлы

  • NOTES.md
  • csx.json
  • go.mod
  • go.sum
  • otel_test.go

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

NOTES.md
# OpenTelemetry Go Baggage Lifecycle and Composite TextMapPropagator Semantics

## Search Known Solution Result
A search for `go.opentelemetry.io/otel` identified existing samples covering trace lifecycle, metric providers, and attribute truncation:
- `sha256:2def692d5f3bfcef049fe24c06470937e1e7fafec85ed2219eb5c0dd894b2597` (*SpanFromContext never returns nil on untraced contexts, RecordError does not set error status*)
- `sha256:156f49b57b8b92e942054f3b0ede10ac18e1ba7f44d5b6c66ccd3af01503465f` (*MeterProvider views configure instrument renaming, attribute allow-list filtering, and ManualReader collections*)
- `sha256:19df5703d8dc81fc82d005c9a1da5e4e2cbf15e79f3bda3895e0373c83079fce` (*Attribute value length truncation across slice and map types*)

This sample verifies distinct behaviour in the core API: W3C Baggage member validation, last-value-wins duplicate resolution, immutable member deletion, and composite TextMapPropagator distributed context coordination across `traceparent`, `tracestate`, and `baggage` HTTP headers.

## Pinned Release
- `go.opentelemetry.io/otel`: `v1.35.0`
- `go.opentelemetry.io/otel/trace`: `v1.35.0`

## Observed Behaviour
1. **Baggage Member Validation**: `baggage.NewMember(key, value)` enforces RFC 7230 token syntax on keys (rejecting whitespace and separator characters) and rejects unescaped delimiters (`,`, `;`, `\`, `"`) in values.
2. **Duplicate Member Resolution**: When multiple members with the same key are passed to `baggage.New(members...)`, the last member provided wins and becomes the stored value.
3. **Immutable Baggage Deletion**: `baggage.DeleteMember(key)` returns a new `Baggage` instance with the member removed and length decremented, leaving the original `Baggage` instance unmodified.
4. **Composite Context Propagation**: `propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})` coordinates both tracing and correlation headers. Calling `Extract` extracts `traceparent` (TraceID, SpanID, TraceFlags) and `tracestate` into a valid `trace.SpanContext` and automatically decodes percent-encoded baggage values (e.g. `DF%2028` to `DF 28`). Calling `Inject` serializes the context into standard W3C HTTP headers.
5. **Malformed Header Resilience**: Extracting a malformed or invalid `traceparent` header string yields an invalid `SpanContext` without panicking or modifying unaffected context values.
csx.json
{"case":{"caseId":"case:sha256:126830ef362fa1ec75a8a4dfb534ac5ab97b08a5bcffba7cb2a91f72de1fc6e0","contract":["Baggage.New applies last-value-wins deduplication when duplicate member keys are provided, while NewMember strictly enforces RFC 7230 token syntax on keys and rejects unencoded delimiters in values","DeleteMember produces a new immutable Baggage with the target key removed without mutating the original Baggage","CompositeTextMapPropagator combines TraceContext and Baggage to extract valid SpanContext and percent-decoded Baggage from W3C HTTP headers","CompositeTextMapPropagator injects traceparent, tracestate, and percent-encoded baggage headers into a carrier","Extracting a malformed traceparent header yields an invalid SpanContext without panicking"],"goal":"Baggage validates RFC 7230 token keys and delimiter-free values while resolving duplicate keys with last-value-wins semantics, and CompositeTextMapPropagator coordinates W3C TraceContext and Baggage header extraction and injection","kind":"HOW","packages":["pkg:golang/go.opentelemetry.io/otel@v1.35.0","pkg:golang/go.opentelemetry.io/otel/trace@v1.35.0"],"schemaVersion":1,"symbols":["go.opentelemetry.io/otel/baggage.New","go.opentelemetry.io/otel/baggage.NewMember","go.opentelemetry.io/otel/baggage.FromContext","go.opentelemetry.io/otel/baggage.Baggage.DeleteMember","go.opentelemetry.io/otel/baggage.Baggage.Member","go.opentelemetry.io/otel/baggage.Baggage.Len","go.opentelemetry.io/otel/propagation.NewCompositeTextMapPropagator","go.opentelemetry.io/otel/propagation.TraceContext","go.opentelemetry.io/otel/propagation.Baggage","go.opentelemetry.io/otel/propagation.HeaderCarrier"]},"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.35.0","pkg:golang/go.opentelemetry.io/otel/trace@v1.35.0"],"schemaVersion":1,"symbols":["go.opentelemetry.io/otel/baggage.New","go.opentelemetry.io/otel/baggage.NewMember","go.opentelemetry.io/otel/baggage.FromContext","go.opentelemetry.io/otel/baggage.Baggage.DeleteMember","go.opentelemetry.io/otel/baggage.Baggage.Member","go.opentelemetry.io/otel/baggage.Baggage.Len","go.opentelemetry.io/otel/propagation.NewCompositeTextMapPropagator","go.opentelemetry.io/otel/propagation.TraceContext","go.opentelemetry.io/otel/propagation.Baggage","go.opentelemetry.io/otel/propagation.HeaderCarrier"],"verifierAdapter":"golang@1"}
go.mod
module example.com/otel-propagation

go 1.22.0

require (
	go.opentelemetry.io/otel v1.35.0
	go.opentelemetry.io/otel/trace v1.35.0
)
go.sum
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/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
otel_test.go
package main

import (
	"context"
	"net/http"
	"strings"
	"testing"

	"go.opentelemetry.io/otel/baggage"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
)

func TestBaggageValidationAndDeduplication(t *testing.T) {
	// 1. Key and value syntax validation per W3C Baggage spec
	validMember, err := baggage.NewMember("user_id", "usr-12345")
	if err != nil {
		t.Fatalf("expected valid member creation to succeed: %v", err)
	}
	if validMember.Key() != "user_id" || validMember.Value() != "usr-12345" {
		t.Fatalf("unexpected member values: key=%q, val=%q", validMember.Key(), validMember.Value())
	}

	// Space in key is invalid under RFC 7230 token rules
	if _, err := baggage.NewMember("user id", "123"); err == nil {
		t.Fatalf("expected error for member key containing whitespace")
	}

	// Comma in value is invalid (needs percent-encoding in W3C baggage)
	if _, err := baggage.NewMember("key", "val1,val2"); err == nil {
		t.Fatalf("expected error for member value containing comma delimiter")
	}

	// Semicolon in value is invalid
	if _, err := baggage.NewMember("key", "val1;val2"); err == nil {
		t.Fatalf("expected error for member value containing semicolon delimiter")
	}

	// Backslash in value is invalid
	if _, err := baggage.NewMember("key", "val\\path"); err == nil {
		t.Fatalf("expected error for member value containing backslash")
	}

	// 2. Last-value-wins deduplication in baggage.New
	mFirst, _ := baggage.NewMember("tenant", "alpha")
	mSecond, _ := baggage.NewMember("tenant", "beta")
	bag, err := baggage.New(mFirst, mSecond)
	if err != nil {
		t.Fatalf("unexpected error creating baggage with duplicate keys: %v", err)
	}
	if bag.Len() != 1 {
		t.Fatalf("expected deduplicated baggage length 1, got %d", bag.Len())
	}
	if got := bag.Member("tenant").Value(); got != "beta" {
		t.Fatalf("expected last member value 'beta', got %q", got)
	}

	// 3. Immutable deletion via DeleteMember
	bagDeleted := bag.DeleteMember("tenant")
	if bagDeleted.Len() != 0 {
		t.Fatalf("expected deleted baggage length 0, got %d", bagDeleted.Len())
	}
	if bagDeleted.Member("tenant").Value() != "" {
		t.Fatalf("expected empty value for deleted member, got %q", bagDeleted.Member("tenant").Value())
	}
	// Original baggage must remain unchanged
	if bag.Len() != 1 || bag.Member("tenant").Value() != "beta" {
		t.Fatalf("original baggage mutated after DeleteMember")
	}
}

func TestCompositePropagator(t *testing.T) {
	// 1. Composite propagator combines TraceContext and Baggage fields
	propagator := propagation.NewCompositeTextMapPropagator(
		propagation.TraceContext{},
		propagation.Baggage{},
	)

	fields := propagator.Fields()
	if len(fields) != 3 || fields[0] != "traceparent" || fields[1] != "tracestate" || fields[2] != "baggage" {
		t.Fatalf("unexpected propagator fields: %v", fields)
	}

	// 2. Extract distributed context from W3C HTTP headers
	headers := http.Header{}
	headers.Set("traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
	headers.Set("tracestate", "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7")
	headers.Set("baggage", "userId=alice,serverNode=DF%2028")

	ctx := propagator.Extract(context.Background(), propagation.HeaderCarrier(headers))

	// Verify extracted SpanContext properties
	sc := trace.SpanContextFromContext(ctx)
	if !sc.IsValid() {
		t.Fatalf("expected extracted SpanContext to be valid")
	}
	if sc.TraceID().String() != "4bf92f3577b34da6a3ce929d0e0e4736" {
		t.Fatalf("unexpected TraceID: %s", sc.TraceID().String())
	}
	if sc.SpanID().String() != "00f067aa0ba902b7" {
		t.Fatalf("unexpected SpanID: %s", sc.SpanID().String())
	}
	if !sc.IsSampled() {
		t.Fatalf("expected SpanContext sampled flag to be true")
	}
	if sc.TraceState().String() != "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7" {
		t.Fatalf("unexpected TraceState: %s", sc.TraceState().String())
	}

	// Verify extracted Baggage properties and percent-decoding
	bag := baggage.FromContext(ctx)
	if bag.Len() != 2 {
		t.Fatalf("expected 2 baggage members, got %d", bag.Len())
	}
	if bag.Member("userId").Value() != "alice" {
		t.Fatalf("expected userId 'alice', got %q", bag.Member("userId").Value())
	}
	if bag.Member("serverNode").Value() != "DF 28" {
		t.Fatalf("expected serverNode 'DF 28' (percent-decoded), got %q", bag.Member("serverNode").Value())
	}

	// 3. Inject context into new Carrier and verify headers
	outCarrier := propagation.HeaderCarrier(http.Header{})
	propagator.Inject(ctx, outCarrier)

	if outCarrier.Get("traceparent") != "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" {
		t.Fatalf("unexpected injected traceparent: %q", outCarrier.Get("traceparent"))
	}
	if outCarrier.Get("tracestate") != "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7" {
		t.Fatalf("unexpected injected tracestate: %q", outCarrier.Get("tracestate"))
	}
	bagHeader := outCarrier.Get("baggage")
	if !strings.Contains(bagHeader, "userId=alice") || !strings.Contains(bagHeader, "serverNode=DF%2028") {
		t.Fatalf("unexpected injected baggage header: %q", bagHeader)
	}

	// 4. Malformed traceparent extraction leaves SpanContext invalid without panic
	malformedCarrier := propagation.HeaderCarrier(http.Header{"traceparent": []string{"invalid-header"}})
	badCtx := propagator.Extract(context.Background(), malformedCarrier)
	if badSc := trace.SpanContextFromContext(badCtx); badSc.IsValid() {
		t.Fatalf("expected invalid SpanContext from malformed traceparent header")
	}
}

Исходный сидер

csx-seed