Repository: open-telemetry/opentelemetry-go-contrib
Pull request #2258 (2022-05-03 21:46:50)

Add autoprop package to provide configuration of propagators with useful defaults and envar support
### Use case

```go
otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
```

to replace

```go
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
```

- Defaults to TraceContext with Baggage
- Arguments to `NewTextMapPropagator` act the same as `NewCompositeTextMapPropagator` and override defaults
- `OTEL_PROPAGATORS` overrides defaults and arguments
- Custom 3rd-party propagators can be registered with `RegisterTextMapPropagator` and then supported in the parsed propagator from `OTEL_PROPAGATORS`

Resolve https://github.com/open-telemetry/opentelemetry-go/issues/1265
Resolve https://github.com/open-telemetry/opentelemetry-go/issues/2304
Related to https://github.com/open-telemetry/opentelemetry-go/issues/1404

codecov[bot]: # [Codecov](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2258?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
> Merging [#2258](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2258?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (a45b5de) into [main](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/commit/6feda8333ee4608534f51a0bcd3e2fe8b60ab900?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (6feda83) will **increase** coverage by `0.3%`.
> The diff coverage is `100.0%`.

[![Impacted file tree graph](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2258/graphs/tree.svg?width=650&height=150&src=pr&token=P6F3W9WA7Q&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2258?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)

```diff
@@           Coverage Diff           @@
##            main   #2258     +/-   ##
=======================================
+ Coverage   73.6%   73.9%   +0.3%     
=======================================
  Files        141     143      +2     
  Lines       6396    6470     +74     
=======================================
+ Hits        4712    4786     +74     
  Misses      1542    1542             
  Partials     142     142             
```


| [Impacted Files](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2258?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [propagators/autoprop/propagator.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2258/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-cHJvcGFnYXRvcnMvYXV0b3Byb3AvcHJvcGFnYXRvci5nbw==) | `100.0% <100.0%> (ø)` | |
| [propagators/autoprop/registry.go](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/pull/2258/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-cHJvcGFnYXRvcnMvYXV0b3Byb3AvcmVnaXN0cnkuZ28=) | `100.0% <100.0%> (ø)` | |

pellared: I think it also resolves https://github.com/open-telemetry/opentelemetry-go/issues/2304
pellared: What do you think of adding `OTEL_PROPAGATORS` support to the "non-contrib" version of OTel Go? It could be done in API-wise backward compatible way. I imagine something like this could be more user-friendly (probably this could be done better)

1. `propagation.NewCompositeTextMapPropagator` could already support `OTEL_PROPAGATORS`. `propagation.Register` is used to register more mappings
2. `tracecontext` `baggage` `none` are supported OOTB. `propagation.Unregister` is used if one would like to change any of the default mappings.
3. The exporter packages like `jaeger` could call `propagation.Register` in its `init()`.

This way existing users would get the `OTEL_PROPAGATORS` without changing their code.
MrAlias: > What do you think of adding `OTEL_PROPAGATORS` support to the "non-contrib" version of OTel Go SDK? I imagine something like this could be done (probably this can be improved)
> 
> 1. `propagation.NewTextMapPropagator` supports `OTEL_PROPAGATORS`. `propagation` is a new package `go.opentelemetry.io/otel/sdk/propagation` as such code cannot live in OTel API according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md#global-propagators
> 2. `tracecontext` `baggage` `none` are supported OOTB. The default propagators are `tracecontext,baggage`.
> 3. `propagation.Register` is used to register more mappings.
> 4. `propagation.Unregister` is used if one would like to change any of the default mappings.
> 5. The exporter packages like `jaeger` call `propagation.Register` in its `init()`. This couples the exporter packages to the SDK but I see some of them are already coupled to the SDK e.g. all detectors are coupled to `go.opentelemetry.io/otel/sdk/resource`.

I would not support this alternate approach adding the propagator setup package to the SDK. The contrib repository is intended to contain such things and adding something such a divergence to the specification would add nonconformity and confusion to the project.

As for the implementation, by only half supporting the values `OTEL_PROPAGATORS` is specified to support we would only support half of the specification. Requiring users to register packages to have full support is not how the specification is defined. Furthermore, it is an attempt to add convince by adding side-effects. A user imports the package and instead of being explicit about registering or using the propagator it becomes a side-effect of the import. If a user would like to setup side-effects with their own propagators they can do so with `autoprop`. They are not limited because of this approach.

I do not see that alternate approach as an improvement to this.
pellared: > such a divergence to the specification would add nonconformity and confusion to the project

Just a note. This is not against the specification. The specification only defines the API requirements.
E.g. is what has been done in OTel .NET SDK: https://github.com/open-telemetry/opentelemetry-dotnet/blob/2e63fd99a8c8263bf3ae3a8bd70c042580bada22/src/OpenTelemetry/Sdk.cs#L32-L36

> by only half supporting the values `OTEL_PROPAGATORS` is specified to support we would only support half of the specification

The specification only says what are the "known values". The default values and `none` SHOULD be supported.

>  Requiring users to register packages to have full support is not how the specification is defined

I agree here. I was thinking about the trace exporters (instead of propagators) 🤦My bad - sorry. The contrib could simply have a helper to register all propagators supported by contrib repository.

Still, I think that it would be better if the SDK would support `OTEL_PROPAGATORS` with support for the OTel default propagators.
MrAlias: > > such a divergence to the specification would add nonconformity and confusion to the project
> 
> This is not against the specification. The specification only defines the API requirements.

Right, this is not defined by the specification. What happens when it is? What happens when other SIGs do it differently? Why would a user expect to find this in the SDK when other SIGs do not offer this?

> > by only half supporting the values `OTEL_PROPAGATORS` is specified to support we would only support half of the specification
> 
> The specification only says what are the "known values". The default values and `none` SHOULD be supported. Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md

I don't follow. If we support half the known values we sill only support half. What about the other half of known values? The user expects these values to supported if we support others, they are both known values.

> Still, I think that it would be better if the SDK would support `OTEL_PROPAGATORS` ...

I agree on this.

> with support for the OTel default propagators.

This is where you lose me. Again, supporting half the known values is supporting half of user expectation. This adds frustration and confusion. This is precisely why we did not add support for this environment variable in the past.

> Changing the proposal:
> 
> 1. `propagation.NewTextMapPropagator` supports `OTEL_PROPAGATORS`. `propagation` is a new package `go.opentelemetry.io/otel/sdk/propagation` as such code cannot live in OTel API according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md#global-propagators

Again, this is a non-starter for me. I would not accept a change to add a `propagation` package to `go.opentelemetry.io/otel/sdk`.


pellared: > Right, this is not defined by the specification. What happens when it is? What happens when other SIGs do it differently? Why would a user expect to find this in the SDK when other SIGs do not offer this?

Most of the other SDKs are already doing it. It was discussed here: https://github.com/open-telemetry/opentelemetry-go/issues/2611

> I don't follow. If we support half the known values we sill only support half. What about the other half of known values? The user expects these values to supported if we support others, they are both known values.

We should always document what we support and what we do not support. E.g. OTel JS has similar approach
- https://github.com/open-telemetry/opentelemetry-js/blob/cfda625e83a164c9e19745392879c32aedcfe76f/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts#L55-L62
- https://github.com/open-telemetry/opentelemetry-js/blob/df0cee9b7fb59fbe5182b69667c00aeb984236ed/packages/opentelemetry-core/src/utils/environment.ts#L129

The downside of current that using `autoprop` would be that it is also dependent on propagators not required by the specification. So far I have seen a lot of caution when bringing potentially necessary dependencies. 

PS. I am not requesting any changes nor blocking this PR. I think it would be good to see what others think.
pellared: I had a call with @MrAlias and we discussed 👆 

The current approach with `autoprop` is safer as

1. We create less packages - less confusion for the users
2. Having it in contrib allows to make breaking changes (as it is still v0)

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ccf705af3..a0fb05c33 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -532,6 +532,15 @@ updates:
     schedule:
       interval: weekly
       day: sunday
+  - package-ecosystem: gomod
+    directory: /propagators/autoprop
+    labels:
+      - dependencies
+      - go
+      - Skip Changelog
+    schedule:
+      interval: weekly
+      day: sunday
   - package-ecosystem: gomod
     directory: /propagators/aws
     labels:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 76a14e8e0..fb685f565 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 
 ## [Unreleased]
 
+### Added
+
+- The `go.opentelemetry.io/contrib/propagators/autoprop` package to provide configuration of propagators with useful defaults and envar support. (#2258)
+
 ## [1.7.0/0.32.0] - 2022-04-28
 
 ### Added
diff --git a/propagators/autoprop/doc.go b/propagators/autoprop/doc.go
new file mode 100644
index 000000000..0f09e7457
--- /dev/null
+++ b/propagators/autoprop/doc.go
@@ -0,0 +1,23 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package autoprop provides an OpenTelemetry TextMapPropagator creation
+// function. The OpenTelemetry specification states that the default
+// TextMapPropagator needs to be a no-operation implementation. The
+// opentelemetry-go project adheres to this requirement. However, for systems
+// that perform propagation this default is not ideal. This package provides a
+// TextMapPropagator with useful defaults (a combined TraceContext and Baggage
+// TextMapPropagator), and supports environment overrides using the
+// OTEL_PROPAGATORS environment variable.
+package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop"
diff --git a/propagators/autoprop/go.mod b/propagators/autoprop/go.mod
new file mode 100644
index 000000000..d5ba5638c
--- /dev/null
+++ b/propagators/autoprop/go.mod
@@ -0,0 +1,25 @@
+module go.opentelemetry.io/contrib/propagators/autoprop
+
+go 1.17
+
+require (
+	github.com/stretchr/testify v1.7.1
+	go.opentelemetry.io/contrib/propagators/aws v1.7.0
+	go.opentelemetry.io/contrib/propagators/b3 v1.7.0
+	go.opentelemetry.io/contrib/propagators/jaeger v1.7.0
+	go.opentelemetry.io/contrib/propagators/ot v1.7.0
+	go.opentelemetry.io/otel v1.7.0
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/go-logr/logr v1.2.3 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.7.0 // indirect
+	go.opentelemetry.io/otel/trace v1.7.0 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	go.uber.org/multierr v1.8.0 // indirect
+	golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+)
diff --git a/propagators/autoprop/go.sum b/propagators/autoprop/go.sum
new file mode 100644
index 000000000..e0001c2d2
--- /dev/null
+++ b/propagators/autoprop/go.sum
@@ -0,0 +1,43 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+go.opentelemetry.io/contrib/propagators/aws v1.7.0 h1:hzLtX+K4YhsrBabA35uBYxCENb5rS/9Z9X8MToTlA3k=
+go.opentelemetry.io/contrib/propagators/aws v1.7.0/go.mod h1:h/ql5T6e1XLRFplWNNdzLHp8eb0dkBu+xYOCYxerh0Q=
+go.opentelemetry.io/contrib/propagators/b3 v1.7.0 h1:oRAenUhj+GFttfIp3gj7HYVzBhPOHgq/dWPDSmLCXSY=
+go.opentelemetry.io/contrib/propagators/b3 v1.7.0/go.mod h1:gXx7AhL4xXCF42gpm9dQvdohoDa2qeyEx4eIIxqK+h4=
+go.opentelemetry.io/contrib/propagators/jaeger v1.7.0 h1:x2mXKtONfOJFfNFSx4QXFx1fms6bKIPVvWvgdiaPdRI=
+go.opentelemetry.io/contrib/propagators/jaeger v1.7.0/go.mod h1:kt2lNImfxV6dETRsDCENd6jU6G0mPRS+P0qlNuvtkTE=
+go.opentelemetry.io/contrib/propagators/ot v1.7.0 h1:KPPToDRxyY/HI3qD4RqwWRbaQ65RIpF8uKWDqWkFHDA=
+go.opentelemetry.io/contrib/propagators/ot v1.7.0/go.mod h1:5qxBZR730yb71uXc3bazxt2Si8o8LQK3iJTnSLca/BU=
+go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
+go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
+go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
+go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
+go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
+go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/propagators/autoprop/propagator.go b/propagators/autoprop/propagator.go
new file mode 100644
index 000000000..1fe177812
--- /dev/null
+++ b/propagators/autoprop/propagator.go
@@ -0,0 +1,129 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop"
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"strings"
+
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/propagation"
+)
+
+// otelPropagatorsEnvKey is the environment variable name identifying
+// propagators to use.
+const otelPropagatorsEnvKey = "OTEL_PROPAGATORS"
+
+// NewTextMapPropagator returns a new TextMapPropagator composited by props or
+// one defined by the OTEL_PROPAGATORS environment variable. The
+// TextMapPropagator defined by OTEL_PROPAGATORS, if set, will take precedence
+// to the once composited by props.
+//
+// The propagators supported with the OTEL_PROPAGATORS environment variable by
+// default are: tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace, and
+// none. Each of these values, and their combination, are supported in
+// conformance with the OpenTelemetry specification. See
+// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#general-sdk-configuration
+// for more information.
+//
+// The supported environment variable propagators can be extended to include
+// custom 3rd-party TextMapPropagator. See the RegisterTextMapPropagator
+// function for more information.
+//
+// If OTEL_PROPAGATORS is not defined and props is no provided, the returned
+// TextMapPropagator will be a composite of the TraceContext and Baggage
+// propagators.
+func NewTextMapPropagator(props ...propagation.TextMapPropagator) propagation.TextMapPropagator {
+	// Environment variable defined propagator has precedence over arguments.
+	envProp, err := parseEnv()
+	if err != nil {
+		// Communicate to the user their supplied value will not be used.
+		otel.Handle(err)
+	}
+	if envProp != nil {
+		return envProp
+	}
+
+	switch len(props) {
+	case 0:
+		// Default to TraceContext and Baggage.
+		return propagation.NewCompositeTextMapPropagator(
+			propagation.TraceContext{}, propagation.Baggage{},
+		)
+	case 1:
+		// Do not add overhead with a composite propagator wrapping a single
+		// propagator, return it directly.
+		return props[0]
+	default:
+		return propagation.NewCompositeTextMapPropagator(props...)
+	}
+}
+
+// errUnknownPropagator is returned when an unknown propagator name is used in
+// the OTEL_PROPAGATORS environment variable.
+var errUnknownPropagator = errors.New("unknown propagator")
+
+// parseEnv returns the composite TextMapPropagators defined by the
+// OTEL_PROPAGATORS environment variable. A nil TextMapPropagator is returned
+// if no propagator is defined for the environment variable. A no-op
+// TextMapPropagator will be returned if "none" is defined anywhere in the
+// environment variable.
+func parseEnv() (propagation.TextMapPropagator, error) {
+	propStrs, defined := os.LookupEnv(otelPropagatorsEnvKey)
+	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
+	}
+}
diff --git a/propagators/autoprop/registry.go b/propagators/autoprop/registry.go
new file mode 100644
index 000000000..499a7da4b
--- /dev/null
+++ b/propagators/autoprop/registry.go
@@ -0,0 +1,117 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop"
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+
+	"go.opentelemetry.io/contrib/propagators/aws/xray"
+	"go.opentelemetry.io/contrib/propagators/b3"
+	"go.opentelemetry.io/contrib/propagators/jaeger"
+	"go.opentelemetry.io/contrib/propagators/ot"
+	"go.opentelemetry.io/otel/propagation"
+)
+
+// none is the special "propagator" name that means no propagator shall be
+// configured.
+const none = "none"
+
+// envRegistry is the index of all supported environment variable
+// values and their mapping to a TextMapPropagator.
+var envRegistry = &registry{
+	names: map[string]propagation.TextMapPropagator{
+		// W3C Trace Context.
+		"tracecontext": propagation.TraceContext{},
+		// W3C Baggage.
+		"baggage": propagation.Baggage{},
+		// B3 single-header format.
+		"b3": b3.New(),
+		// B3 multi-header format.
+		"b3multi": b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)),
+		// Jaeger.
+		"jaeger": jaeger.Jaeger{},
+		// AWS X-Ray.
+		"xray": xray.Propagator{},
+		// OpenTracing Trace.
+		"ottrace": ot.OT{},
+
+		// No-op TextMapPropagator.
+		none: propagation.NewCompositeTextMapPropagator(),
+	},
+}
+
+// registry maintains a map of propagator names to TextMapPropagator
+// implementations that is safe for concurrent use by multiple goroutines
+// without additional locking or coordination.
+type registry struct {
+	mu    sync.Mutex
+	names map[string]propagation.TextMapPropagator
+}
+
+// load returns the value stored in the registry index for a key, or nil if no
+// value is present. The ok result indicates whether value was found in the
+// index.
+func (r *registry) load(key string) (p propagation.TextMapPropagator, ok bool) {
+	r.mu.Lock()
+	p, ok = r.names[key]
+	r.mu.Unlock()
+	return p, ok
+}
+
+var errDupReg = errors.New("duplicate registration")
+
+// store sets the value for a key if is not already in the registry. errDupReg
+// is returned if the registry already contains key.
+func (r *registry) store(key string, value propagation.TextMapPropagator) error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	if r.names == nil {
+		r.names = map[string]propagation.TextMapPropagator{key: value}
+		return nil
+	}
+	if _, ok := r.names[key]; ok {
+		return fmt.Errorf("%w: %q", errDupReg, key)
+	}
+	r.names[key] = value
+	return nil
+}
+
+// drop removes key from the registry if it exists, otherwise nothing.
+func (r *registry) drop(key string) {
+	r.mu.Lock()
+	delete(r.names, key)
+	r.mu.Unlock()
+}
+
+// RegisterTextMapPropagator sets the TextMapPropagator p to be used when the
+// OTEL_PROPAGATORS environment variable contains the propagator name. This
+// 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 {
+		// 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
+		// intercept cross-cutting concerns.
+		//
+		// Panic for all other errors as well. At this point there should not
+		// be any other errors returned from the store operation. If there
+		// are, alert the developer that adding them as soon as possible that
+		// they need to be handled here.
+		panic(err)
+	}
+}
diff --git a/versions.yaml b/versions.yaml
index 544be0d07..382272f6c 100644
--- a/versions.yaml
+++ b/versions.yaml
@@ -30,6 +30,7 @@ module-sets:
     version: v0.32.0
     modules:
       - go.opentelemetry.io/contrib/detectors/aws/lambda
