Skip to content

Commit 8432515

Browse files
committed
feat: add environment carrier
Signed-off-by: Alan Clucas <alan@clucas.org>
1 parent 5c491d4 commit 8432515

File tree

5 files changed

+232
-0
lines changed

5 files changed

+232
-0
lines changed

propagators/envcar/carrier.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
package envcar // import "go.opentelemetry.io/contrib/propagators/envcar"
4+
5+
import (
6+
"os"
7+
"strings"
8+
9+
"go.opentelemetry.io/otel/propagation"
10+
)
11+
12+
// EnvCarrier is a TextMapCarrier that uses the environment variables as a
13+
// storage medium for propagated key-value pairs. The keys are uppercased
14+
// before being used to access the environment variables.
15+
// This is useful for propagating values that are set in the environment
16+
// and need to be accessed by different processes or services.
17+
// The keys are uppercased to avoid case sensitivity issues across different
18+
// operating systems and environments.
19+
type Carrier struct {
20+
// SetEnvFunc is a function that sets the environment variable.
21+
// Usually, you want to set the environment variable for processes
22+
// that are spawned by the current process.
23+
// By default implementation, it does nothing.
24+
SetEnvFunc func(key, value string) error
25+
}
26+
27+
var _ propagation.TextMapCarrier = Carrier{}
28+
29+
// Get returns the value associated with the passed key.
30+
// The key is uppercased before being used to access the environment variable.
31+
func (Carrier) Get(key string) string {
32+
k := strings.ToUpper(key)
33+
return os.Getenv(k)
34+
}
35+
36+
// Set stores the key-value pair in the environment variable.
37+
// The key is uppercased before being used to set the environment variable.
38+
// If SetEnvFunc is not set, this method does nothing.
39+
func (e Carrier) Set(key, value string) {
40+
if e.SetEnvFunc == nil {
41+
return
42+
}
43+
k := strings.ToUpper(key)
44+
_ = e.SetEnvFunc(k, value)
45+
}
46+
47+
// Keys lists the keys stored in this carrier.
48+
// This returns all the keys in the environment variables.
49+
func (Carrier) Keys() []string {
50+
keys := make([]string, 0, len(os.Environ()))
51+
for _, kv := range os.Environ() {
52+
kvPair := strings.SplitN(kv, "=", 2)
53+
if len(kvPair) < 1 {
54+
continue
55+
}
56+
keys = append(keys, strings.ToLower(kvPair[0]))
57+
}
58+
return keys
59+
}

