Skip to content

Commit 324662a

Browse files
Joibelpellaredkamphausdmathieu
authored
envcar: add environment carrier (#8442)
Signed-off-by: Alan Clucas <alan@clucas.org> Co-authored-by: Robert Pająk <pellared@hotmail.com> Co-authored-by: Christophe Kamphaus <christophe.kamphaus@gmail.com> Co-authored-by: Damien Mathieu <42@dmathieu.com>
1 parent 69addb4 commit 324662a

File tree

12 files changed

+587
-0
lines changed

12 files changed

+587
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- Add environment variables propagation carrier in `go.opentelemetry.io/contrib/propagators/envcar`. (#8442)
14+
1115
### Changed
1216

1317
- Upgrade `go.opentelemetry.io/otel/semconv` to `v1.40.0`, including updates across instrumentation and detector modules. (#8631)

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ processors/minsev @open-te
6464
propagators/autoprop/ @open-telemetry/go-approvers @MrAlias
6565
propagators/aws/ @open-telemetry/go-approvers @akats7
6666
propagators/b3/ @open-telemetry/go-approvers @pellared
67+
propagators/envcar/ @open-telemetry/go-approvers @Joibel @pellared
6768
propagators/jaeger/ @open-telemetry/go-approvers @yurishkuro
6869
propagators/opencensus/ @open-telemetry/go-approvers @dashpole
6970
propagators/ot/ @open-telemetry/go-approvers @pellared

propagators/envcar/carrier.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
"sync"
10+
11+
"go.opentelemetry.io/otel/propagation"
12+
)
13+
14+
// upperWithUnderscores converts a string so that A-Z and 0-9 and _ are kept
15+
// as-is, a-z is uppercased, and all other characters are replaced with _.
16+
func upperWithUnderscores(s string) string {
17+
b := make([]byte, 0, len(s))
18+
for _, r := range s {
19+
switch {
20+
case r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '_':
21+
b = append(b, byte(r))
22+
case r >= 'a' && r <= 'z':
23+
b = append(b, byte(r+'A'-'a'))
24+
default:
25+
b = append(b, '_')
26+
}
27+
}
28+
return string(b)
29+
}
30+
31+
// Carrier is a TextMapCarrier that uses the environment variables as a
32+
// storage medium for propagated key-value pairs. The keys are normalised
33+
// before being used to access the environment variables.
34+
// This is useful for propagating values that are set in the environment
35+
// and need to be accessed by different processes or services.
36+
// The keys are uppercased to avoid case sensitivity issues across different
37+
// operating systems and environments.
38+
//
39+
// If you do not set SetEnvFunc, [Carrier.Set] will do nothing.
40+
// Using [os.Setenv] here is discouraged as the environment should
41+
// be immutable:
42+
// https://opentelemetry.io/docs/specs/otel/context/env-carriers/#environment-variable-immutability
43+
type Carrier struct {
44+
// SetEnvFunc is the function that sets the environment variable.
45+
// Usually, you want to set the environment variables for processes
46+
// that are spawned by the current process.
47+
SetEnvFunc func(key, value string)
48+
values map[string]string
49+
once sync.Once
50+
}
51+
52+
// Compile time check that Carrier implements the TextMapCarrier.
53+
var _ propagation.TextMapCarrier = (*Carrier)(nil)
54+
55+
// fetch runs once on first access, and stores the environment in the
56+
// carrier.
57+
func (c *Carrier) fetch() {
58+
c.once.Do(func() {
59+
environ := os.Environ()
60+
c.values = make(map[string]string, len(environ))
61+
for _, kv := range environ {
62+
kvPair := strings.SplitN(kv, "=", 2)
63+
c.values[kvPair[0]] = kvPair[1]
64+
}
65+
})
66+
}
67+
68+
// Get returns the value associated with the normalized passed key.
69+
// The first call to [Carrier.Get] or [Carrier.Keys] for a
70+
// given Carrier will read and store the values from the
71+
// environment and all future reads will be from that store.
72+
func (c *Carrier) Get(key string) string {
73+
c.fetch()
74+
return c.values[upperWithUnderscores(key)]
75+
}
76+
77+
// Set stores the key-value pair in the environment variable.
78+
// The key is normalized before being used to set the
79+
// environment variable.
80+
// If SetEnvFunc is not set, this method does nothing.
81+
func (c *Carrier) Set(key, value string) {
82+
if c.SetEnvFunc == nil {
83+
return
84+
}
85+
k := upperWithUnderscores(key)
86+
c.SetEnvFunc(k, value)
87+
}
88+
89+
// Keys lists the keys stored in this carrier.
90+
// This returns all the keys in the environment variables.
91+
// The first call to [Carrier.Get] or [Carrier.Keys] for a
92+
// given Carrier will read and store the values from the
93+
// environment and all future reads will be from that store.
94+
// Keys are returned as is, without any normalization, but
95+
// this behavior is subject to change.
96+
func (c *Carrier) Keys() []string {
97+
c.fetch()
98+
keys := make([]string, 0, len(c.values))
99+
for key := range c.values {
100+
keys = append(keys, key)
101+
}
102+
return keys
103+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package envcar_test
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"os"
10+
"os/exec"
11+
12+
"go.opentelemetry.io/otel/propagation"
13+
"go.opentelemetry.io/otel/trace"
14+
15+
"go.opentelemetry.io/contrib/propagators/envcar"
16+
)
17+
18+
// This example is a go program where the environment variables are carrying the
19+
// trace information, and we're going to pick them up into our context.
20+
func ExampleCarrier_extractFromParent() {
21+
// Simulate environment variables set by a parent process.
22+
// In practice, these would already be set when this process starts.
23+
_ = os.Setenv("TRACEPARENT", "00-0102030405060708090a0b0c0d0e0f10-0102030405060708-01")
24+
25+
// Create a carrier to read trace context from environment variables.
26+
carrier := envcar.Carrier{}
27+
28+
// Extract trace context that was propagated by the parent process.
29+
prop := propagation.TraceContext{}
30+
ctx := prop.Extract(context.Background(), &carrier)
31+
32+
// The context now contains the span context from the parent.
33+
spanCtx := trace.SpanContextFromContext(ctx)
34+
fmt.Printf("Trace ID: %s\n", spanCtx.TraceID())
35+
fmt.Printf("Span ID: %s\n", spanCtx.SpanID())
36+
fmt.Printf("Sampled: %t\n", spanCtx.IsSampled())
37+
// Output:
38+
// Trace ID: 0102030405060708090a0b0c0d0e0f10
39+
// Span ID: 0102030405060708
40+
// Sampled: true
41+
}
42+
43+
// This example is a go program where we have a trace and we'd like to inject it
44+
// into a command we're going to run.
45+
func ExampleCarrier_childProcess() {
46+
// Create a span context with a known trace ID.
47+
traceID := trace.TraceID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}
48+
spanID := trace.SpanID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
49+
spanCtx := trace.NewSpanContext(trace.SpanContextConfig{
50+
TraceID: traceID,
51+
SpanID: spanID,
52+
TraceFlags: trace.FlagsSampled,
53+
})
54+
ctx := trace.ContextWithSpanContext(context.Background(), spanCtx)
55+
56+
// Prepare a command that prints the TRACEPARENT environment variable.
57+
cmd := exec.Command("printenv", "TRACEPARENT")
58+
cmd.Env = os.Environ()
59+
60+
// Create a carrier that injects trace context into the child
61+
// process's environment rather than the current process's.
62+
carrier := envcar.Carrier{
63+
SetEnvFunc: func(key, value string) {
64+
cmd.Env = append(cmd.Env, key+"="+value)
65+
},
66+
}
67+
68+
// Inject trace context into the child's environment.
69+
prop := propagation.TraceContext{}
70+
prop.Inject(ctx, &carrier)
71+
72+
// The child process now has trace context in its environment,
73+
// independent of the parent process's environment variables.
74+
out, err := cmd.Output()
75+
if err != nil {
76+
fmt.Println("error:", err)
77+
return
78+
}
79+
fmt.Print(string(out))
80+
// Output: 00-0102030405060708090a0b0c0d0e0f10-0102030405060708-01
81+
}

0 commit comments

Comments
 (0)