Skip to content

Commit c015059

Browse files
MrAliasXSAM
andauthored
Add the sdk/internal/x package (#5444)
Add the `x` package to handle experimental feature flagging within the go.opentelemetry.io/otel/sdk module. Currently this only supports enabling experimental resource semantic conventions. Resolve #5436 cc @pyohannes --------- Co-authored-by: Sam Xie <[email protected]>
1 parent db74d18 commit c015059

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed

sdk/internal/x/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Experimental Features
2+
3+
The SDK contains features that have not yet stabilized in the OpenTelemetry specification.
4+
These features are added to the OpenTelemetry Go SDK prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
5+
6+
These feature may change in backwards incompatible ways as feedback is applied.
7+
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
8+
9+
## Features
10+
11+
- [Resource](#resource)
12+
13+
### Resource
14+
15+
[OpenTelemetry resource semantic conventions] include many attribute definitions that are defined as experimental.
16+
To have experimental semantic conventions be added by [resource detectors] set the `OTEL_GO_X_RESOURCE` environment variable.
17+
The value set must be the case-insensitive string of `"true"` to enable the feature.
18+
All other values are ignored.
19+
20+
<!-- TODO: document what attributes are added by which detector -->
21+
22+
[OpenTelemetry resource semantic conventions]: https://opentelemetry.io/docs/specs/semconv/resource/
23+
[resource detectors]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/resource#Detector
24+
25+
#### Examples
26+
27+
Enable experimental resource semantic conventions.
28+
29+
```console
30+
export OTEL_GO_X_RESOURCE=true
31+
```
32+
33+
Disable experimental resource semantic conventions.
34+
35+
```console
36+
unset OTEL_GO_X_RESOURCE
37+
```
38+
39+
## Compatibility and Stability
40+
41+
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../VERSIONING.md).
42+
These features may be removed or modified in successive version releases, including patch versions.
43+
44+
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.
45+
There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version.
46+
If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support.

sdk/internal/x/x.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package x contains support for OTel SDK experimental features.
5+
//
6+
// This package should only be used for features defined in the specification.
7+
// It should not be used for experiments or new project ideas.
8+
package x // import "go.opentelemetry.io/otel/sdk/internal/x"
9+
10+
import (
11+
"os"
12+
"strings"
13+
)
14+
15+
// Resource is an experimental feature flag that defines if resource detectors
16+
// should be included experimental semantic conventions.
17+
//
18+
// To enable this feature set the OTEL_GO_X_RESOURCE environment variable
19+
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
20+
// will also enable this).
21+
var Resource = newFeature("RESOURCE", func(v string) (string, bool) {
22+
if strings.ToLower(v) == "true" {
23+
return v, true
24+
}
25+
return "", false
26+
})
27+
28+
// Feature is an experimental feature control flag. It provides a uniform way
29+
// to interact with these feature flags and parse their values.
30+
type Feature[T any] struct {
31+
key string
32+
parse func(v string) (T, bool)
33+
}
34+
35+
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
36+
const envKeyRoot = "OTEL_GO_X_"
37+
return Feature[T]{
38+
key: envKeyRoot + suffix,
39+
parse: parse,
40+
}
41+
}
42+
43+
// Key returns the environment variable key that needs to be set to enable the
44+
// feature.
45+
func (f Feature[T]) Key() string { return f.key }
46+
47+
// Lookup returns the user configured value for the feature and true if the
48+
// user has enabled the feature. Otherwise, if the feature is not enabled, a
49+
// zero-value and false are returned.
50+
func (f Feature[T]) Lookup() (v T, ok bool) {
51+
// https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value
52+
//
53+
// > The SDK MUST interpret an empty value of an environment variable the
54+
// > same way as when the variable is unset.
55+
vRaw := os.Getenv(f.key)
56+
if vRaw == "" {
57+
return v, ok
58+
}
59+
return f.parse(vRaw)
60+
}
61+
62+
// Enabled returns if the feature is enabled.
63+
func (f Feature[T]) Enabled() bool {
64+
_, ok := f.Lookup()
65+
return ok
66+
}

sdk/internal/x/x_test.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 x
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestExemplars(t *testing.T) {
14+
const key = "OTEL_GO_X_RESOURCE"
15+
require.Equal(t, key, Resource.Key())
16+
17+
t.Run("true", run(setenv(key, "true"), assertEnabled(Resource, "true")))
18+
t.Run("True", run(setenv(key, "True"), assertEnabled(Resource, "True")))
19+
t.Run("TRUE", run(setenv(key, "TRUE"), assertEnabled(Resource, "TRUE")))
20+
t.Run("false", run(setenv(key, "false"), assertDisabled(Resource)))
21+
t.Run("1", run(setenv(key, "1"), assertDisabled(Resource)))
22+
t.Run("empty", run(assertDisabled(Resource)))
23+
}
24+
25+
func run(steps ...func(*testing.T)) func(*testing.T) {
26+
return func(t *testing.T) {
27+
t.Helper()
28+
for _, step := range steps {
29+
step(t)
30+
}
31+
}
32+
}
33+
34+
func setenv(k, v string) func(t *testing.T) {
35+
return func(t *testing.T) { t.Setenv(k, v) }
36+
}
37+
38+
func assertEnabled[T any](f Feature[T], want T) func(*testing.T) {
39+
return func(t *testing.T) {
40+
t.Helper()
41+
assert.True(t, f.Enabled(), "not enabled")
42+
43+
v, ok := f.Lookup()
44+
assert.True(t, ok, "Lookup state")
45+
assert.Equal(t, want, v, "Lookup value")
46+
}
47+
}
48+
49+
func assertDisabled[T any](f Feature[T]) func(*testing.T) {
50+
var zero T
51+
return func(t *testing.T) {
52+
t.Helper()
53+
54+
assert.False(t, f.Enabled(), "enabled")
55+
56+
v, ok := f.Lookup()
57+
assert.False(t, ok, "Lookup state")
58+
assert.Equal(t, zero, v, "Lookup value")
59+
}
60+
}

0 commit comments

Comments
 (0)