Skip to content
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

Allow setting the Sampler via environment variables #2517

Merged
merged 39 commits into from Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
79367b4
Allow setting the Sampler via environment variables (#2305)
vibhavp Jan 17, 2022
18713e2
Add changelog entry.
vibhavp Jan 17, 2022
46717eb
Replace t.Setenv with internaltest/SetEnvVariables for Go <= 1.6.
vibhavp Jan 18, 2022
9be40f9
Handle the lack of a sampler argument without logging errors.
vibhavp Jan 18, 2022
e88a6b3
Add additional test cases and error checks.
vibhavp Jan 18, 2022
3666485
Refactor documentation.
vibhavp Jan 19, 2022
de64ab6
emitBatchOverhead should only be used for splitting spans into batche…
ken8203 Jan 18, 2022
9baf34a
Add additional errors types, simplify abstractions and error handling
vibhavp Feb 7, 2022
3e41f19
Make error comparisons less fragile.
vibhavp Feb 7, 2022
9da3953
Fix typo in jaeger example (#2524)
bvwells Jan 19, 2022
e8e5636
Fix some typos in docs for Go libraries (#2520)
jeremy-stytch Jan 20, 2022
1cc331d
Fix getting-started.md Run function (#2527)
thinkgos Jan 22, 2022
6a17fd9
Bump github.com/google/go-cmp from 0.5.6 to 0.5.7 across the project …
MadVikingGod Jan 24, 2022
259bc50
Un-escape url coding when parsing baggage. (#2529)
MadVikingGod Jan 24, 2022
cfe16a2
Bump go.opentelemetry.io/proto/otlp from 0.11.0 to 0.12.0 (#2546)
MadVikingGod Jan 24, 2022
f064335
Remove unused sdk/internal/santize (#2549)
MrAlias Jan 26, 2022
1ea46ad
Add links to code examples and docs (#2551)
MrAlias Jan 26, 2022
9c3a231
Bump github.com/prometheus/client_golang from 1.11.0 to 1.12.0 in /ex…
dependabot[bot] Jan 26, 2022
fe8af71
Optimize evictedQueue implementation and use (#2556)
MrAlias Jan 27, 2022
797d7df
Add env support for batch span processor (#2515)
sincejune Jan 28, 2022
8b41a05
Bump golang.org/x/tools from 0.1.8 to 0.1.9 in /internal/tools (#2566)
dependabot[bot] Jan 30, 2022
64a7b19
Bump github.com/golangci/golangci-lint from 1.43.0 to 1.44.0 in /inte…
dependabot[bot] Jan 30, 2022
48e7ea8
Bump github.com/prometheus/client_golang from 1.12.0 to 1.12.1 in /ex…
dependabot[bot] Jan 31, 2022
a7e5d73
Fix TestBackoffRetry in otlp/internal/retry package (#2562)
MrAlias Jan 31, 2022
d5a0ed0
Bump google.golang.org/grpc from 1.43.0 to 1.44.0 in /exporters/otlp/…
dependabot[bot] Feb 1, 2022
45a1500
Bump google.golang.org/grpc from 1.43.0 to 1.44.0 in /example/otel-co…
dependabot[bot] Feb 1, 2022
8dc7c69
Bump google.golang.org/grpc from 1.43.0 to 1.44.0 in /exporters/otlp/…
dependabot[bot] Feb 1, 2022
3fe7c09
Change Options to accept type not pointer (#2558)
MrAlias Feb 1, 2022
409cd54
Do not store TracerProvider or Tracer fields in SDK recordingSpan (#2…
MrAlias Feb 1, 2022
f31ea0a
[website_docs] fix page meta-links (#2580)
chalin Feb 3, 2022
0be1658
Validate members once, in `NewMember` (#2522)
dmathieu Feb 4, 2022
0354823
Fix link to Zipkin exporter (#2581)
MrAlias Feb 7, 2022
edc91fd
Unexport EnvBatchSpanProcessor* constants (#2583)
MrAlias Feb 7, 2022
d3a3e0c
Avoid an extra allocation in applyTracerProviderEnvConfigs.
vibhavp Mar 5, 2022
e512343
Add additional errors for ratio > 1.0.
vibhavp Mar 5, 2022
3af1c81
Add test cases for ratio > 1.0.
vibhavp Mar 5, 2022
10fafb8
Merge branch 'main' into sdk-env-var-opts
vibhavp Mar 21, 2022
3400b5a
Update CHANGELOG.md
MrAlias Mar 21, 2022
7c25f9f
Merge branch 'main' into sdk-env-var-opts
MrAlias Mar 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -16,6 +16,7 @@ Code instrumented with the `go.opentelemetry.io/otel/metric` will need to be mod
### Added

- Add go 1.18 to our compatibility tests. (#2679)
- Allow configuring the Sampler with the `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG` environment variables. (#2305, #2517)

### Changed

Expand Down
29 changes: 28 additions & 1 deletion sdk/trace/provider.go
Expand Up @@ -99,6 +99,7 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
o := tracerProviderConfig{
spanLimits: NewSpanLimits(),
}
o = applyTracerProviderEnvConfigs(o)

for _, opt := range opts {
o = opt.apply(o)
Expand Down Expand Up @@ -335,7 +336,10 @@ func WithIDGenerator(g IDGenerator) TracerProviderOption {
// Tracers the TracerProvider creates to make their sampling decisions for the
// Spans they create.
//
// If this option is not used, the TracerProvider will use a
// This option overrides the Sampler configured through the OTEL_TRACES_SAMPLER
// and OTEL_TRACES_SAMPLER_ARG environment variables. If this option is not used
pellared marked this conversation as resolved.
Show resolved Hide resolved
// and the sampler is not configured through environment variables or the environment
// contains invalid/unsupported configuration, the TracerProvider will use a
// ParentBased(AlwaysSample) Sampler by default.
func WithSampler(s Sampler) TracerProviderOption {
return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig {
Expand Down Expand Up @@ -408,6 +412,29 @@ func WithRawSpanLimits(limits SpanLimits) TracerProviderOption {
})
}

func applyTracerProviderEnvConfigs(cfg tracerProviderConfig) tracerProviderConfig {
for _, opt := range tracerProviderOptionsFromEnv() {
cfg = opt.apply(cfg)
}

return cfg
}

func tracerProviderOptionsFromEnv() []TracerProviderOption {
var opts []TracerProviderOption

sampler, err := samplerFromEnv()
if err != nil {
otel.Handle(err)
vibhavp marked this conversation as resolved.
Show resolved Hide resolved
}

if sampler != nil {
opts = append(opts, WithSampler(sampler))
}

return opts
}

// ensureValidTracerProviderConfig ensures that given TracerProviderConfig is valid.
func ensureValidTracerProviderConfig(cfg tracerProviderConfig) tracerProviderConfig {
if cfg.sampler == nil {
Expand Down
165 changes: 165 additions & 0 deletions sdk/trace/provider_test.go
Expand Up @@ -17,10 +17,14 @@ package trace
import (
"context"
"errors"
"fmt"
"math/rand"
"testing"

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

ottest "go.opentelemetry.io/otel/internal/internaltest"
"go.opentelemetry.io/otel/trace"
)

Expand Down Expand Up @@ -94,3 +98,164 @@ func TestSchemaURL(t *testing.T) {
tracerStruct := tracerIface.(*tracer)
assert.EqualValues(t, schemaURL, tracerStruct.instrumentationLibrary.SchemaURL)
}

func TestTracerProviderSamplerConfigFromEnv(t *testing.T) {
type testCase struct {
sampler string
samplerArg string
argOptional bool
description string
errorType error
invalidArgErrorType interface{}
}

randFloat := rand.Float64()

tests := []testCase{
{
sampler: "invalid-sampler",
argOptional: true,
description: ParentBased(AlwaysSample()).Description(),
errorType: errUnsupportedSampler("invalid-sampler"),
invalidArgErrorType: func() *errUnsupportedSampler { e := errUnsupportedSampler("invalid-sampler"); return &e }(),
},
{
sampler: "always_on",
argOptional: true,
description: AlwaysSample().Description(),
},
{
sampler: "always_off",
argOptional: true,
description: NeverSample().Description(),
},
{
sampler: "traceidratio",
samplerArg: fmt.Sprintf("%g", randFloat),
description: TraceIDRatioBased(randFloat).Description(),
},
{
sampler: "traceidratio",
samplerArg: fmt.Sprintf("%g", -randFloat),
description: TraceIDRatioBased(1.0).Description(),
errorType: errNegativeTraceIDRatio,
},
{
sampler: "traceidratio",
samplerArg: fmt.Sprintf("%g", 1+randFloat),
description: TraceIDRatioBased(1.0).Description(),
errorType: errGreaterThanOneTraceIDRatio,
},
{
sampler: "traceidratio",
argOptional: true,
description: TraceIDRatioBased(1.0).Description(),
invalidArgErrorType: new(samplerArgParseError),
},
{
sampler: "parentbased_always_on",
argOptional: true,
description: ParentBased(AlwaysSample()).Description(),
},
{
sampler: "parentbased_always_off",
argOptional: true,
description: ParentBased(NeverSample()).Description(),
},
{
sampler: "parentbased_traceidratio",
samplerArg: fmt.Sprintf("%g", randFloat),
description: ParentBased(TraceIDRatioBased(randFloat)).Description(),
},
{
sampler: "parentbased_traceidratio",
samplerArg: fmt.Sprintf("%g", -randFloat),
description: ParentBased(TraceIDRatioBased(1.0)).Description(),
errorType: errNegativeTraceIDRatio,
},
{
sampler: "parentbased_traceidratio",
samplerArg: fmt.Sprintf("%g", 1+randFloat),
description: ParentBased(TraceIDRatioBased(1.0)).Description(),
errorType: errGreaterThanOneTraceIDRatio,
},
{
sampler: "parentbased_traceidratio",
argOptional: true,
description: ParentBased(TraceIDRatioBased(1.0)).Description(),
invalidArgErrorType: new(samplerArgParseError),
},
}

handler.Reset()

for _, test := range tests {
t.Run(test.sampler, func(t *testing.T) {
envVars := map[string]string{
"OTEL_TRACES_SAMPLER": test.sampler,
}

if test.samplerArg != "" {
envVars["OTEL_TRACES_SAMPLER_ARG"] = test.samplerArg
}
envStore, err := ottest.SetEnvVariables(envVars)
require.NoError(t, err)
t.Cleanup(func() {
handler.Reset()
require.NoError(t, envStore.Restore())
})

stp := NewTracerProvider(WithSyncer(NewTestExporter()))
assert.Equal(t, test.description, stp.sampler.Description())
if test.errorType != nil {
testStoredError(t, test.errorType)
} else {
assert.Empty(t, handler.errs)
}

if test.argOptional {
t.Run("invalid sampler arg", func(t *testing.T) {
envStore, err := ottest.SetEnvVariables(map[string]string{
"OTEL_TRACES_SAMPLER": test.sampler,
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
"OTEL_TRACES_SAMPLER_ARG": "invalid-ignored-string",
})
require.NoError(t, err)
t.Cleanup(func() {
handler.Reset()
require.NoError(t, envStore.Restore())
})

stp := NewTracerProvider(WithSyncer(NewTestExporter()))
t.Cleanup(func() {
require.NoError(t, stp.Shutdown(context.Background()))
})
assert.Equal(t, test.description, stp.sampler.Description())

if test.invalidArgErrorType != nil {
testStoredError(t, test.invalidArgErrorType)
} else {
assert.Empty(t, handler.errs)
}
})
}
})
}
}

func testStoredError(t *testing.T, target interface{}) {
t.Helper()

if assert.Len(t, handler.errs, 1) && assert.Error(t, handler.errs[0]) {
err := handler.errs[0]

require.Implements(t, (*error)(nil), target)
require.NotNil(t, target.(error))

defer handler.Reset()
if errors.Is(err, target.(error)) {
return
}

assert.ErrorAs(t, err, target)
}
}
107 changes: 107 additions & 0 deletions sdk/trace/sampler_env.go
@@ -0,0 +1,107 @@
// 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 trace // import "go.opentelemetry.io/otel/sdk/trace"

import (
"errors"
"fmt"
"os"
"strconv"
"strings"
)

const (
tracesSamplerKey = "OTEL_TRACES_SAMPLER"
tracesSamplerArgKey = "OTEL_TRACES_SAMPLER_ARG"

samplerAlwaysOn = "always_on"
samplerAlwaysOff = "always_off"
samplerTraceIDRatio = "traceidratio"
samplerParentBasedAlwaysOn = "parentbased_always_on"
samplerParsedBasedAlwaysOff = "parentbased_always_off"
samplerParentBasedTraceIDRatio = "parentbased_traceidratio"
)

type errUnsupportedSampler string

func (e errUnsupportedSampler) Error() string {
return fmt.Sprintf("unsupported sampler: %s", string(e))
}

var (
errNegativeTraceIDRatio = errors.New("invalid trace ID ratio: less than 0.0")
errGreaterThanOneTraceIDRatio = errors.New("invalid trace ID ratio: greater than 1.0")
)

type samplerArgParseError struct {
parseErr error
}

func (e samplerArgParseError) Error() string {
return fmt.Sprintf("parsing sampler argument: %s", e.parseErr.Error())
}

func (e samplerArgParseError) Unwrap() error {
return e.parseErr
}

func samplerFromEnv() (Sampler, error) {
sampler, ok := os.LookupEnv(tracesSamplerKey)
if !ok {
return nil, nil
}

sampler = strings.ToLower(strings.TrimSpace(sampler))
samplerArg, hasSamplerArg := os.LookupEnv(tracesSamplerArgKey)
samplerArg = strings.TrimSpace(samplerArg)

switch sampler {
case samplerAlwaysOn:
return AlwaysSample(), nil
case samplerAlwaysOff:
return NeverSample(), nil
case samplerTraceIDRatio:
ratio, err := parseTraceIDRatio(samplerArg, hasSamplerArg)
return ratio, err
case samplerParentBasedAlwaysOn:
return ParentBased(AlwaysSample()), nil
case samplerParsedBasedAlwaysOff:
return ParentBased(NeverSample()), nil
case samplerParentBasedTraceIDRatio:
ratio, err := parseTraceIDRatio(samplerArg, hasSamplerArg)
return ParentBased(ratio), err
default:
return nil, errUnsupportedSampler(sampler)
}

}

func parseTraceIDRatio(arg string, hasSamplerArg bool) (Sampler, error) {
if !hasSamplerArg {
return TraceIDRatioBased(1.0), nil
}
v, err := strconv.ParseFloat(arg, 64)
if err != nil {
return TraceIDRatioBased(1.0), samplerArgParseError{err}
}
if v < 0.0 {
return TraceIDRatioBased(1.0), errNegativeTraceIDRatio
}
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
if v > 1.0 {
return TraceIDRatioBased(1.0), errGreaterThanOneTraceIDRatio
}

return TraceIDRatioBased(v), nil
}