From c18691fd63149886398cef409858e9b48c8c1bc9 Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh <62210712+akstron@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:28:58 +0530 Subject: [PATCH] Support Environment Variable for Jaeger Remote Sampler (#6310) ## Which problem is this PR solving? Resolves #6256 ## Description of the changes - Added capability to configure jaeger remote sampler using `OTEL_TRACES_SAMPLER_ARG` - Environment variables would be applied before the code values for the corresponding keys. - If the corresponding values exist in code, we take those. --------- Signed-off-by: Alok Kumar Singh Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 4 ++ .../jaegerremote/sampler_remote_options.go | 54 +++++++++++++++++ samplers/jaegerremote/sampler_remote_test.go | 59 +++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e95a2c4cb..bfcacfc5e5e 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 + +- Added support for providing `endpoint`, `pollingIntervalMs` and `initialSamplingRate` using environment variable `OTEL_TRACES_SAMPLER_ARG` in `go.opentelemetry.io/contrib/samples/jaegerremote`. (#6310) + diff --git a/samplers/jaegerremote/sampler_remote_options.go b/samplers/jaegerremote/sampler_remote_options.go index ffb26057083..2922ec6484b 100644 --- a/samplers/jaegerremote/sampler_remote_options.go +++ b/samplers/jaegerremote/sampler_remote_options.go @@ -19,6 +19,10 @@ package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( + "fmt" + "os" + "strconv" + "strings" "time" "github.com/go-logr/logr" @@ -37,6 +41,45 @@ type config struct { logger logr.Logger } +func getEnvOptions() ([]Option, []error) { + var options []Option + // list of errors which will be logged once logger is set by the user + var errs []error + + args := strings.Split(os.Getenv("OTEL_TRACES_SAMPLER_ARG"), ",") + for _, arg := range args { + keyValue := strings.Split(arg, "=") + if len(keyValue) != 2 { + errs = append(errs, fmt.Errorf("argument %s is not of type '='", arg)) + continue + } + key := strings.Trim(keyValue[0], " ") + value := strings.Trim(keyValue[1], " ") + + switch key { + case "endpoint": + options = append(options, WithSamplingServerURL(value)) + case "pollingIntervalMs": + intervalMs, err := strconv.Atoi(value) + if err != nil { + errs = append(errs, fmt.Errorf("%s parsing failed with :%w", key, err)) + continue + } + options = append(options, WithSamplingRefreshInterval(time.Duration(intervalMs)*time.Millisecond)) + case "initialSamplingRate": + samplingRate, err := strconv.ParseFloat(value, 64) + if err != nil { + errs = append(errs, fmt.Errorf("%s parsing failed with :%w", key, err)) + continue + } + options = append(options, WithInitialSampler(trace.TraceIDRatioBased(samplingRate))) + default: + errs = append(errs, fmt.Errorf("invalid argument %s in OTEL_TRACE_SAMPLER_ARG", key)) + } + } + return options, errs +} + // newConfig returns an appropriately configured config. func newConfig(options ...Option) config { c := config{ @@ -55,9 +98,20 @@ func newConfig(options ...Option) config { }, logger: logr.Discard(), } + + envOptions, errs := getEnvOptions() + for _, option := range envOptions { + option.apply(&c) + } + for _, option := range options { option.apply(&c) } + + for _, err := range errs { + c.logger.Error(err, "env variable parsing failure") + } + c.updaters = append([]samplerUpdater{&perOperationSamplerUpdater{ MaxOperations: c.posParams.MaxOperations, OperationNameLateBinding: c.posParams.OperationNameLateBinding, diff --git a/samplers/jaegerremote/sampler_remote_test.go b/samplers/jaegerremote/sampler_remote_test.go index 79d711a5f38..93b16da4615 100644 --- a/samplers/jaegerremote/sampler_remote_test.go +++ b/samplers/jaegerremote/sampler_remote_test.go @@ -595,3 +595,62 @@ func TestDefaultSamplingStrategyFetcher_Timeout(t *testing.T) { fetcher := newHTTPSamplingStrategyFetcher("") assert.Equal(t, defaultRemoteSamplingTimeout, fetcher.httpClient.Timeout) } + +func TestEnvVarSettingForNewTracer(t *testing.T) { + type testConfig struct { + samplingServerURL string + samplingRefreshInterval time.Duration + } + + tests := []struct { + otelTraceSamplerArgs string + expErrs []string + codeOptions []Option + expConfig testConfig + }{ + { + otelTraceSamplerArgs: "endpoint=http://localhost:14250,pollingIntervalMs=5000,initialSamplingRate=0.25", + expErrs: []string{}, + }, + { + otelTraceSamplerArgs: "endpointhttp://localhost:14250,pollingIntervalMs=5x000,initialSamplingRate=0.xyz25,invalidKey=invalidValue", + expErrs: []string{ + "argument endpointhttp://localhost:14250 is not of type '='", + "pollingIntervalMs parsing failed", + "initialSamplingRate parsing failed", + "invalid argument invalidKey in OTEL_TRACE_SAMPLER_ARG", + }, + }, + { + // Make sure we don't override values provided in code + otelTraceSamplerArgs: "endpoint=http://localhost:14250,pollingIntervalMs=5000,initialSamplingRate=0.25", + expErrs: []string{}, + codeOptions: []Option{ + WithSamplingServerURL("http://localhost:5778"), + }, + expConfig: testConfig{ + samplingServerURL: "http://localhost:5778", + samplingRefreshInterval: time.Millisecond * 5000, + }, + }, + } + + for _, test := range tests { + t.Run("", func(t *testing.T) { + t.Setenv("OTEL_TRACES_SAMPLER_ARG", test.otelTraceSamplerArgs) + + _, errs := getEnvOptions() + require.Equal(t, len(test.expErrs), len(errs)) + + for i := range len(errs) { + require.ErrorContains(t, errs[i], test.expErrs[i]) + } + + if test.codeOptions != nil { + cfg := newConfig(test.codeOptions...) + require.Equal(t, test.expConfig.samplingServerURL, cfg.samplingServerURL) + require.Equal(t, test.expConfig.samplingRefreshInterval, cfg.samplingRefreshInterval) + } + }) + } +}