From 8111c341677efbc0b9ce685deda720678c770b19 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 15 May 2025 11:39:57 +0200 Subject: [PATCH 1/6] propagation: add EnvCarrier --- propagation/env.go | 43 +++++++++++++++++++++++++++ propagation/env_test.go | 64 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 propagation/env.go create mode 100644 propagation/env_test.go diff --git a/propagation/env.go b/propagation/env.go new file mode 100644 index 00000000000..3e93b437748 --- /dev/null +++ b/propagation/env.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package propagation // import "go.opentelemetry.io/otel/propagation" + +import ( + "os" + "strings" +) + +// EnvCarrier is a TextMapCarrier that uses the environment variables as a +// storage medium for propagated key-value pairs. The keys are uppercased +// before being used to access the environment variables. +// 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. +type EnvCarrier struct{} + +var _ TextMapCarrier = EnvCarrier{} + +// Get returns the value associated with the passed key. +// The key is uppercased before being used to access the environment variable. +func (EnvCarrier) Get(key string) string { + k := strings.ToUpper(key) + return os.Getenv(k) +} + +// Set stores the key-value pair in the environment variable. +// The key is uppercased before being used to set the environment variable. +func (EnvCarrier) Set(key, value string) { + k := strings.ToUpper(key) + os.Setenv(k, value) +} + +// Keys lists the keys stored in this carrier. +// This method is not implemented for EnvCarrier as it is not possible to +// list all environment variables in a portable way. +func (EnvCarrier) Keys() []string { + // I don't know why TextMapCarrier even has a Keys method. + // It looks like it was some mistake in the original design. + return nil +} diff --git a/propagation/env_test.go b/propagation/env_test.go new file mode 100644 index 00000000000..6ea62481e20 --- /dev/null +++ b/propagation/env_test.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package propagation_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +func TestExtractValidTraceContextFromEnv(t *testing.T) { + stateStr := "key1=value1,key2=value2" + state, err := trace.ParseTraceState(stateStr) + require.NoError(t, err) + + tests := []struct { + name string + envs map[string]string + want trace.SpanContext + }{ + { + name: "sampled", + envs: map[string]string{ + "TRACEPARENT": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + }, + want: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + Remote: true, + }), + }, + { + name: "valid tracestate", + envs: map[string]string{ + "TRACEPARENT": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", + "TRACESTATE": stateStr, + }, + want: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceState: state, + Remote: true, + }), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + for k, v := range tc.envs { + t.Setenv(k, v) + } + ctx = prop.Extract(ctx, propagation.EnvCarrier{}) + assert.Equal(t, tc.want, trace.SpanContextFromContext(ctx)) + }) + } +} From b64493dff6ef556387001d0736332a2eacf315c1 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 15 May 2025 11:43:30 +0200 Subject: [PATCH 2/6] ignore os.Setenv error --- propagation/env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/propagation/env.go b/propagation/env.go index 3e93b437748..49be99eb562 100644 --- a/propagation/env.go +++ b/propagation/env.go @@ -30,7 +30,7 @@ func (EnvCarrier) Get(key string) string { // The key is uppercased before being used to set the environment variable. func (EnvCarrier) Set(key, value string) { k := strings.ToUpper(key) - os.Setenv(k, value) + _ = os.Setenv(k, value) } // Keys lists the keys stored in this carrier. From 24993b41101503570b93dfdcb294d504cbd20331 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 15 May 2025 12:14:58 +0200 Subject: [PATCH 3/6] SetEnvFunc --- propagation/env.go | 15 ++++++++-- propagation/env_test.go | 63 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/propagation/env.go b/propagation/env.go index 49be99eb562..d018015195c 100644 --- a/propagation/env.go +++ b/propagation/env.go @@ -15,7 +15,13 @@ import ( // 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. -type EnvCarrier struct{} +type EnvCarrier struct { + // SetEnvFunc is a function that sets the environment variable. + // Usually, you want to set the environment variable for processes + // that are spawned by the current process. + // By default implementation, it does nothing. + SetEnvFunc func(key, value string) error +} var _ TextMapCarrier = EnvCarrier{} @@ -28,9 +34,12 @@ func (EnvCarrier) Get(key string) string { // Set stores the key-value pair in the environment variable. // The key is uppercased before being used to set the environment variable. -func (EnvCarrier) Set(key, value string) { +func (e EnvCarrier) Set(key, value string) { + if e.SetEnvFunc == nil { + return + } k := strings.ToUpper(key) - _ = os.Setenv(k, value) + _ = e.SetEnvFunc(k, value) } // Keys lists the keys stored in this carrier. diff --git a/propagation/env_test.go b/propagation/env_test.go index 6ea62481e20..761da6ad7ea 100644 --- a/propagation/env_test.go +++ b/propagation/env_test.go @@ -5,6 +5,7 @@ package propagation_test import ( "context" + "os" "testing" "github.com/stretchr/testify/assert" @@ -14,7 +15,7 @@ import ( "go.opentelemetry.io/otel/trace" ) -func TestExtractValidTraceContextFromEnv(t *testing.T) { +func TestExtractValidTraceContextEnvCarrier(t *testing.T) { stateStr := "key1=value1,key2=value2" state, err := trace.ParseTraceState(stateStr) require.NoError(t, err) @@ -62,3 +63,63 @@ func TestExtractValidTraceContextFromEnv(t *testing.T) { }) } } + +func TestInjectTraceContextEnvCarrier(t *testing.T) { + stateStr := "key1=value1,key2=value2" + state, err := trace.ParseTraceState(stateStr) + require.NoError(t, err) + + tests := []struct { + name string + want map[string]string + sc trace.SpanContext + }{ + { + name: "sampled", + want: map[string]string{ + "TRACEPARENT": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + }, + sc: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + Remote: true, + }), + }, + { + name: "with tracestate", + want: map[string]string{ + "TRACEPARENT": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", + "TRACESTATE": stateStr, + }, + sc: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceState: state, + Remote: true, + }), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + ctx = trace.ContextWithRemoteSpanContext(ctx, tc.sc) + c := propagation.EnvCarrier{ + SetEnvFunc: func(key, value string) error { + t.Setenv(key, value) + return nil + }, + } + + prop.Inject(ctx, c) + + for k, v := range tc.want { + if got := os.Getenv(k); got != v { + t.Errorf("got %s=%s, want %s=%s", k, got, k, v) + } + + } + }) + } +} From d3f4b2474bd832053870e012369a83b1850aed4a Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 15 May 2025 12:17:01 +0200 Subject: [PATCH 4/6] fix lint --- propagation/env_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/propagation/env_test.go b/propagation/env_test.go index 761da6ad7ea..853edabe1da 100644 --- a/propagation/env_test.go +++ b/propagation/env_test.go @@ -118,7 +118,6 @@ func TestInjectTraceContextEnvCarrier(t *testing.T) { if got := os.Getenv(k); got != v { t.Errorf("got %s=%s, want %s=%s", k, got, k, v) } - } }) } From 3089dec235847112293a4f0ac7a35794d5831164 Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Thu, 15 May 2025 12:19:11 +0200 Subject: [PATCH 5/6] Add clairifying comment --- propagation/env.go | 1 + 1 file changed, 1 insertion(+) diff --git a/propagation/env.go b/propagation/env.go index d018015195c..610c26e30ab 100644 --- a/propagation/env.go +++ b/propagation/env.go @@ -34,6 +34,7 @@ func (EnvCarrier) Get(key string) string { // Set stores the key-value pair in the environment variable. // The key is uppercased before being used to set the environment variable. +// If SetEnvFunc is not set, this method does nothing. func (e EnvCarrier) Set(key, value string) { if e.SetEnvFunc == nil { return From c34667456aed15126e4d68c9354ff1251759909c Mon Sep 17 00:00:00 2001 From: Robert Pajak Date: Fri, 23 May 2025 10:33:28 +0200 Subject: [PATCH 6/6] Implement EnvCarrier.Keys --- propagation/env.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/propagation/env.go b/propagation/env.go index 610c26e30ab..b8de0058171 100644 --- a/propagation/env.go +++ b/propagation/env.go @@ -44,10 +44,15 @@ func (e EnvCarrier) Set(key, value string) { } // Keys lists the keys stored in this carrier. -// This method is not implemented for EnvCarrier as it is not possible to -// list all environment variables in a portable way. +// This returns all the keys in the environment variables. func (EnvCarrier) Keys() []string { - // I don't know why TextMapCarrier even has a Keys method. - // It looks like it was some mistake in the original design. - return nil + keys := make([]string, 0, len(os.Environ())) + for _, kv := range os.Environ() { + kvPair := strings.SplitN(kv, "=", 2) + if len(kvPair) < 1 { + continue + } + keys = append(keys, kvPair[0]) + } + return keys }