+      - go.opentelemetry.io/contrib/propagators/autoprop
       - go.opentelemetry.io/contrib/propagators/opencensus
       - go.opentelemetry.io/contrib/propagators/opencensus/examples
       - go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron


Test patch:
diff --git a/propagators/autoprop/example_test.go b/propagators/autoprop/example_test.go
new file mode 100644
index 000000000..f98ca9c02
--- /dev/null
+++ b/propagators/autoprop/example_test.go
@@ -0,0 +1,84 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package autoprop_test
+
+import (
+	"fmt"
+	"os"
+	"sort"
+
+	"go.opentelemetry.io/contrib/propagators/autoprop"
+	"go.opentelemetry.io/contrib/propagators/b3"
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/propagation"
+)
+
+func ExampleNewTextMapPropagator() {
+	// NewTextMapPropagator returns a TraceContext and Baggage propagator by
+	// default. The response of this function can be directly registered with
+	// the go.opentelemetry.io/otel package.
+	otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
+
+	fields := otel.GetTextMapPropagator().Fields()
+	sort.Strings(fields)
+	fmt.Println(fields)
+	// Output: [baggage traceparent tracestate]
+}
+
+func ExampleNewTextMapPropagator_arguments() {
+	// NewTextMapPropagator behaves the same as the
+	// NewCompositeTextMapPropagator function in the
+	// go.opentelemetry.io/otel/propagation package when TextMapPropagator are
+	// passed as arguments.
+	fields := autoprop.NewTextMapPropagator(
+		propagation.TraceContext{},
+		propagation.Baggage{},
+		b3.New(),
+	).Fields()
+	sort.Strings(fields)
+	fmt.Println(fields)
+	// Output: [baggage traceparent tracestate x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid]
+}
+
+func ExampleNewTextMapPropagator_environment() {
+	// Propagators set for the OTEL_PROPAGATORS environment variable take
+	// precedence and will override any arguments passed to
+	// NewTextMapPropagator.
+	_ = os.Setenv("OTEL_PROPAGATORS", "b3,baggage")
+
+	// Returns only a B3 and Baggage TextMapPropagator (i.e. does not include
+	// TraceContext).
+	fields := autoprop.NewTextMapPropagator(propagation.TraceContext{}).Fields()
+	sort.Strings(fields)
+	fmt.Println(fields)
+	// Output: [baggage x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid]
+}
+
+type myTextMapPropagator struct{ propagation.TextMapPropagator }
+
+func (myTextMapPropagator) Fields() []string {
+	return []string{"my-header-val"}
+}
+
+func ExampleRegisterTextMapPropagator() {
+	// To use your own or a 3rd-party exporter via the OTEL_PROPAGATORS
+	// environment variable, it needs to be registered prior to calling
+	// NewTextMapPropagator.
+	autoprop.RegisterTextMapPropagator("custom-prop", myTextMapPropagator{})
+
+	_ = os.Setenv("OTEL_PROPAGATORS", "custom-prop")
+	fmt.Println(autoprop.NewTextMapPropagator().Fields())
+	// Output: [my-header-val]
+}
diff --git a/propagators/autoprop/propagator_test.go b/propagators/autoprop/propagator_test.go
new file mode 100644
index 000000000..9eebc1a2f
--- /dev/null
+++ b/propagators/autoprop/propagator_test.go
@@ -0,0 +1,67 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package autoprop
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/propagation"
+)
+
+type handler struct {
+	err error
+}
+
+func (h *handler) Handle(err error) { h.err = err }
+
+func TestNewTextMapPropagatorInvalidEnvVal(t *testing.T) {
+	h := &handler{}
+	otel.SetErrorHandler(h)
+
+	const name = "invalid-name"
+	t.Setenv(otelPropagatorsEnvKey, name)
+	_ = NewTextMapPropagator()
+	assert.ErrorIs(t, h.err, errUnknownPropagator)
+}
+
+func TestNewTextMapPropagatorDefault(t *testing.T) {
+	expect := []string{"traceparent", "tracestate", "baggage"}
+	assert.ElementsMatch(t, expect, NewTextMapPropagator().Fields())
+}
+
+type ptrNoop struct{}
+
+func (*ptrNoop) Inject(context.Context, propagation.TextMapCarrier) {}
+
+func (*ptrNoop) Extract(context.Context, propagation.TextMapCarrier) context.Context {
+	return context.Background()
+}
+func (*ptrNoop) Fields() []string {
+	return nil
+}
+
+func TestNewTextMapPropagatorSingleNoOverhead(t *testing.T) {
+	p := &ptrNoop{}
+	assert.Same(t, p, NewTextMapPropagator(p))
+}
+
+func TestNewTextMapPropagatorMultiEnvNone(t *testing.T) {
+	t.Setenv(otelPropagatorsEnvKey, "b3,none,tracecontext")
+	assert.Equal(t, noop, NewTextMapPropagator())
+}
diff --git a/propagators/autoprop/registry_test.go b/propagators/autoprop/registry_test.go
new file mode 100644
index 000000000..7f284b6a2
--- /dev/null
+++ b/propagators/autoprop/registry_test.go
@@ -0,0 +1,87 @@
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package autoprop
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"go.opentelemetry.io/otel/propagation"
+)
+
+var noop = propagation.NewCompositeTextMapPropagator()
+
+func TestRegistryEmptyStore(t *testing.T) {
+	r := registry{}
+	assert.NotPanics(t, func() {
+		require.NoError(t, r.store("first", noop))
+	})
+}
+
+func TestRegistryEmptyLoad(t *testing.T) {
+	r := registry{}
+	assert.NotPanics(t, func() {
+		v, ok := r.load("non-existent")
+		assert.False(t, ok, "empty registry should hold nothing")
+		assert.Nil(t, v, "non-nil propagator returned")
+	})
+}
+
+func TestRegistryConcurrentSafe(t *testing.T) {
+	const propName = "prop"
+
+	r := registry{}
+	assert.NotPanics(t, func() {
+		require.NoError(t, r.store(propName, noop))
+	})
+
+	go func() {
+		assert.NotPanics(t, func() {
+			require.ErrorIs(t, r.store(propName, noop), errDupReg)
+		})
+	}()
+
+	go func() {
+		assert.NotPanics(t, func() {
+			v, ok := r.load(propName)
+			assert.True(t, ok, "missing propagator in registry")
+			assert.Equal(t, noop, v, "wrong propagator retuned")
+		})
+	}()
+}
+
+func TestRegisterTextMapPropagator(t *testing.T) {
+	const propName = "custom"
+	RegisterTextMapPropagator(propName, noop)
+	t.Cleanup(func() { envRegistry.drop(propName) })
+
+	v, ok := envRegistry.load(propName)
+	assert.True(t, ok, "missing propagator in envRegistry")
+	assert.Equal(t, noop, v, "wrong propagator stored")
+}
+
+func TestDuplicateRegisterTextMapPropagatorPanics(t *testing.T) {
+	const propName = "custom"
+	RegisterTextMapPropagator(propName, noop)
+	t.Cleanup(func() { envRegistry.drop(propName) })
+
+	errString := fmt.Sprintf("%s: %q", errDupReg, propName)
+	assert.PanicsWithError(t, errString, func() {
+		RegisterTextMapPropagator(propName, noop)
+	})
+}