Skip to content

Commit f9eee61

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

File tree

7 files changed

+241
-0
lines changed

7 files changed

+241
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1313
- `WithMetricAttributesFn` option in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to define dynamic attributes on auto-instrumented metrics. (#8191)
1414
- Add support for configuring propagators in `go.opentelemetry.io/contrib/otelconf`. (#8281)
1515
- Add `const Version` in `go.opentelemetry.io/contrib/bridges/prometheus`. (#8401)
16+
- Add environment carrier in `go.opentelemetry.io/contrib/propagators/envcar`. (#8300)
1617

1718
### Fixed
1819

propagators/envcar/carrier.go

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

propagators/envcar/carrier_test.go

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

propagators/envcar/doc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package envcar implements the Environment Carrier specification as documented
5+
// here https://opentelemetry.io/docs/specs/otel/context/env-carriers/
6+
package envcar // import "go.opentelemetry.io/contrib/propagators/envcar"

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)