Skip to content

[Prototype] propagation: add EnvCarrier #6778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions propagation/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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 {
// 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{}

// 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.
// If SetEnvFunc is not set, this method does nothing.
func (e EnvCarrier) Set(key, value string) {
if e.SetEnvFunc == nil {
return
}
k := strings.ToUpper(key)
_ = e.SetEnvFunc(k, value)
}

// Keys lists the keys stored in this carrier.
// This returns all the keys in the environment variables.
func (EnvCarrier) Keys() []string {
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])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe

Suggested change
keys = append(keys, kvPair[0])
keys = append(keys, strings.ToLower(kvPair[0]))

}
return keys
}
124 changes: 124 additions & 0 deletions propagation/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package propagation_test

import (
"context"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)

func TestExtractValidTraceContextEnvCarrier(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))
})
}
}

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)
}
}
})
}
}
Loading