From a9d8b2056add2f7af238da87ca3fea9e5c534007 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Tue, 9 Jun 2026 11:35:59 +0200 Subject: [PATCH 01/22] envcar: ignore non-normalized env names on Carrier.Get and Carrier.Keys --- CHANGELOG.md | 4 +++ propagators/envcar/carrier.go | 36 ++++++++++++-------- propagators/envcar/carrier_example_test.go | 5 --- propagators/envcar/carrier_test.go | 38 +++++++++++++--------- propagators/envcar/doc.go | 2 +- propagators/envcar/normalize.go | 23 +++++++++++++ propagators/envcar/normalize_test.go | 19 +++++++++++ 7 files changed, 91 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2a89a79d0..ce8bd38ba32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add `azurecontainerapps` resource detector for Azure Container Apps. (#8939) +### Changed + +- Ignore non-normalized environment variable names on `Carrier.Get` and `Carrier.Keys` in `go.opentelemetry.io/contrib/propagators/envcar`. + diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 9e6206b3f43..0445b5ecd55 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -11,9 +11,11 @@ import ( "go.opentelemetry.io/otel/propagation" ) -// Carrier is a TextMapCarrier that uses the environment variables as a -// storage medium for propagated key-value pairs. The keys are normalized -// before being used to access the environment variables. +// Carrier is a TextMapCarrier that uses environment variables as a storage +// medium for propagated key-value pairs. Keys passed to [Carrier.Get] and +// [Carrier.Set] are normalized before lookup or write. Environment variables +// read from the current process are stored only when their names are already +// normalized. // This is useful for propagating values that are set in the environment // and need to be accessed by different processes or services. // The keys are uppercased to avoid case sensitivity issues across different @@ -25,6 +27,7 @@ import ( // https://opentelemetry.io/docs/specs/otel/context/env-carriers/#environment-variable-immutability type Carrier struct { // SetEnvFunc is the function that sets the environment variable. + // [Carrier.Set] calls SetEnvFunc with a normalized key. // Usually, you want to set the environment variables for processes // that are spawned by the current process. SetEnvFunc func(key, value string) @@ -35,24 +38,28 @@ type Carrier struct { // Compile time check that Carrier implements the TextMapCarrier. var _ propagation.TextMapCarrier = (*Carrier)(nil) -// fetch runs once on first access, and stores the environment in the -// carrier. +// fetch runs once on first access, and stores environment variables with +// already-normalized names in the carrier. func (c *Carrier) fetch() { c.once.Do(func() { environ := os.Environ() c.values = make(map[string]string, len(environ)) for _, kv := range environ { kvPair := strings.SplitN(kv, "=", 2) - key := normalize(kvPair[0]) + key := kvPair[0] + if !normalized(key) { + continue + } c.values[key] = kvPair[1] } }) } -// Get returns the value associated with the normalized passed key. +// Get returns the value associated with the normalized key. // The first call to [Carrier.Get] or [Carrier.Keys] for a -// given Carrier will read and store the values from the -// environment and all future reads will be from that store. +// given Carrier will read and store the values from environment variables +// whose names are already normalized, and all future reads will be from that +// store. func (c *Carrier) Get(key string) string { c.fetch() return c.values[normalize(key)] @@ -70,12 +77,13 @@ func (c *Carrier) Set(key, value string) { c.SetEnvFunc(k, value) } -// Keys lists the keys stored in this carrier. -// This returns all the keys in the environment variables. +// Keys lists the normalized keys stored in this carrier. +// This returns all keys from environment variables whose names are already +// normalized. // The first call to [Carrier.Get] or [Carrier.Keys] for a -// given Carrier will read and store the values from the -// environment and all future reads will be from that store. -// The keys are returned in their normalized form. +// given Carrier will read and store the values from environment variables +// whose names are already normalized, and all future reads will be from that +// store. func (c *Carrier) Keys() []string { c.fetch() keys := make([]string, 0, len(c.values)) diff --git a/propagators/envcar/carrier_example_test.go b/propagators/envcar/carrier_example_test.go index 799786faa92..f235fe25b1c 100644 --- a/propagators/envcar/carrier_example_test.go +++ b/propagators/envcar/carrier_example_test.go @@ -34,10 +34,6 @@ func ExampleCarrier_extractFromParent() { fmt.Printf("Trace ID: %s\n", spanCtx.TraceID()) fmt.Printf("Span ID: %s\n", spanCtx.SpanID()) fmt.Printf("Sampled: %t\n", spanCtx.IsSampled()) - // Output: - // Trace ID: 0102030405060708090a0b0c0d0e0f10 - // Span ID: 0102030405060708 - // Sampled: true } // This example is a go program where we have a trace and we'd like to inject it @@ -77,5 +73,4 @@ func ExampleCarrier_childProcess() { return } fmt.Print(string(out)) - // Output: 00-0102030405060708090a0b0c0d0e0f10-0102030405060708-01 } diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index c37e2a7519f..a927ef1f3ba 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -61,19 +61,6 @@ func TestExtractValidTraceContextEnvCarrier(t *testing.T) { Remote: true, }), }, - { - name: "lowercase env names", - envs: map[string]string{ - "traceparent": "00-000000000000007b00000000000001c8-000000000000007b-00", - "tracestate": stateStr, - }, - want: trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: traceID, - SpanID: spanID, - TraceState: state, - Remote: true, - }), - }, } for _, tc := range tests { @@ -88,6 +75,15 @@ func TestExtractValidTraceContextEnvCarrier(t *testing.T) { } } +func TestExtractIgnoresNonNormalizedEnvNames(t *testing.T) { + t.Setenv("traceparent", "00-000000000000007b00000000000001c8-000000000000007b-00") + t.Setenv("tracestate", "key1=value1,key2=value2") + + ctx := prop.Extract(t.Context(), &envcar.Carrier{}) + + assert.False(t, trace.SpanContextFromContext(ctx).IsValid()) +} + func TestInjectTraceContextEnvCarrier(t *testing.T) { stateStr := "key1=value1,key2=value2" state, err := trace.ParseTraceState(stateStr) @@ -147,13 +143,15 @@ func TestInjectTraceContextEnvCarrier(t *testing.T) { } func TestCarrierKeys(t *testing.T) { - t.Setenv("traceparent", "value") + t.Setenv("TRACEPARENT", "value") + t.Setenv("envcar_non_normalized_key", "ignored") c := envcar.Carrier{} keys := c.Keys() assert.Contains(t, keys, "TRACEPARENT") - assert.NotContains(t, keys, "traceparent") + assert.NotContains(t, keys, "envcar_non_normalized_key") + assert.NotContains(t, keys, "ENVCAR_NON_NORMALIZED_KEY") } func TestCarrierSetNilFunc(_ *testing.T) { @@ -162,13 +160,21 @@ func TestCarrierSetNilFunc(_ *testing.T) { } func TestCarrierGetNormalizesKey(t *testing.T) { - t.Setenv("traceparent", "myvalue") + t.Setenv("TRACEPARENT", "myvalue") c := envcar.Carrier{} assert.Equal(t, "myvalue", c.Get("traceparent")) assert.Equal(t, "myvalue", c.Get("TRACEPARENT")) } +func TestCarrierGetIgnoresNonNormalizedEnvNames(t *testing.T) { + t.Setenv("envcar_get_non_normalized_key", "ignored") + + c := envcar.Carrier{} + assert.Empty(t, c.Get("envcar_get_non_normalized_key")) + assert.Empty(t, c.Get("ENVCAR_GET_NON_NORMALIZED_KEY")) +} + func TestCarrierSetUppercasesUnderscoresKey(t *testing.T) { var gotKey string var gotValue string diff --git a/propagators/envcar/doc.go b/propagators/envcar/doc.go index 73d0c22f42c..5eb4750f855 100644 --- a/propagators/envcar/doc.go +++ b/propagators/envcar/doc.go @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 // Package envcar implements the Environment Carrier specification as documented -// here https://opentelemetry.io/docs/specs/otel/context/env-carriers/ +// here: https://opentelemetry.io/docs/specs/otel/context/env-carriers/. package envcar // import "go.opentelemetry.io/contrib/propagators/envcar" diff --git a/propagators/envcar/normalize.go b/propagators/envcar/normalize.go index cdc90445b6e..0a37213150c 100644 --- a/propagators/envcar/normalize.go +++ b/propagators/envcar/normalize.go @@ -46,3 +46,26 @@ func normalize(s string) string { } return string(b) } + +// normalized reports whether s is already a normalized environment variable +// name. +func normalized(s string) bool { + if s == "" { + return true + } + // Normalized names cannot start with a digit; normalize would prepend '_'. + if s[0] >= '0' && s[0] <= '9' { + return false + } + + for _, r := range s { + switch { + // A-Z, 0-9, and _ are already normalized. + case r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '_': + default: + // Lowercase letters and all other characters would be rewritten. + return false + } + } + return true +} diff --git a/propagators/envcar/normalize_test.go b/propagators/envcar/normalize_test.go index 20965c67944..9f8ca8b33ae 100644 --- a/propagators/envcar/normalize_test.go +++ b/propagators/envcar/normalize_test.go @@ -37,6 +37,14 @@ func TestNormalize(t *testing.T) { } } +func TestNormalized(t *testing.T) { + for _, tc := range normalizeCases { + t.Run(tc.in, func(t *testing.T) { + assert.Equal(t, tc.in == tc.want, normalized(tc.in)) + }) + } +} + func BenchmarkNormalize(b *testing.B) { for _, tc := range normalizeCases { b.Run(tc.in, func(b *testing.B) { @@ -47,3 +55,14 @@ func BenchmarkNormalize(b *testing.B) { }) } } + +func BenchmarkNormalized(b *testing.B) { + for _, tc := range normalizeCases { + b.Run(tc.in, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + normalized(tc.in) + } + }) + } +} From 0d68660fe6743abab444b220da1178d15069908e Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Tue, 9 Jun 2026 11:37:02 +0200 Subject: [PATCH 02/22] add PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce8bd38ba32..73023b3ccf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- Ignore non-normalized environment variable names on `Carrier.Get` and `Carrier.Keys` in `go.opentelemetry.io/contrib/propagators/envcar`. +- Ignore non-normalized environment variable names on `Carrier.Get` and `Carrier.Keys` in `go.opentelemetry.io/contrib/propagators/envcar`. (#9112) From a55d64e1f11f5a54036e9f6acfbc188d68672468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Tue, 9 Jun 2026 11:46:15 +0200 Subject: [PATCH 03/22] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- propagators/envcar/carrier_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index a927ef1f3ba..ff509ba4bac 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -76,6 +76,10 @@ func TestExtractValidTraceContextEnvCarrier(t *testing.T) { } func TestExtractIgnoresNonNormalizedEnvNames(t *testing.T) { + // Guard against TRACEPARENT/TRACESTATE being set in the outer test environment. + t.Setenv("TRACEPARENT", "") + t.Setenv("TRACESTATE", "") + t.Setenv("traceparent", "00-000000000000007b00000000000001c8-000000000000007b-00") t.Setenv("tracestate", "key1=value1,key2=value2") From 48860247f0fb073a7d04d2a5bf9100733e783d78 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Tue, 9 Jun 2026 12:35:55 +0200 Subject: [PATCH 04/22] skip some tests on windows --- propagators/envcar/carrier_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index ff509ba4bac..5f27b1e53d3 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -6,6 +6,7 @@ package envcar_test import ( "os" "os/exec" + "runtime" "slices" "strings" "sync" @@ -76,6 +77,10 @@ func TestExtractValidTraceContextEnvCarrier(t *testing.T) { } func TestExtractIgnoresNonNormalizedEnvNames(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows environment variables are case-insensitive, so this test is not applicable.") + } + // Guard against TRACEPARENT/TRACESTATE being set in the outer test environment. t.Setenv("TRACEPARENT", "") t.Setenv("TRACESTATE", "") @@ -172,6 +177,10 @@ func TestCarrierGetNormalizesKey(t *testing.T) { } func TestCarrierGetIgnoresNonNormalizedEnvNames(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows environment variables are case-insensitive, so this test is not applicable.") + } + t.Setenv("envcar_get_non_normalized_key", "ignored") c := envcar.Carrier{} From d74740b6cd3bf716d76c47ac01634efd3ea96a9d Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Tue, 9 Jun 2026 18:51:52 +0200 Subject: [PATCH 05/22] examples to restore the original value when it existed --- propagators/envcar/carrier_example_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/propagators/envcar/carrier_example_test.go b/propagators/envcar/carrier_example_test.go index f235fe25b1c..a0a72954fde 100644 --- a/propagators/envcar/carrier_example_test.go +++ b/propagators/envcar/carrier_example_test.go @@ -20,6 +20,14 @@ import ( func ExampleCarrier_extractFromParent() { // Simulate environment variables set by a parent process. // In practice, these would already be set when this process starts. + orig, ok := os.LookupEnv("TRACEPARENT") + defer func() { + if ok { + _ = os.Setenv("TRACEPARENT", orig) + return + } + _ = os.Unsetenv("TRACEPARENT") + }() _ = os.Setenv("TRACEPARENT", "00-0102030405060708090a0b0c0d0e0f10-0102030405060708-01") // Create a carrier to read trace context from environment variables. @@ -34,6 +42,10 @@ func ExampleCarrier_extractFromParent() { fmt.Printf("Trace ID: %s\n", spanCtx.TraceID()) fmt.Printf("Span ID: %s\n", spanCtx.SpanID()) fmt.Printf("Sampled: %t\n", spanCtx.IsSampled()) + // Output: + // Trace ID: 0102030405060708090a0b0c0d0e0f10 + // Span ID: 0102030405060708 + // Sampled: true } // This example is a go program where we have a trace and we'd like to inject it @@ -73,4 +85,5 @@ func ExampleCarrier_childProcess() { return } fmt.Print(string(out)) + // Output: 00-0102030405060708090a0b0c0d0e0f10-0102030405060708-01 } From ca0cacfb9daae79c96c67fa46358d516d320e97b Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 18 Jun 2026 08:19:55 +0200 Subject: [PATCH 06/22] avoid environment scan in Carrier.Get --- CHANGELOG.md | 2 +- propagators/envcar/carrier.go | 23 +++++++----------- propagators/envcar/carrier_test.go | 38 ++++++++++++++++++------------ 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7722bc13abd..027af96750b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- Ignore non-normalized environment variable names on `Carrier.Get` and `Carrier.Keys` in `go.opentelemetry.io/contrib/propagators/envcar`. (#9112) +- Use direct normalized-key lookups in `Carrier.Get` and ignore non-normalized environment variable names in `Carrier.Keys` in `go.opentelemetry.io/contrib/propagators/envcar`. (#9112) - The `Version()` function in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` has been replaced by `const Version`. (#9076) - Set `error.type` attribute instead of adding `exception` span events in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#8977) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 0445b5ecd55..2d95a708ccd 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -14,8 +14,8 @@ import ( // Carrier is a TextMapCarrier that uses environment variables as a storage // medium for propagated key-value pairs. Keys passed to [Carrier.Get] and // [Carrier.Set] are normalized before lookup or write. Environment variables -// read from the current process are stored only when their names are already -// normalized. +// listed by [Carrier.Keys] are stored only when their names are already +// normalized. [Carrier.Get] reads the normalized key directly. // This is useful for propagating values that are set in the environment // and need to be accessed by different processes or services. // The keys are uppercased to avoid case sensitivity issues across different @@ -38,7 +38,7 @@ type Carrier struct { // Compile time check that Carrier implements the TextMapCarrier. var _ propagation.TextMapCarrier = (*Carrier)(nil) -// fetch runs once on first access, and stores environment variables with +// fetch runs once on first Keys access, and stores environment variables with // already-normalized names in the carrier. func (c *Carrier) fetch() { c.once.Do(func() { @@ -56,13 +56,9 @@ func (c *Carrier) fetch() { } // Get returns the value associated with the normalized key. -// The first call to [Carrier.Get] or [Carrier.Keys] for a -// given Carrier will read and store the values from environment variables -// whose names are already normalized, and all future reads will be from that -// store. -func (c *Carrier) Get(key string) string { - c.fetch() - return c.values[normalize(key)] +// It reads the normalized key directly from the current process environment. +func (*Carrier) Get(key string) string { + return os.Getenv(normalize(key)) } // Set stores the key-value pair in the environment variable. @@ -80,10 +76,9 @@ func (c *Carrier) Set(key, value string) { // Keys lists the normalized keys stored in this carrier. // This returns all keys from environment variables whose names are already // normalized. -// The first call to [Carrier.Get] or [Carrier.Keys] for a -// given Carrier will read and store the values from environment variables -// whose names are already normalized, and all future reads will be from that -// store. +// The first call to [Carrier.Keys] for a given Carrier will read and store the +// values from environment variables whose names are already normalized, and all +// future reads will be from that store. func (c *Carrier) Keys() []string { c.fetch() keys := make([]string, 0, len(c.values)) diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index 5f27b1e53d3..e8630573ac6 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -7,7 +7,6 @@ import ( "os" "os/exec" "runtime" - "slices" "strings" "sync" "testing" @@ -211,6 +210,29 @@ func TestCarrierSetUppercasesUnderscoresKey(t *testing.T) { assert.Equal(t, "🧳", gotValue) } +func TestCarrierGetReadsDirectly(t *testing.T) { + t.Setenv("TRACEPARENT", "myvalue") + c := envcar.Carrier{} + require.Equal(t, "myvalue", c.Get("TRACEPARENT")) + + t.Setenv("TRACEPARENT", "bad") + t.Setenv("ENVCAR_GET_READS_DIRECTLY_NEW", "value") + + assert.Equal(t, "bad", c.Get("TRACEPARENT")) + assert.Contains(t, c.Keys(), "ENVCAR_GET_READS_DIRECTLY_NEW") +} + +func TestCarrierKeysFetchOnce(t *testing.T) { + t.Setenv("TRACEPARENT", "myvalue") + c := envcar.Carrier{} + require.Contains(t, c.Keys(), "TRACEPARENT") + + t.Setenv("ENVCAR_KEYS_FETCH_ONCE_NEW", "value") + + assert.Contains(t, c.Keys(), "TRACEPARENT") + assert.NotContains(t, c.Keys(), "ENVCAR_KEYS_FETCH_ONCE_NEW") +} + func TestConcurrentChildProcesses(t *testing.T) { // Test that concurrent goroutines can each spawn child processes // with their own unique trace context. @@ -279,17 +301,3 @@ func TestConcurrentChildProcesses(t *testing.T) { "goroutine %d: child process received wrong trace context", r.index) } } - -// Ensure Get and Keys to fetch from cache. -func TestCarrierGetFetchOnce(t *testing.T) { - t.Setenv("TRACEPARENT", "myvalue") - c := envcar.Carrier{} - require.Equal(t, "myvalue", c.Get("TRACEPARENT")) - require.NotEqual(t, -1, slices.Index(c.Keys(), "TRACEPARENT")) - t.Setenv("TRACEPARENT", "bad") - t.Setenv("SOMETHINGNEW", "bad") - // Assert a carrier instance reads the env vars only once: - assert.Equal(t, "myvalue", c.Get("TRACEPARENT")) - assert.NotEqual(t, -1, slices.Index(c.Keys(), "TRACEPARENT")) - assert.Equal(t, -1, slices.Index(c.Keys(), "SOMETHINGNEW")) -} From 5b1606fabd376ac0b55b82c225d749205a9144ac Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 18 Jun 2026 08:37:06 +0200 Subject: [PATCH 07/22] add environment carrier operational guidance --- propagators/envcar/carrier_example_test.go | 8 +++++--- propagators/envcar/doc.go | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/propagators/envcar/carrier_example_test.go b/propagators/envcar/carrier_example_test.go index a0a72954fde..a49d4da6389 100644 --- a/propagators/envcar/carrier_example_test.go +++ b/propagators/envcar/carrier_example_test.go @@ -18,8 +18,9 @@ import ( // This example is a go program where the environment variables are carrying the // trace information, and we're going to pick them up into our context. func ExampleCarrier_extractFromParent() { - // Simulate environment variables set by a parent process. - // In practice, these would already be set when this process starts. + // Simulate an environment variable set by a parent process. In practice, + // this would already be set when this process starts, and the application + // would extract context during initialization. orig, ok := os.LookupEnv("TRACEPARENT") defer func() { if ok { @@ -62,6 +63,7 @@ func ExampleCarrier_childProcess() { ctx := trace.ContextWithSpanContext(context.Background(), spanCtx) // Prepare a command that prints the TRACEPARENT environment variable. + // Each child process gets its own copy of the environment. cmd := exec.CommandContext(context.Background(), "printenv", "TRACEPARENT") cmd.Env = os.Environ() @@ -73,7 +75,7 @@ func ExampleCarrier_childProcess() { }, } - // Inject trace context into the child's environment. + // Inject trace context into the child's environment copy. prop := propagation.TraceContext{} prop.Inject(ctx, &carrier) diff --git a/propagators/envcar/doc.go b/propagators/envcar/doc.go index 5eb4750f855..17504883e09 100644 --- a/propagators/envcar/doc.go +++ b/propagators/envcar/doc.go @@ -1,6 +1,17 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -// Package envcar implements the Environment Carrier specification as documented -// here: https://opentelemetry.io/docs/specs/otel/context/env-carriers/. +// Package envcar implements the +// [Environment Variables as Context Propagation Carriers specification]. +// +// Environment variable propagation is intended for process boundaries where +// network protocols are not available, such as batch jobs, CI/CD, and command +// line tools. Treat context-related environment variables as process-startup +// input. +// +// Note that environment variables can be visible to code in the same process +// and, on many systems, to other users or processes with sufficient +// permissions. Do not use this carrier for sensitive context. +// +// [Environment Variables as Context Propagation Carriers specification]: https://opentelemetry.io/docs/specs/otel/context/env-carriers/ package envcar // import "go.opentelemetry.io/contrib/propagators/envcar" From 0fedf62352559778c0e14f5983e11cd783f47b51 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 18 Jun 2026 08:47:35 +0200 Subject: [PATCH 08/22] handle empty env key normalization --- propagators/envcar/normalize.go | 9 +++++---- propagators/envcar/normalize_test.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/propagators/envcar/normalize.go b/propagators/envcar/normalize.go index 0a37213150c..c3d2aeeaa4a 100644 --- a/propagators/envcar/normalize.go +++ b/propagators/envcar/normalize.go @@ -9,13 +9,14 @@ import ( // normalize converts s to a valid POSIX environment variable name. // The conversion rules are: +// - Empty input is converted to _. // - A–Z, 0–9, and _ are kept as-is. // - a–z are uppercased. // - All other characters are replaced with _. // - If the result would start with a digit, an underscore is prepended. func normalize(s string) string { if s == "" { - return "" + return "_" } // Pre-allocate the exact output length. If the first byte is a digit, @@ -47,11 +48,11 @@ func normalize(s string) string { return string(b) } -// normalized reports whether s is already a normalized environment variable -// name. +// normalized reports whether s is already a non-empty normalized environment +// variable name. func normalized(s string) bool { if s == "" { - return true + return false } // Normalized names cannot start with a digit; normalize would prepend '_'. if s[0] >= '0' && s[0] <= '9' { diff --git a/propagators/envcar/normalize_test.go b/propagators/envcar/normalize_test.go index 9f8ca8b33ae..0b1f031763a 100644 --- a/propagators/envcar/normalize_test.go +++ b/propagators/envcar/normalize_test.go @@ -12,7 +12,7 @@ import ( var normalizeCases = []struct { in, want string }{ - {"", ""}, + {"", "_"}, {"ABC", "ABC"}, {"abc", "ABC"}, {"01239", "_01239"}, From f57490e4040338cb5ade486b040be6a183dda02b Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 18 Jun 2026 08:58:55 +0200 Subject: [PATCH 09/22] isolate non-normalized env tests --- propagators/envcar/carrier_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index e8630573ac6..0b46d578e1b 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -153,6 +153,7 @@ func TestInjectTraceContextEnvCarrier(t *testing.T) { func TestCarrierKeys(t *testing.T) { t.Setenv("TRACEPARENT", "value") t.Setenv("envcar_non_normalized_key", "ignored") + unsetenv(t, "ENVCAR_NON_NORMALIZED_KEY") c := envcar.Carrier{} keys := c.Keys() @@ -181,6 +182,7 @@ func TestCarrierGetIgnoresNonNormalizedEnvNames(t *testing.T) { } t.Setenv("envcar_get_non_normalized_key", "ignored") + unsetenv(t, "ENVCAR_GET_NON_NORMALIZED_KEY") c := envcar.Carrier{} assert.Empty(t, c.Get("envcar_get_non_normalized_key")) @@ -233,6 +235,20 @@ func TestCarrierKeysFetchOnce(t *testing.T) { assert.NotContains(t, c.Keys(), "ENVCAR_KEYS_FETCH_ONCE_NEW") } +func unsetenv(t *testing.T, key string) { + t.Helper() + + value, ok := os.LookupEnv(key) + require.NoError(t, os.Unsetenv(key)) + t.Cleanup(func() { + if ok { + require.NoError(t, os.Setenv(key, value)) //nolint:usetesting // t.Setenv cannot restore after an explicit unset. + return + } + require.NoError(t, os.Unsetenv(key)) + }) +} + func TestConcurrentChildProcesses(t *testing.T) { // Test that concurrent goroutines can each spawn child processes // with their own unique trace context. From fd398ac66899bbde4052dfe9dd16bef4f651f787 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 08:33:10 +0200 Subject: [PATCH 10/22] optimize caching --- propagators/envcar/carrier.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 2d95a708ccd..426114738a5 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -31,7 +31,7 @@ type Carrier struct { // Usually, you want to set the environment variables for processes // that are spawned by the current process. SetEnvFunc func(key, value string) - values map[string]string + values map[string]struct{} once sync.Once } @@ -43,14 +43,14 @@ var _ propagation.TextMapCarrier = (*Carrier)(nil) func (c *Carrier) fetch() { c.once.Do(func() { environ := os.Environ() - c.values = make(map[string]string, len(environ)) + c.values = make(map[string]struct{}, len(environ)) for _, kv := range environ { kvPair := strings.SplitN(kv, "=", 2) key := kvPair[0] if !normalized(key) { continue } - c.values[key] = kvPair[1] + c.values[key] = struct{}{} } }) } From 8c69898425e973abf2bc986d63b5e46257da26f2 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 08:39:22 +0200 Subject: [PATCH 11/22] rename cache --- propagators/envcar/carrier.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 426114738a5..2b09ab685af 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -31,7 +31,7 @@ type Carrier struct { // Usually, you want to set the environment variables for processes // that are spawned by the current process. SetEnvFunc func(key, value string) - values map[string]struct{} + keys map[string]struct{} once sync.Once } @@ -43,14 +43,14 @@ var _ propagation.TextMapCarrier = (*Carrier)(nil) func (c *Carrier) fetch() { c.once.Do(func() { environ := os.Environ() - c.values = make(map[string]struct{}, len(environ)) + c.keys = make(map[string]struct{}, len(environ)) for _, kv := range environ { kvPair := strings.SplitN(kv, "=", 2) key := kvPair[0] if !normalized(key) { continue } - c.values[key] = struct{}{} + c.keys[key] = struct{}{} } }) } @@ -81,8 +81,8 @@ func (c *Carrier) Set(key, value string) { // future reads will be from that store. func (c *Carrier) Keys() []string { c.fetch() - keys := make([]string, 0, len(c.values)) - for key := range c.values { + keys := make([]string, 0, len(c.keys)) + for key := range c.keys { keys = append(keys, key) } return keys From 39c204ef42c7508c3c50176162fcfc39ac4fb674 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 08:42:19 +0200 Subject: [PATCH 12/22] update GoDoc --- propagators/envcar/carrier.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 2b09ab685af..398f3b9c004 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -13,9 +13,9 @@ import ( // Carrier is a TextMapCarrier that uses environment variables as a storage // medium for propagated key-value pairs. Keys passed to [Carrier.Get] and -// [Carrier.Set] are normalized before lookup or write. Environment variables -// listed by [Carrier.Keys] are stored only when their names are already -// normalized. [Carrier.Get] reads the normalized key directly. +// [Carrier.Set] are normalized before lookup or write. [Carrier.Keys] only +// lists environment variable names that are already normalized. [Carrier.Get] +// reads the normalized key directly. // This is useful for propagating values that are set in the environment // and need to be accessed by different processes or services. // The keys are uppercased to avoid case sensitivity issues across different @@ -38,8 +38,8 @@ type Carrier struct { // Compile time check that Carrier implements the TextMapCarrier. var _ propagation.TextMapCarrier = (*Carrier)(nil) -// fetch runs once on first Keys access, and stores environment variables with -// already-normalized names in the carrier. +// fetch runs once on first Keys access, and stores the names of environment +// variables that are already normalized. func (c *Carrier) fetch() { c.once.Do(func() { environ := os.Environ() @@ -76,9 +76,9 @@ func (c *Carrier) Set(key, value string) { // Keys lists the normalized keys stored in this carrier. // This returns all keys from environment variables whose names are already // normalized. -// The first call to [Carrier.Keys] for a given Carrier will read and store the -// values from environment variables whose names are already normalized, and all -// future reads will be from that store. +// The first call to [Carrier.Keys] for a given Carrier will read the current +// process environment and store the already-normalized environment variable +// names. Future Keys reads return that cached name set. func (c *Carrier) Keys() []string { c.fetch() keys := make([]string, 0, len(c.keys)) From c48452538ea12a3122702e67072f9f19f4f5f40f Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 09:04:16 +0200 Subject: [PATCH 13/22] refactor tests --- propagators/envcar/carrier_test.go | 42 +++++++++++++----------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index 0b46d578e1b..5caabfcb0ba 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -80,10 +80,8 @@ func TestExtractIgnoresNonNormalizedEnvNames(t *testing.T) { t.Skip("Windows environment variables are case-insensitive, so this test is not applicable.") } - // Guard against TRACEPARENT/TRACESTATE being set in the outer test environment. - t.Setenv("TRACEPARENT", "") - t.Setenv("TRACESTATE", "") - + unsetenv(t, "TRACEPARENT") + unsetenv(t, "TRACESTATE") t.Setenv("traceparent", "00-000000000000007b00000000000001c8-000000000000007b-00") t.Setenv("tracestate", "key1=value1,key2=value2") @@ -152,8 +150,8 @@ func TestInjectTraceContextEnvCarrier(t *testing.T) { func TestCarrierKeys(t *testing.T) { t.Setenv("TRACEPARENT", "value") - t.Setenv("envcar_non_normalized_key", "ignored") unsetenv(t, "ENVCAR_NON_NORMALIZED_KEY") + t.Setenv("envcar-non-normalized-key", "ignored") c := envcar.Carrier{} keys := c.Keys() @@ -177,12 +175,8 @@ func TestCarrierGetNormalizesKey(t *testing.T) { } func TestCarrierGetIgnoresNonNormalizedEnvNames(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("Windows environment variables are case-insensitive, so this test is not applicable.") - } - - t.Setenv("envcar_get_non_normalized_key", "ignored") unsetenv(t, "ENVCAR_GET_NON_NORMALIZED_KEY") + t.Setenv("envcar-get-non-normalized-key", "ignored") c := envcar.Carrier{} assert.Empty(t, c.Get("envcar_get_non_normalized_key")) @@ -235,20 +229,6 @@ func TestCarrierKeysFetchOnce(t *testing.T) { assert.NotContains(t, c.Keys(), "ENVCAR_KEYS_FETCH_ONCE_NEW") } -func unsetenv(t *testing.T, key string) { - t.Helper() - - value, ok := os.LookupEnv(key) - require.NoError(t, os.Unsetenv(key)) - t.Cleanup(func() { - if ok { - require.NoError(t, os.Setenv(key, value)) //nolint:usetesting // t.Setenv cannot restore after an explicit unset. - return - } - require.NoError(t, os.Unsetenv(key)) - }) -} - func TestConcurrentChildProcesses(t *testing.T) { // Test that concurrent goroutines can each spawn child processes // with their own unique trace context. @@ -317,3 +297,17 @@ func TestConcurrentChildProcesses(t *testing.T) { "goroutine %d: child process received wrong trace context", r.index) } } + +func unsetenv(t *testing.T, key string) { + t.Helper() + + value, ok := os.LookupEnv(key) + require.NoError(t, os.Unsetenv(key)) + t.Cleanup(func() { + if ok { + require.NoError(t, os.Setenv(key, value)) //nolint:usetesting // t.Setenv cannot restore after an explicit unset. + return + } + require.NoError(t, os.Unsetenv(key)) + }) +} From 591c0c3a004b6ccdce6ef9b14694ee70e16bb758 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 09:13:18 +0200 Subject: [PATCH 14/22] fix TestCarrierKeys --- propagators/envcar/carrier_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index 5caabfcb0ba..51e48b517bf 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -157,7 +157,7 @@ func TestCarrierKeys(t *testing.T) { keys := c.Keys() assert.Contains(t, keys, "TRACEPARENT") - assert.NotContains(t, keys, "envcar_non_normalized_key") + assert.NotContains(t, keys, "envcar-non-normalized-key") assert.NotContains(t, keys, "ENVCAR_NON_NORMALIZED_KEY") } From 060e0d3763ec0b474e16b33316eac2de1d3ee355 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 09:25:17 +0200 Subject: [PATCH 15/22] optimize fetch --- propagators/envcar/carrier.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 398f3b9c004..5a87030a89c 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -45,8 +45,7 @@ func (c *Carrier) fetch() { environ := os.Environ() c.keys = make(map[string]struct{}, len(environ)) for _, kv := range environ { - kvPair := strings.SplitN(kv, "=", 2) - key := kvPair[0] + key, _, _ := strings.Cut(kv, "=") if !normalized(key) { continue } From 6a2ef5555a1819fae070b490ab8da076e02021ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Sat, 20 Jun 2026 09:33:38 +0200 Subject: [PATCH 16/22] add a note regarding Windows Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- propagators/envcar/carrier.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 5a87030a89c..491641bd01d 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -14,8 +14,8 @@ import ( // Carrier is a TextMapCarrier that uses environment variables as a storage // medium for propagated key-value pairs. Keys passed to [Carrier.Get] and // [Carrier.Set] are normalized before lookup or write. [Carrier.Keys] only -// lists environment variable names that are already normalized. [Carrier.Get] -// reads the normalized key directly. +// lists environment variable names that are already normalized; on platforms +// with case-insensitive environment variable lookup (e.g. Windows) [Carrier.Get] may still match non-normalized names. // This is useful for propagating values that are set in the environment // and need to be accessed by different processes or services. // The keys are uppercased to avoid case sensitivity issues across different From aeaccb08719bfc01986cf8b781855b2981756528 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 20:04:18 +0200 Subject: [PATCH 17/22] Keys to read directly from environ --- CHANGELOG.md | 2 +- propagators/envcar/carrier.go | 35 ++++++++---------------------- propagators/envcar/carrier_test.go | 4 ++-- 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 027af96750b..be4257d2d09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- Use direct normalized-key lookups in `Carrier.Get` and ignore non-normalized environment variable names in `Carrier.Keys` in `go.opentelemetry.io/contrib/propagators/envcar`. (#9112) +- Use direct normalized-key lookups in `Carrier.Get` and `Carrier.Keys` in `go.opentelemetry.io/contrib/propagators/envcar`. (#9112) - The `Version()` function in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` has been replaced by `const Version`. (#9076) - Set `error.type` attribute instead of adding `exception` span events in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#8977) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 491641bd01d..7320e987b5e 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -6,7 +6,6 @@ package envcar // import "go.opentelemetry.io/contrib/propagators/envcar" import ( "os" "strings" - "sync" "go.opentelemetry.io/otel/propagation" ) @@ -31,29 +30,11 @@ type Carrier struct { // Usually, you want to set the environment variables for processes // that are spawned by the current process. SetEnvFunc func(key, value string) - keys map[string]struct{} - once sync.Once } // Compile time check that Carrier implements the TextMapCarrier. var _ propagation.TextMapCarrier = (*Carrier)(nil) -// fetch runs once on first Keys access, and stores the names of environment -// variables that are already normalized. -func (c *Carrier) fetch() { - c.once.Do(func() { - environ := os.Environ() - c.keys = make(map[string]struct{}, len(environ)) - for _, kv := range environ { - key, _, _ := strings.Cut(kv, "=") - if !normalized(key) { - continue - } - c.keys[key] = struct{}{} - } - }) -} - // Get returns the value associated with the normalized key. // It reads the normalized key directly from the current process environment. func (*Carrier) Get(key string) string { @@ -75,13 +56,15 @@ func (c *Carrier) Set(key, value string) { // Keys lists the normalized keys stored in this carrier. // This returns all keys from environment variables whose names are already // normalized. -// The first call to [Carrier.Keys] for a given Carrier will read the current -// process environment and store the already-normalized environment variable -// names. Future Keys reads return that cached name set. -func (c *Carrier) Keys() []string { - c.fetch() - keys := make([]string, 0, len(c.keys)) - for key := range c.keys { +// It reads directly from the current process environment. +func (*Carrier) Keys() []string { + environ := os.Environ() + keys := make([]string, 0, len(environ)) + for _, kv := range environ { + key, _, _ := strings.Cut(kv, "=") + if !normalized(key) { + continue + } keys = append(keys, key) } return keys diff --git a/propagators/envcar/carrier_test.go b/propagators/envcar/carrier_test.go index 51e48b517bf..938314dd95a 100644 --- a/propagators/envcar/carrier_test.go +++ b/propagators/envcar/carrier_test.go @@ -218,7 +218,7 @@ func TestCarrierGetReadsDirectly(t *testing.T) { assert.Contains(t, c.Keys(), "ENVCAR_GET_READS_DIRECTLY_NEW") } -func TestCarrierKeysFetchOnce(t *testing.T) { +func TestCarrierKeysReadsDirectly(t *testing.T) { t.Setenv("TRACEPARENT", "myvalue") c := envcar.Carrier{} require.Contains(t, c.Keys(), "TRACEPARENT") @@ -226,7 +226,7 @@ func TestCarrierKeysFetchOnce(t *testing.T) { t.Setenv("ENVCAR_KEYS_FETCH_ONCE_NEW", "value") assert.Contains(t, c.Keys(), "TRACEPARENT") - assert.NotContains(t, c.Keys(), "ENVCAR_KEYS_FETCH_ONCE_NEW") + assert.Contains(t, c.Keys(), "ENVCAR_KEYS_FETCH_ONCE_NEW") } func TestConcurrentChildProcesses(t *testing.T) { From ef3ca092ca04a0c708c93506ae624c708c18b9bd Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 20:15:58 +0200 Subject: [PATCH 18/22] note that applications should extract context during startup --- propagators/envcar/carrier_example_test.go | 3 ++- propagators/envcar/doc.go | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/propagators/envcar/carrier_example_test.go b/propagators/envcar/carrier_example_test.go index a49d4da6389..5d4f2be50e2 100644 --- a/propagators/envcar/carrier_example_test.go +++ b/propagators/envcar/carrier_example_test.go @@ -38,7 +38,8 @@ func ExampleCarrier_extractFromParent() { prop := propagation.TraceContext{} ctx := prop.Extract(context.Background(), &carrier) - // The context now contains the span context from the parent. + // The context now contains the span context from the parent. Keep and pass + // this context instead of repeatedly extracting from the environment. spanCtx := trace.SpanContextFromContext(ctx) fmt.Printf("Trace ID: %s\n", spanCtx.TraceID()) fmt.Printf("Span ID: %s\n", spanCtx.SpanID()) diff --git a/propagators/envcar/doc.go b/propagators/envcar/doc.go index 17504883e09..7c4ab2bf3f0 100644 --- a/propagators/envcar/doc.go +++ b/propagators/envcar/doc.go @@ -9,6 +9,11 @@ // line tools. Treat context-related environment variables as process-startup // input. // +// Applications should extract context from the environment once during startup +// and then use the extracted context. Repeated extraction uses the process +// environment as a live parent-context source and may observe later environment +// variable changes. +// // Note that environment variables can be visible to code in the same process // and, on many systems, to other users or processes with sufficient // permissions. Do not use this carrier for sensitive context. From 8da7802b8bf5570f0d59b9f89807769fe27a5e23 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 21:11:58 +0200 Subject: [PATCH 19/22] improve GoDoc --- propagators/envcar/carrier.go | 55 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/propagators/envcar/carrier.go b/propagators/envcar/carrier.go index 7320e987b5e..9f3a0631978 100644 --- a/propagators/envcar/carrier.go +++ b/propagators/envcar/carrier.go @@ -10,40 +10,48 @@ import ( "go.opentelemetry.io/otel/propagation" ) -// Carrier is a TextMapCarrier that uses environment variables as a storage -// medium for propagated key-value pairs. Keys passed to [Carrier.Get] and -// [Carrier.Set] are normalized before lookup or write. [Carrier.Keys] only -// lists environment variable names that are already normalized; on platforms -// with case-insensitive environment variable lookup (e.g. Windows) [Carrier.Get] may still match non-normalized names. -// This is useful for propagating values that are set in the environment -// and need to be accessed by different processes or services. -// The keys are uppercased to avoid case sensitivity issues across different -// operating systems and environments. +// Carrier is a [propagation.TextMapCarrier] for environment variables. // -// If you do not set SetEnvFunc, [Carrier.Set] will do nothing. -// Using [os.Setenv] here is discouraged as the environment should -// be immutable: -// https://opentelemetry.io/docs/specs/otel/context/env-carriers/#environment-variable-immutability +// [Carrier.Get] and [Carrier.Keys] read from the current process environment. +// [Carrier.Get] normalizes the key before lookup. [Carrier.Keys] only lists +// environment variable names that are already normalized. +// +// [Carrier.Set] writes through [Carrier.SetEnvFunc] with a normalized key. If +// SetEnvFunc is nil, [Carrier.Set] does nothing. This lets injection target the +// environment of a child process without mutating the current process +// environment. Using [os.Setenv] as SetEnvFunc is discouraged because +// applications should not modify their own context-related environment +// variables. +// +// Key name normalization is defined by the [OpenTelemetry specification]. +// +// [OpenTelemetry specification]: https://opentelemetry.io/docs/specs/otel/context/env-carriers/#key-name-normalization type Carrier struct { - // SetEnvFunc is the function that sets the environment variable. + // SetEnvFunc sets an environment variable for injected context. // [Carrier.Set] calls SetEnvFunc with a normalized key. - // Usually, you want to set the environment variables for processes - // that are spawned by the current process. + // + // Set this to update the environment that will be passed to a child + // process. Leave it nil for an extract-only carrier. SetEnvFunc func(key, value string) } // Compile time check that Carrier implements the TextMapCarrier. var _ propagation.TextMapCarrier = (*Carrier)(nil) -// Get returns the value associated with the normalized key. -// It reads the normalized key directly from the current process environment. +// Get normalizes key and returns the corresponding value from the current +// process environment. It returns an empty string if the normalized +// environment variable is unset or set to an empty value. +// +// On platforms with case-insensitive environment lookup, such as Windows, the +// lookup may match an environment variable whose name differs from the +// normalized key only by case. func (*Carrier) Get(key string) string { return os.Getenv(normalize(key)) } -// Set stores the key-value pair in the environment variable. -// The key is normalized before being used to set the -// environment variable. +// Set stores the key-value pair by calling [Carrier.SetEnvFunc]. +// The key is normalized before SetEnvFunc is called. +// // If SetEnvFunc is not set, this method does nothing. func (c *Carrier) Set(key, value string) { if c.SetEnvFunc == nil { @@ -53,10 +61,7 @@ func (c *Carrier) Set(key, value string) { c.SetEnvFunc(k, value) } -// Keys lists the normalized keys stored in this carrier. -// This returns all keys from environment variables whose names are already -// normalized. -// It reads directly from the current process environment. +// Keys returns normalized environment variable names from the current process. func (*Carrier) Keys() []string { environ := os.Environ() keys := make([]string, 0, len(environ)) From 19c35a09ec2a97f9d7667b2042eecc03bb2f1608 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 21:25:20 +0200 Subject: [PATCH 20/22] refactor examples --- propagators/envcar/carrier_example_test.go | 30 +++++++++++++++------- propagators/envcar/go.mod | 9 +++++-- propagators/envcar/go.sum | 17 +++++++----- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/propagators/envcar/carrier_example_test.go b/propagators/envcar/carrier_example_test.go index 5d4f2be50e2..bc079a1fadb 100644 --- a/propagators/envcar/carrier_example_test.go +++ b/propagators/envcar/carrier_example_test.go @@ -10,6 +10,7 @@ import ( "os/exec" "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/propagators/envcar" @@ -53,15 +54,14 @@ func ExampleCarrier_extractFromParent() { // This example is a go program where we have a trace and we'd like to inject it // into a command we're going to run. func ExampleCarrier_childProcess() { - // Create a span context with a known trace ID. - traceID := trace.TraceID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10} - spanID := trace.SpanID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - spanCtx := trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: traceID, - SpanID: spanID, - TraceFlags: trace.FlagsSampled, - }) - ctx := trace.ContextWithSpanContext(context.Background(), spanCtx) + // Create a tracer provider that starts spans with known IDs. + tp := sdktrace.NewTracerProvider(sdktrace.WithIDGenerator(fixedIDGenerator{})) + defer func() { + _ = tp.Shutdown(context.Background()) + }() + + ctx, span := tp.Tracer("example").Start(context.Background(), "parent") + defer span.End() // Prepare a command that prints the TRACEPARENT environment variable. // Each child process gets its own copy of the environment. @@ -90,3 +90,15 @@ func ExampleCarrier_childProcess() { fmt.Print(string(out)) // Output: 00-0102030405060708090a0b0c0d0e0f10-0102030405060708-01 } + +type fixedIDGenerator struct{} + +func (fixedIDGenerator) NewIDs(context.Context) (trace.TraceID, trace.SpanID) { + traceID := trace.TraceID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10} + spanID := trace.SpanID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + return traceID, spanID +} + +func (fixedIDGenerator) NewSpanID(context.Context, trace.TraceID) trace.SpanID { + return trace.SpanID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} +} diff --git a/propagators/envcar/go.mod b/propagators/envcar/go.mod index 8038bb637cb..dfa27edc1dd 100644 --- a/propagators/envcar/go.mod +++ b/propagators/envcar/go.mod @@ -5,15 +5,20 @@ go 1.25.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.44.0 + go.opentelemetry.io/otel/sdk v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.15.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/propagators/envcar/go.sum b/propagators/envcar/go.sum index e6943105703..6334979efc9 100644 --- a/propagators/envcar/go.sum +++ b/propagators/envcar/go.sum @@ -1,25 +1,22 @@ 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc= github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -30,8 +27,16 @@ go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 9dcb3ef80d821806ee4bad4e112d2308454473d2 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Sat, 20 Jun 2026 21:26:58 +0200 Subject: [PATCH 21/22] improve example comments --- propagators/envcar/carrier_example_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/propagators/envcar/carrier_example_test.go b/propagators/envcar/carrier_example_test.go index bc079a1fadb..9c4ca0369f2 100644 --- a/propagators/envcar/carrier_example_test.go +++ b/propagators/envcar/carrier_example_test.go @@ -16,8 +16,8 @@ import ( "go.opentelemetry.io/contrib/propagators/envcar" ) -// This example is a go program where the environment variables are carrying the -// trace information, and we're going to pick them up into our context. +// An example where the environment variables are carrying the trace +// information, and we're going to pick them up into our context. func ExampleCarrier_extractFromParent() { // Simulate an environment variable set by a parent process. In practice, // this would already be set when this process starts, and the application @@ -51,8 +51,8 @@ func ExampleCarrier_extractFromParent() { // Sampled: true } -// This example is a go program where we have a trace and we'd like to inject it -// into a command we're going to run. +// An example where we have a trace and we'd like to inject it into a command +// we're going to run. func ExampleCarrier_childProcess() { // Create a tracer provider that starts spans with known IDs. tp := sdktrace.NewTracerProvider(sdktrace.WithIDGenerator(fixedIDGenerator{})) From f15acc3594013c97fad75e9a0295fb2bde4252ed Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 25 Jun 2026 13:57:38 +0200 Subject: [PATCH 22/22] do not use SDK in ExampleCarrier_childProcess --- propagators/envcar/carrier_example_test.go | 30 +++++++--------------- propagators/envcar/go.mod | 9 ++----- propagators/envcar/go.sum | 17 +++++------- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/propagators/envcar/carrier_example_test.go b/propagators/envcar/carrier_example_test.go index 9c4ca0369f2..14210f97446 100644 --- a/propagators/envcar/carrier_example_test.go +++ b/propagators/envcar/carrier_example_test.go @@ -10,7 +10,6 @@ import ( "os/exec" "go.opentelemetry.io/otel/propagation" - sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/propagators/envcar" @@ -54,14 +53,15 @@ func ExampleCarrier_extractFromParent() { // An example where we have a trace and we'd like to inject it into a command // we're going to run. func ExampleCarrier_childProcess() { - // Create a tracer provider that starts spans with known IDs. - tp := sdktrace.NewTracerProvider(sdktrace.WithIDGenerator(fixedIDGenerator{})) - defer func() { - _ = tp.Shutdown(context.Background()) - }() - - ctx, span := tp.Tracer("example").Start(context.Background(), "parent") - defer span.End() + // Create a span context with a known trace ID. + traceID := trace.TraceID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10} + spanID := trace.SpanID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + spanCtx := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }) + ctx := trace.ContextWithSpanContext(context.Background(), spanCtx) // Prepare a command that prints the TRACEPARENT environment variable. // Each child process gets its own copy of the environment. @@ -90,15 +90,3 @@ func ExampleCarrier_childProcess() { fmt.Print(string(out)) // Output: 00-0102030405060708090a0b0c0d0e0f10-0102030405060708-01 } - -type fixedIDGenerator struct{} - -func (fixedIDGenerator) NewIDs(context.Context) (trace.TraceID, trace.SpanID) { - traceID := trace.TraceID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10} - spanID := trace.SpanID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - return traceID, spanID -} - -func (fixedIDGenerator) NewSpanID(context.Context, trace.TraceID) trace.SpanID { - return trace.SpanID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} -} diff --git a/propagators/envcar/go.mod b/propagators/envcar/go.mod index dfa27edc1dd..8038bb637cb 100644 --- a/propagators/envcar/go.mod +++ b/propagators/envcar/go.mod @@ -5,20 +5,15 @@ go 1.25.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.44.0 - go.opentelemetry.io/otel/sdk v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.15.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - golang.org/x/sys v0.45.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/propagators/envcar/go.sum b/propagators/envcar/go.sum index 6334979efc9..e6943105703 100644 --- a/propagators/envcar/go.sum +++ b/propagators/envcar/go.sum @@ -1,22 +1,25 @@ 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc= github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -27,16 +30,8 @@ go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=