Repository: open-telemetry/opentelemetry-go-contrib (go)

Issue:
Request to expose string based configuration in autoprop module.
## Background

Currently autoprop module only supports setting the propagator either by passing actual propagators objects or via environment variable OTEL_PROPAGATOR.

This limits consumers of the module to expose these settings as external configuration strings.

 For example OTel Collector using this autoprop can only configure the autoprop module either by introducing 

1) it's own configuration semantics and later convert the configuration strings into propagator objects to pass them to the autoprop or 

2) Set the environment variables from withing the process by taking the values as configuration strings. 


But having an api that takes in a string with values equivalent to what gets passed in OTEL_PROPAGATOR let consumers expose the sdks settings as configuration. 

see https://github.com/open-telemetry/opentelemetry-collector/issues/5572


Pull request description:
Add TextMapPropagator function to autoprop
Resolve #2590

The collector plans to add configuration that will use a propagator describe in a text configuration file. That text needs to be converted into a propagator. This adds the `GetTextMapPropagator` function to do that conversion.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cde625eb2..b3b0e344a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 
 ## [Unreleased]
 
+### Added
+
+- The `GetTextMapPropagator` function to `go.opentelemetry.io/contrib/propagators/autoprop`.
+  This function is used to return a composite `TextMapPropagator` from registered names (instead of having to specify with an environment variable). (#2593)
+
 ### Changed
 
 - Upgraded all `semconv` package use to `v1.12.0`. (#2589)
diff --git a/propagators/autoprop/propagator.go b/propagators/autoprop/propagator.go
index 1fe177812..29ac8580a 100644
--- a/propagators/autoprop/propagator.go
+++ b/propagators/autoprop/propagator.go
@@ -16,7 +16,6 @@ package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop"
 
 import (
 	"errors"
-	"fmt"
 	"os"
 	"strings"
 
@@ -87,43 +86,5 @@ func parseEnv() (propagation.TextMapPropagator, error) {
 	if !defined {
 		return nil, nil
 	}
-
-	const sep = ","
-
-	var (
-		props   []propagation.TextMapPropagator
-		unknown []string
-	)
-
-	for _, pStr := range strings.Split(propStrs, sep) {
-		if pStr == none {
-			// If "none" is passed in combination with any other propagator,
-			// the result still needs to be a no-op propagator. Therefore,
-			// short-circuit here.
-			return propagation.NewCompositeTextMapPropagator(), nil
-		}
-
-		p, ok := envRegistry.load(pStr)
-		if !ok {
-			unknown = append(unknown, pStr)
-			continue
-		}
-		props = append(props, p)
-	}
-
-	var err error
-	if len(unknown) > 0 {
-		joined := strings.Join(unknown, sep)
-		err = fmt.Errorf("%w: %s", errUnknownPropagator, joined)
-	}
-
-	switch len(props) {
-	case 0:
-		return nil, err
-	case 1:
-		// Do not return a composite of a single propagator.
-		return props[0], err
-	default:
-		return propagation.NewCompositeTextMapPropagator(props...), err
-	}
+	return TextMapPropagator(strings.Split(propStrs, ",")...)
 }
diff --git a/propagators/autoprop/registry.go b/propagators/autoprop/registry.go
index 499a7da4b..a831773f3 100644
--- a/propagators/autoprop/registry.go
+++ b/propagators/autoprop/registry.go
@@ -17,6 +17,7 @@ package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop"
 import (
 	"errors"
 	"fmt"
+	"strings"
 	"sync"
 
 	"go.opentelemetry.io/contrib/propagators/aws/xray"
@@ -30,9 +31,9 @@ import (
 // configured.
 const none = "none"
 
-// envRegistry is the index of all supported environment variable
-// values and their mapping to a TextMapPropagator.
-var envRegistry = &registry{
+// propagators is the registry of TextMapPropagators registered with this
+// package. It includes all the OpenTelemetry defaults at startup.
+var propagators = &registry{
 	names: map[string]propagation.TextMapPropagator{
 		// W3C Trace Context.
 		"tracecontext": propagation.TraceContext{},
@@ -102,7 +103,7 @@ func (r *registry) drop(key string) {
 // will panic if name has already been registered or is a default
 // (tracecontext, baggage, b3, b3multi, jaeger, xray, or ottrace).
 func RegisterTextMapPropagator(name string, p propagation.TextMapPropagator) {
-	if err := envRegistry.store(name, p); err != nil {
+	if err := propagators.store(name, p); err != nil {
 		// envRegistry.store will return errDupReg if name is already
 		// registered. Panic here so the user is made aware of the duplicate
 		// registration, which could be done by malicious code trying to
@@ -115,3 +116,54 @@ func RegisterTextMapPropagator(name string, p propagation.TextMapPropagator) {
 		panic(err)
 	}
 }
+
+// TextMapPropagator returns a TextMapPropagator composed from the
+// passed names of registered TextMapPropagators. Each name must match an
+// already registered TextMapPropagator (see the RegisterTextMapPropagator
+// function for more information) or a default (tracecontext, baggage, b3,
+// b3multi, jaeger, xray, or ottrace).
+//
+// If "none" is included in the arguments, or no names are provided, the
+// returned TextMapPropagator will be a no-operation implementation.
+//
+// An error is returned for any un-registered names. The remaining, known,
+// names will be used to compose a TextMapPropagator that is returned with the
+// error.
+func TextMapPropagator(names ...string) (propagation.TextMapPropagator, error) {
+	var (
+		props   []propagation.TextMapPropagator
+		unknown []string
+	)
+
+	for _, name := range names {
+		if name == none {
+			// If "none" is passed in combination with any other propagator,
+			// the result still needs to be a no-op propagator. Therefore,
+			// short-circuit here.
+			return propagation.NewCompositeTextMapPropagator(), nil
+		}
+
+		p, ok := propagators.load(name)
+		if !ok {
+			unknown = append(unknown, name)
+			continue
+		}
+		props = append(props, p)
+	}
+
+	var err error
+	if len(unknown) > 0 {
+		joined := strings.Join(unknown, ",")
+		err = fmt.Errorf("%w: %s", errUnknownPropagator, joined)
+	}
+
+	switch len(props) {
+	case 0:
+		return nil, err
+	case 1:
+		// Do not return a composite of a single propagator.
+		return props[0], err
+	default:
+		return propagation.NewCompositeTextMapPropagator(props...), err
+	}
+}


Test patch:
diff --git a/propagators/autoprop/example_test.go b/propagators/autoprop/example_test.go
index f98ca9c02..699372215 100644
--- a/propagators/autoprop/example_test.go
+++ b/propagators/autoprop/example_test.go
@@ -82,3 +82,31 @@ func ExampleRegisterTextMapPropagator() {
 	fmt.Println(autoprop.NewTextMapPropagator().Fields())
 	// Output: [my-header-val]
 }
+
+func ExampleGetTextMapPropagator() {
+	prop, err := autoprop.TextMapPropagator("b3", "baggage")
+	if err != nil {
+		// Handle error appropriately.
+		panic(err)
+	}
+
+	fields := prop.Fields()
+	sort.Strings(fields)
+	fmt.Println(fields)
+	// Output: [baggage x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid]
+}
+
+func ExampleGetTextMapPropagator_custom() {
+	// To use your own or a 3rd-party exporter it needs to be registered prior
+	// to calling GetTextMapPropagator.
+	autoprop.RegisterTextMapPropagator("custom-get-prop", myTextMapPropagator{})
+
+	prop, err := autoprop.TextMapPropagator("custom-get-prop")
+	if err != nil {
+		// Handle error appropriately.
+		panic(err)
+	}
+
+	fmt.Println(prop.Fields())
+	// Output: [my-header-val]
+}
diff --git a/propagators/autoprop/registry_test.go b/propagators/autoprop/registry_test.go
index 7f284b6a2..a08606244 100644
--- a/propagators/autoprop/registry_test.go
+++ b/propagators/autoprop/registry_test.go
@@ -68,9 +68,9 @@ func TestRegistryConcurrentSafe(t *testing.T) {
 func TestRegisterTextMapPropagator(t *testing.T) {
 	const propName = "custom"
 	RegisterTextMapPropagator(propName, noop)
-	t.Cleanup(func() { envRegistry.drop(propName) })
+	t.Cleanup(func() { propagators.drop(propName) })
 
-	v, ok := envRegistry.load(propName)
+	v, ok := propagators.load(propName)
 	assert.True(t, ok, "missing propagator in envRegistry")
 	assert.Equal(t, noop, v, "wrong propagator stored")
 }
@@ -78,7 +78,7 @@ func TestRegisterTextMapPropagator(t *testing.T) {
 func TestDuplicateRegisterTextMapPropagatorPanics(t *testing.T) {
 	const propName = "custom"
 	RegisterTextMapPropagator(propName, noop)
-	t.Cleanup(func() { envRegistry.drop(propName) })
+	t.Cleanup(func() { propagators.drop(propName) })
 
 	errString := fmt.Sprintf("%s: %q", errDupReg, propName)
 	assert.PanicsWithError(t, errString, func() {