샘플
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 with…
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
컨트랙트
- 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
파일
- NOTES.md
- csx.json
- go.mod
- go.sum
- otel_test.go
소스
# 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.
{"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"}
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
)
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=
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")
}
}