diff --git a/CHANGELOG.md b/CHANGELOG.md index 098a96cc33..ace327f84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,16 @@ Increment the: ## [Unreleased] +* [SDK] Support `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG` environment variables + [#4709](https://github.com/open-telemetry/opentelemetry-cpp/issues/4709) + Users can now configure the sampler via environment variables, supporting: + - `always_on`, `always_off`, `traceidratio`, `parentbased_traceidratio`, + `parentbased_always_on`, `parentbased_always_off` + - For ratio-based samplers, `OTEL_TRACES_SAMPLER_ARG` specifies the ratio (default 1.0) + - Environment configuration applies to TracerProvider, TracerContext, and factory methods + - Explicitly supplied samplers always take precedence over environment variables + - Invalid sampler names or ratios fall back to the default AlwaysOnSampler with a warning + ## [1.30.0] 2026-10-22 This release contains: diff --git a/sdk/include/opentelemetry/sdk/trace/sampler_env.h b/sdk/include/opentelemetry/sdk/trace/sampler_env.h new file mode 100644 index 0000000000..57fde62a47 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/sampler_env.h @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ + +namespace sampler_env +{ + +/** + * @brief Returns a sampler built from OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG + * environment variables, or a default AlwaysOnSampler if the variables are not set. + * + * Supported sampler values: + * - always_on: Always sample the trace + * - always_off: Never sample the trace + * - traceidratio: Sample a fraction of traces based on trace ID, requires OTEL_TRACES_SAMPLER_ARG + * - parentbased_traceidratio: Sample based on parent and trace ID, requires OTEL_TRACES_SAMPLER_ARG + * - parentbased_always_on: Use parent sample decision, default to AlwaysOn + * - parentbased_always_off: Use parent sample decision, default to AlwaysOff + * + * For traceidratio and parentbased_traceidratio, OTEL_TRACES_SAMPLER_ARG specifies the ratio + * (0.0 to 1.0). If unset, defaults to 1.0. + * + * If an unknown sampler name or invalid ratio is provided, logs a warning and returns + * AlwaysOnSampler. + */ +OPENTELEMETRY_EXPORT std::unique_ptr GetSamplerFromEnv(); + +} // namespace sampler_env + +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE + diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_context.h b/sdk/include/opentelemetry/sdk/trace/tracer_context.h index 3631f1ace3..64600d7150 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_context.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_context.h @@ -13,6 +13,7 @@ #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/random_id_generator.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" @@ -44,7 +45,7 @@ class TracerContext std::vector> &&processor, const opentelemetry::sdk::resource::Resource &resource = opentelemetry::sdk::resource::Resource::Create({}), - std::unique_ptr sampler = std::unique_ptr(new AlwaysOnSampler), + std::unique_ptr sampler = nullptr, std::unique_ptr id_generator = std::unique_ptr(new RandomIdGenerator()), std::unique_ptr> tracer_configurator = diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h index 2cf00024f8..ff7d56ae93 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h @@ -14,6 +14,7 @@ #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/random_id_generator.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" @@ -36,8 +37,9 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T * @param processor The span processor for this tracer provider. This must * not be a nullptr. * @param resource The resources for this tracer provider. - * @param sampler The sampler for this tracer provider. This must - * not be a nullptr. + * @param sampler The sampler for this tracer provider. If null, a sampler is built from + * OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG environment variables, defaulting to + * AlwaysOnSampler if unset. * @param id_generator The custom id generator for this tracer provider. This must * not be a nullptr * @param tracer_configurator Provides access to a function that computes the TracerConfig for @@ -48,7 +50,7 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T std::unique_ptr processor, const opentelemetry::sdk::resource::Resource &resource = opentelemetry::sdk::resource::Resource::Create({}), - std::unique_ptr sampler = std::unique_ptr(new AlwaysOnSampler), + std::unique_ptr sampler = nullptr, std::unique_ptr id_generator = std::unique_ptr(new RandomIdGenerator()), std::unique_ptr> tracer_configurator = @@ -62,7 +64,7 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T std::vector> &&processors, const opentelemetry::sdk::resource::Resource &resource = opentelemetry::sdk::resource::Resource::Create({}), - std::unique_ptr sampler = std::unique_ptr(new AlwaysOnSampler), + std::unique_ptr sampler = nullptr, std::unique_ptr id_generator = std::unique_ptr(new RandomIdGenerator()), std::unique_ptr> tracer_configurator = diff --git a/sdk/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index 366c15eac8..2957be4f46 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -17,6 +17,7 @@ add_library( simple_processor_factory.cc span_data.cc span_limits.cc + sampler_env.cc samplers/always_on_factory.cc samplers/always_off_factory.cc samplers/parent.cc diff --git a/sdk/src/trace/sampler_env.cc b/sdk/src/trace/sampler_env.cc new file mode 100644 index 0000000000..95843ca6a1 --- /dev/null +++ b/sdk/src/trace/sampler_env.cc @@ -0,0 +1,141 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" +#include "opentelemetry/sdk/trace/samplers/always_off_factory.h" +#include "opentelemetry/sdk/trace/samplers/always_on_factory.h" +#include "opentelemetry/sdk/trace/samplers/parent_factory.h" +#include "opentelemetry/sdk/trace/samplers/trace_id_ratio_factory.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ + +namespace sampler_env +{ + +namespace +{ + +// Case-insensitive comparison of two ASCII strings +static bool CaseInsensitiveAsciiEquals(const std::string &lhs, const std::string &rhs) noexcept +{ + if (lhs.size() != rhs.size()) + { + return false; + } + for (size_t i = 0; i < lhs.size(); ++i) + { + char a = lhs[i]; + char b = rhs[i]; + if (a >= 'A' && a <= 'Z') + a += 32; + if (b >= 'A' && b <= 'Z') + b += 32; + if (a != b) + { + return false; + } + } + return true; +} + +} // namespace + +static constexpr const char *kTracesSamplerEnv = "OTEL_TRACES_SAMPLER"; +static constexpr const char *kTracesSamplerArgEnv = "OTEL_TRACES_SAMPLER_ARG"; +static constexpr double kDefaultTraceIdRatio = 1.0; + +std::unique_ptr GetSamplerFromEnv() +{ + std::string sampler_name; + bool has_sampler_env = + opentelemetry::sdk::common::GetStringEnvironmentVariable(kTracesSamplerEnv, sampler_name); + + if (!has_sampler_env || sampler_name.empty()) + { + // When unset, preserve current default behavior (AlwaysOnSampler) + return AlwaysOnSamplerFactory::Create(); + } + + float ratio_arg = kDefaultTraceIdRatio; + bool has_ratio_env = + opentelemetry::sdk::common::GetFloatEnvironmentVariable(kTracesSamplerArgEnv, ratio_arg); + + // For traceidratio and parentbased_traceidratio, default to 1.0 if arg is unset + if (!has_ratio_env) + { + ratio_arg = static_cast(kDefaultTraceIdRatio); + } + + // Build sampler based on environment variable value + if (CaseInsensitiveAsciiEquals(sampler_name, "always_on")) + { + return AlwaysOnSamplerFactory::Create(); + } + else if (CaseInsensitiveAsciiEquals(sampler_name, "always_off")) + { + return AlwaysOffSamplerFactory::Create(); + } + else if (CaseInsensitiveAsciiEquals(sampler_name, "traceidratio")) + { + // Validate ratio + if (std::isnan(ratio_arg) || std::isinf(ratio_arg) || ratio_arg < 0.0f || ratio_arg > 1.0f) + { + OTEL_INTERNAL_LOG_WARN("Environment variable <" << kTracesSamplerArgEnv + << "> has invalid value <" << ratio_arg + << "> for traceidratio sampler, " + << "falling back to AlwaysOnSampler"); + return AlwaysOnSamplerFactory::Create(); + } + return TraceIdRatioBasedSamplerFactory::Create(ratio_arg); + } + else if (CaseInsensitiveAsciiEquals(sampler_name, "parentbased_traceidratio")) + { + // Validate ratio - if invalid, use default ratio of 1.0 + if (std::isnan(ratio_arg) || std::isinf(ratio_arg) || ratio_arg < 0.0f || ratio_arg > 1.0f) + { + OTEL_INTERNAL_LOG_WARN("Environment variable <" << kTracesSamplerArgEnv + << "> has invalid value <" << ratio_arg + << "> for parentbased_traceidratio sampler, " + << "using default ratio of 1.0"); + ratio_arg = static_cast(kDefaultTraceIdRatio); + } + auto root_sampler = TraceIdRatioBasedSamplerFactory::Create(ratio_arg); + return ParentBasedSamplerFactory::Create(std::shared_ptr(root_sampler.release())); + } + else if (CaseInsensitiveAsciiEquals(sampler_name, "parentbased_always_on")) + { + auto root_sampler = AlwaysOnSamplerFactory::Create(); + return ParentBasedSamplerFactory::Create(std::shared_ptr(root_sampler.release())); + } + else if (CaseInsensitiveAsciiEquals(sampler_name, "parentbased_always_off")) + { + auto root_sampler = AlwaysOffSamplerFactory::Create(); + return ParentBasedSamplerFactory::Create(std::shared_ptr(root_sampler.release())); + } + else + { + // Unknown sampler name + OTEL_INTERNAL_LOG_WARN("Environment variable <" << kTracesSamplerEnv << "> has unknown value <" + << sampler_name + << ">, falling back to AlwaysOnSampler"); + return AlwaysOnSamplerFactory::Create(); + } +} + +} // namespace sampler_env + +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/sdk/src/trace/tracer_context.cc b/sdk/src/trace/tracer_context.cc index e9472fff83..2f92952315 100644 --- a/sdk/src/trace/tracer_context.cc +++ b/sdk/src/trace/tracer_context.cc @@ -13,6 +13,7 @@ #include "opentelemetry/sdk/trace/multi_span_processor.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" #include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -38,7 +39,12 @@ TracerContext::TracerContext( processor_(std::unique_ptr(new MultiSpanProcessor(std::move(processors)))), tracer_configurator_(std::move(tracer_configurator)), span_limits_(span_limits) -{} +{ + if (!sampler_) + { + sampler_ = sampler_env::GetSamplerFromEnv(); + } +} Sampler &TracerContext::GetSampler() const noexcept { diff --git a/sdk/src/trace/tracer_context_factory.cc b/sdk/src/trace/tracer_context_factory.cc index 9472dc041f..acf26f5f93 100644 --- a/sdk/src/trace/tracer_context_factory.cc +++ b/sdk/src/trace/tracer_context_factory.cc @@ -11,6 +11,7 @@ #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/random_id_generator_factory.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" #include "opentelemetry/sdk/trace/samplers/always_on_factory.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -34,7 +35,7 @@ std::unique_ptr TracerContextFactory::Create( std::vector> &&processors, const opentelemetry::sdk::resource::Resource &resource) { - auto sampler = AlwaysOnSamplerFactory::Create(); + auto sampler = sampler_env::GetSamplerFromEnv(); return Create(std::move(processors), resource, std::move(sampler)); } diff --git a/sdk/src/trace/tracer_provider.cc b/sdk/src/trace/tracer_provider.cc index ac11b97045..ced38b72ee 100644 --- a/sdk/src/trace/tracer_provider.cc +++ b/sdk/src/trace/tracer_provider.cc @@ -18,6 +18,7 @@ #include "opentelemetry/sdk/trace/id_generator.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" #include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_config.h" @@ -82,6 +83,10 @@ TracerProvider::TracerProvider( SpanLimits span_limits) : noop_tracer_(CreateNoopTracerFallback()) { + if (!sampler) + { + sampler = sampler_env::GetSamplerFromEnv(); + } std::vector> processors; processors.push_back(std::move(processor)); context_ = std::make_shared(std::move(processors), resource, std::move(sampler), @@ -98,15 +103,20 @@ TracerProvider::TracerProvider( std::unique_ptr id_generator, std::unique_ptr> tracer_configurator, SpanLimits span_limits) - : context_(std::make_shared(std::move(processors), + : noop_tracer_(CreateNoopTracerFallback()) +{ + if (!sampler) + { + sampler = sampler_env::GetSamplerFromEnv(); + } + context_ = std::make_shared(std::move(processors), resource, std::move(sampler), std::move(id_generator), std::move(tracer_configurator), - span_limits)), - noop_tracer_(CreateNoopTracerFallback()), - initialized_(true) -{} + span_limits); + initialized_ = true; +} TracerProvider::~TracerProvider() { diff --git a/sdk/src/trace/tracer_provider_factory.cc b/sdk/src/trace/tracer_provider_factory.cc index 6ae418430c..6fceb2b126 100644 --- a/sdk/src/trace/tracer_provider_factory.cc +++ b/sdk/src/trace/tracer_provider_factory.cc @@ -11,6 +11,7 @@ #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/random_id_generator_factory.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" #include "opentelemetry/sdk/trace/samplers/always_on_factory.h" #include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" @@ -36,7 +37,7 @@ std::unique_ptr TracerProviderFactory std::unique_ptr processor, const opentelemetry::sdk::resource::Resource &resource) { - auto sampler = AlwaysOnSamplerFactory::Create(); + auto sampler = sampler_env::GetSamplerFromEnv(); return Create(std::move(processor), resource, std::move(sampler)); } @@ -100,7 +101,7 @@ std::unique_ptr TracerProviderFactory std::vector> &&processors, const opentelemetry::sdk::resource::Resource &resource) { - auto sampler = AlwaysOnSamplerFactory::Create(); + auto sampler = sampler_env::GetSamplerFromEnv(); return Create(std::move(processors), resource, std::move(sampler)); } diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 35a3a4a685..4a0d442634 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -236,6 +236,21 @@ cc_test( ], ) +cc_test( + name = "sampler_env_test", + srcs = [ + "sampler_env_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "tracer_config_test", srcs = [ diff --git a/sdk/test/trace/CMakeLists.txt b/sdk/test/trace/CMakeLists.txt index 4641fa76a5..0624d568f0 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -16,6 +16,7 @@ foreach( probability_sampler_test trace_id_ratio_sampler_test composable_sampler_test + sampler_env_test batch_span_processor_test batch_span_processor_test_stress tracer_config_test) diff --git a/sdk/test/trace/sampler_env_test.cc b/sdk/test/trace/sampler_env_test.cc new file mode 100644 index 0000000000..bb9af7b8b1 --- /dev/null +++ b/sdk/test/trace/sampler_env_test.cc @@ -0,0 +1,284 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include + +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/sampler_env.h" +#include "opentelemetry/sdk/trace/samplers/always_off_factory.h" +#include "opentelemetry/sdk/trace/samplers/always_on_factory.h" +#include "opentelemetry/sdk/trace/samplers/parent_factory.h" +#include "opentelemetry/sdk/trace/samplers/trace_id_ratio_factory.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/trace_flags.h" + +#if defined(_MSC_VER) +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +#endif + +using opentelemetry::sdk::trace::AlwaysOffSamplerFactory; +using opentelemetry::sdk::trace::AlwaysOnSamplerFactory; +using opentelemetry::sdk::trace::ParentBasedSamplerFactory; +using opentelemetry::sdk::trace::TraceIdRatioBasedSamplerFactory; +using opentelemetry::sdk::trace::sampler_env::GetSamplerFromEnv; + +#ifndef NO_GETENV + +class SamplerEnvTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Clean up any existing environment variables + unsetenv("OTEL_TRACES_SAMPLER"); + unsetenv("OTEL_TRACES_SAMPLER_ARG"); + } + + void TearDown() override + { + // Clean up environment variables after each test + unsetenv("OTEL_TRACES_SAMPLER"); + unsetenv("OTEL_TRACES_SAMPLER_ARG"); + } +}; + +TEST_F(SamplerEnvTest, UnsetVariablesReturnsAlwaysOnSampler) +{ + auto sampler = GetSamplerFromEnv(); + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, AlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "always_on", 1); + auto sampler = GetSamplerFromEnv(); + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, AlwaysOffSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "always_off", 1); + auto sampler = GetSamplerFromEnv(); + EXPECT_EQ(sampler->GetDescription(), AlwaysOffSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, TraceIdRatioSamplerWithValidRatio) +{ + setenv("OTEL_TRACES_SAMPLER", "traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "0.5", 1); + auto sampler = GetSamplerFromEnv(); + EXPECT_EQ(sampler->GetDescription(), + TraceIdRatioBasedSamplerFactory::Create(0.5)->GetDescription()); +} + +TEST_F(SamplerEnvTest, TraceIdRatioSamplerWithDefaultRatio) +{ + setenv("OTEL_TRACES_SAMPLER", "traceidratio", 1); + // OTEL_TRACES_SAMPLER_ARG not set, should default to 1.0 + auto sampler = GetSamplerFromEnv(); + EXPECT_EQ(sampler->GetDescription(), + TraceIdRatioBasedSamplerFactory::Create(1.0)->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedTraceIdRatioSamplerWithValidRatio) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "0.5", 1); + auto sampler = GetSamplerFromEnv(); + auto root_sampler = TraceIdRatioBasedSamplerFactory::Create(0.5); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedTraceIdRatioSamplerWithDefaultRatio) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_traceidratio", 1); + // OTEL_TRACES_SAMPLER_ARG not set, should default to 1.0 + auto sampler = GetSamplerFromEnv(); + auto root_sampler = TraceIdRatioBasedSamplerFactory::Create(1.0); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_always_on", 1); + auto sampler = GetSamplerFromEnv(); + auto root_sampler = AlwaysOnSamplerFactory::Create(); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedAlwaysOffSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_always_off", 1); + auto sampler = GetSamplerFromEnv(); + auto root_sampler = AlwaysOffSamplerFactory::Create(); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, UnknownSamplerNameReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "unknown_sampler", 1); + auto sampler = GetSamplerFromEnv(); + // Should fall back to AlwaysOnSampler for unknown names + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, TraceIdRatioWithNegativeRatioReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "-0.5", 1); + auto sampler = GetSamplerFromEnv(); + // Should fall back to AlwaysOnSampler for invalid ratio + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, TraceIdRatioWithRatioGreaterThanOneReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "1.5", 1); + auto sampler = GetSamplerFromEnv(); + // Should fall back to AlwaysOnSampler for invalid ratio + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, TraceIdRatioWithNaNReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "nan", 1); + auto sampler = GetSamplerFromEnv(); + // Should fall back to AlwaysOnSampler for invalid ratio + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, TraceIdRatioWithInfinityReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "inf", 1); + auto sampler = GetSamplerFromEnv(); + // Should fall back to AlwaysOnSampler for invalid ratio + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedTraceIdRatioWithInvalidRatioReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "1.5", 1); + auto sampler = GetSamplerFromEnv(); + // Should use default ratio of 1.0 for invalid ratio, not fall back to AlwaysOnSampler + auto root_sampler = TraceIdRatioBasedSamplerFactory::Create(1.0); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedTraceIdRatioWithNegativeRatioReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "-0.5", 1); + auto sampler = GetSamplerFromEnv(); + // Should use default ratio of 1.0 for invalid ratio, not fall back to AlwaysOnSampler + auto root_sampler = TraceIdRatioBasedSamplerFactory::Create(1.0); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedTraceIdRatioWithNaNReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "nan", 1); + auto sampler = GetSamplerFromEnv(); + // Should use default ratio of 1.0 for invalid ratio, not fall back to AlwaysOnSampler + auto root_sampler = TraceIdRatioBasedSamplerFactory::Create(1.0); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, ParentBasedTraceIdRatioWithMalformedRatio) +{ + setenv("OTEL_TRACES_SAMPLER", "parentbased_traceidratio", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "0.5abc", 1); + auto sampler = GetSamplerFromEnv(); + // Malformed ratio should be treated as unset, use default ratio of 1.0 + auto root_sampler = TraceIdRatioBasedSamplerFactory::Create(1.0); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, EmptySamplerNameReturnsAlwaysOnSampler) +{ + setenv("OTEL_TRACES_SAMPLER", "", 1); + auto sampler = GetSamplerFromEnv(); + // Empty string should be treated as unset + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, CaseInsensitiveSamplerNames) +{ + setenv("OTEL_TRACES_SAMPLER", "ALWAYS_ON", 1); + auto sampler = GetSamplerFromEnv(); + // Should match case-insensitively and return AlwaysOnSampler + EXPECT_EQ(sampler->GetDescription(), AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, CaseInsensitiveSamplerNamesAlwaysOff) +{ + setenv("OTEL_TRACES_SAMPLER", "ALWAYS_OFF", 1); + auto sampler = GetSamplerFromEnv(); + // Should match case-insensitively and return AlwaysOffSampler + EXPECT_EQ(sampler->GetDescription(), AlwaysOffSamplerFactory::Create()->GetDescription()); +} + +TEST_F(SamplerEnvTest, CaseInsensitiveSamplerNamesTraceIdRatio) +{ + setenv("OTEL_TRACES_SAMPLER", "TRACEIDRATIO", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "0.5", 1); + auto sampler = GetSamplerFromEnv(); + // Should match case-insensitively and return TraceIdRatioBasedSampler + EXPECT_EQ(sampler->GetDescription(), + TraceIdRatioBasedSamplerFactory::Create(0.5)->GetDescription()); +} + +TEST_F(SamplerEnvTest, CaseInsensitiveSamplerNamesParentBased) +{ + setenv("OTEL_TRACES_SAMPLER", "PARENTBASED_ALWAYS_ON", 1); + auto sampler = GetSamplerFromEnv(); + auto root_sampler = AlwaysOnSamplerFactory::Create(); + auto expected_sampler = ParentBasedSamplerFactory::Create( + std::shared_ptr(root_sampler.release())); + // Should match case-insensitively and return ParentBasedSampler + EXPECT_EQ(sampler->GetDescription(), expected_sampler->GetDescription()); +} + +TEST_F(SamplerEnvTest, ExplicitSamplerOverridesEnvironmentVariables) +{ + setenv("OTEL_TRACES_SAMPLER", "always_off", 1); + setenv("OTEL_TRACES_SAMPLER_ARG", "0.5", 1); + + // Create a sampler directly - this should override environment variables + auto explicit_sampler = AlwaysOnSamplerFactory::Create(); + auto description = explicit_sampler->GetDescription(); + + // Environment variable should still return AlwaysOffSampler when called + auto env_sampler = GetSamplerFromEnv(); + EXPECT_EQ(env_sampler->GetDescription(), AlwaysOffSamplerFactory::Create()->GetDescription()); + + // But explicit sampler should be AlwaysOnSampler + EXPECT_EQ(description, AlwaysOnSamplerFactory::Create()->GetDescription()); +} + +#endif // NO_GETENV \ No newline at end of file