propagators/envcar/carrier_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
package envcar_test
4+
5+
import (
6+
"context"
7+
"os"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"go.opentelemetry.io/contrib/propagators/envcar"
14+
"go.opentelemetry.io/otel/propagation"
15+
"go.opentelemetry.io/otel/trace"
16+
)
17+
18+
var (
19+
traceID = trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0, 0x1, 0xc8}
20+
spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b}
21+
prop = propagation.TraceContext{}
22+
)
23+
24+
func TestExtractValidTraceContextEnvCarrier(t *testing.T) {
25+
stateStr := "key1=value1,key2=value2"
26+
state, err := trace.ParseTraceState(stateStr)
27+
require.NoError(t, err)
28+
29+
tests := []struct {
30+
name string
31+
envs map[string]string
32+
want trace.SpanContext
33+
}{
34+
{
35+
name: "sampled",
36+
envs: map[string]string{
37+
"TRACEPARENT": "00-000000000000007b00000000000001c8-000000000000007b-01",
38+
},
39+
want: trace.NewSpanContext(trace.SpanContextConfig{
40+
TraceID: traceID,
41+
SpanID: spanID,
42+
TraceFlags: trace.FlagsSampled,
43+
Remote: true,
44+
}),
45+
},
46+
{
47+
name: "valid tracestate",
48+
envs: map[string]string{
49+
"TRACEPARENT": "00-000000000000007b00000000000001c8-000000000000007b-00",
50+
"TRACESTATE": stateStr,
51+
},
52+
want: trace.NewSpanContext(trace.SpanContextConfig{
53+
TraceID: traceID,
54+
SpanID: spanID,
55+
TraceState: state,
56+
Remote: true,
57+
}),
58+
},
59+
}
60+
61+
for _, tc := range tests {
62+
t.Run(tc.name, func(t *testing.T) {
63+
ctx := context.Background()
64+
for k, v := range tc.envs {
65+
t.Setenv(k, v)
66+
}
67+
ctx = prop.Extract(ctx, envcar.Carrier{})
68+
assert.Equal(t, tc.want, trace.SpanContextFromContext(ctx))
69+
})
70+
}
71+
}
72+
73+
func TestInjectTraceContextEnvCarrier(t *testing.T) {
74+
stateStr := "key1=value1,key2=value2"
75+
state, err := trace.ParseTraceState(stateStr)
76+
require.NoError(t, err)
77+
78+
tests := []struct {
79+
name string
80+
want map[string]string
81+
sc trace.SpanContext
82+
}{
83+
{
84+
name: "sampled",
85+
want: map[string]string{
86+
"TRACEPARENT": "00-000000000000007b00000000000001c8-000000000000007b-01",
87+
},
88+
sc: trace.NewSpanContext(trace.SpanContextConfig{
89+
TraceID: traceID,
90+
SpanID: spanID,
91+
TraceFlags: trace.FlagsSampled,
92+
Remote: true,
93+
}),
94+
},
95+
{
96+
name: "with tracestate",
97+
want: map[string]string{
98+
"TRACEPARENT": "00-000000000000007b00000000000001c8-000000000000007b-00",
99+
"TRACESTATE": stateStr,
100+
},
101+
sc: trace.NewSpanContext(trace.SpanContextConfig{
102+
TraceID: traceID,
103+
SpanID: spanID,
104+
TraceState: state,
105+
Remote: true,
106+
}),
107+
},
108+
}
109+
110+
for _, tc := range tests {
111+
t.Run(tc.name, func(t *testing.T) {
112+
ctx := context.Background()
113+
ctx = trace.ContextWithRemoteSpanContext(ctx, tc.sc)
114+
c := envcar.Carrier{
115+
SetEnvFunc: func(key, value string) error {
116+
t.Setenv(key, value)
117+
return nil
118+
},
119+
}
120+
121+
prop.Inject(ctx, c)
122+
123+
for k, v := range tc.want {
124+
if got := os.Getenv(k); got != v {
125+
t.Errorf("got %s=%s, want %s=%s", k, got, k, v)
126+
}
127+
}
128+
})
129+
}
130+
}

propagators/envcar/go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module go.opentelemetry.io/contrib/propagators/envcar
2+
3+
go 1.24.0
4+
5+
require (
6+
github.com/stretchr/testify v1.11.1
7+
go.opentelemetry.io/otel v1.39.0
8+
go.opentelemetry.io/otel/trace v1.39.0
9+
)
10+
11+
require (
12+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
13+
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/pmezard/go-difflib v1.0.0 // indirect
15+
gopkg.in/yaml.v3 v3.0.1 // indirect
16+
)

propagators/envcar/go.sum

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
2+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
6+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
7+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
8+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
9+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
10+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
11+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
12+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
13+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
14+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
15+
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
16+
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
17+
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
18+
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
19+
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
20+
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
21+
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
22+
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
23+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
24+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
25+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
26+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

versions.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module-sets:
2525
- go.opentelemetry.io/contrib/detectors/aws/lambda
2626
- go.opentelemetry.io/contrib/exporters/autoexport
2727
- go.opentelemetry.io/contrib/propagators/autoprop
28+
- go.opentelemetry.io/contrib/propagators/envcar
2829
- go.opentelemetry.io/contrib/propagators/opencensus
2930
- go.opentelemetry.io/contrib/propagators/opencensus/examples
3031
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp

0 commit comments

Comments
 (0)