From 1f21f0b8ece05b2b4978fea3571ebceaafae3cea Mon Sep 17 00:00:00 2001 From: Dmitrii Korzhimanov <52735121+HrMathematiker@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:28:11 +0200 Subject: [PATCH] [SDK] Read span limits from environment variables Adds span_limits_env::GetSpanLimitsFromEnv(), which builds a SpanLimits from the environment variables defined in the specification (OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT, OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, and the general OTEL_ATTRIBUTE_* fallbacks), and uses it as the default in the TracerProviderFactory::Create overloads that previously defaulted to SpanLimits::NoLimits(). A field whose variable is unset keeps its NoLimits() value, so behavior is unchanged when no variables are set; explicitly passed SpanLimits keep taking precedence. --- CHANGELOG.md | 6 + .../opentelemetry/sdk/trace/span_limits.h | 18 +++ sdk/src/trace/CMakeLists.txt | 1 + sdk/src/trace/span_limits.cc | 75 +++++++++++++ sdk/src/trace/tracer_provider_factory.cc | 4 +- sdk/test/trace/tracer_provider_test.cc | 103 ++++++++++++++++++ 6 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 sdk/src/trace/span_limits.cc diff --git a/CHANGELOG.md b/CHANGELOG.md index 468fbd8f7e..c6d287c8a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ Increment the: ## [Unreleased] +* [SDK] Read span limits from the environment variables defined in the + specification (OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, + OTEL_SPAN_LINK_COUNT_LIMIT, ...) in the TracerProviderFactory overloads + that do not receive explicit SpanLimits + [#4258](https://github.com/open-telemetry/opentelemetry-cpp/pull/4258) + * docs: update supported development platforms [#4260](https://github.com/open-telemetry/opentelemetry-cpp/pull/4260) diff --git a/sdk/include/opentelemetry/sdk/trace/span_limits.h b/sdk/include/opentelemetry/sdk/trace/span_limits.h index 3f2db6e3cc..cd0feafe61 100644 --- a/sdk/include/opentelemetry/sdk/trace/span_limits.h +++ b/sdk/include/opentelemetry/sdk/trace/span_limits.h @@ -63,6 +63,24 @@ struct SpanLimits } }; +namespace span_limits_env +{ + +/** + * @brief Returns span limits read from the environment variables defined in the + * OpenTelemetry specification + * (https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#span-limits). + * + * A field whose environment variable is not set keeps its SpanLimits::NoLimits() value, so with + * no variables set the result equals SpanLimits::NoLimits() and SDK behavior is unchanged. The + * span-specific variables (OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, + * OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT) take precedence over the general ones + * (OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_ATTRIBUTE_COUNT_LIMIT). + */ +OPENTELEMETRY_EXPORT SpanLimits GetSpanLimitsFromEnv(); + +} // namespace span_limits_env + } // namespace trace } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index 0d3e69d96b..ebd1aebf06 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -16,6 +16,7 @@ add_library( batch_span_processor_options.cc simple_processor_factory.cc span_data.cc + span_limits.cc samplers/always_on_factory.cc samplers/always_off_factory.cc samplers/parent.cc diff --git a/sdk/src/trace/span_limits.cc b/sdk/src/trace/span_limits.cc new file mode 100644 index 0000000000..b0edf924f2 --- /dev/null +++ b/sdk/src/trace/span_limits.cc @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/sdk/trace/span_limits.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +namespace span_limits_env +{ + +// Environment variable names, see +// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#span-limits +static constexpr const char *kAttributeValueLengthLimitEnv = "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"; +static constexpr const char *kAttributeCountLimitEnv = "OTEL_ATTRIBUTE_COUNT_LIMIT"; +static constexpr const char *kSpanAttributeValueLengthLimitEnv = + "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT"; +static constexpr const char *kSpanAttributeCountLimitEnv = "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"; +static constexpr const char *kSpanEventCountLimitEnv = "OTEL_SPAN_EVENT_COUNT_LIMIT"; +static constexpr const char *kSpanLinkCountLimitEnv = "OTEL_SPAN_LINK_COUNT_LIMIT"; +static constexpr const char *kEventAttributeCountLimitEnv = "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"; +static constexpr const char *kLinkAttributeCountLimitEnv = "OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"; + +namespace +{ + +void UpdateFromEnv(const char *env_var_name, std::uint32_t &limit) +{ + std::uint32_t value{}; + if (opentelemetry::sdk::common::GetUintEnvironmentVariable(env_var_name, value)) + { + limit = value; + } +} + +void UpdateFromEnv(const char *env_var_name, std::size_t &limit) +{ + std::uint32_t value{}; + if (opentelemetry::sdk::common::GetUintEnvironmentVariable(env_var_name, value)) + { + limit = static_cast(value); + } +} + +} // namespace + +SpanLimits GetSpanLimitsFromEnv() +{ + SpanLimits limits = SpanLimits::NoLimits(); + + // General attribute limits first; the span-specific variables below take precedence. + UpdateFromEnv(kAttributeValueLengthLimitEnv, limits.attribute_value_length_limit); + UpdateFromEnv(kAttributeCountLimitEnv, limits.attribute_count_limit); + + UpdateFromEnv(kSpanAttributeValueLengthLimitEnv, limits.attribute_value_length_limit); + UpdateFromEnv(kSpanAttributeCountLimitEnv, limits.attribute_count_limit); + UpdateFromEnv(kSpanEventCountLimitEnv, limits.event_count_limit); + UpdateFromEnv(kSpanLinkCountLimitEnv, limits.link_count_limit); + UpdateFromEnv(kEventAttributeCountLimitEnv, limits.event_attribute_count_limit); + UpdateFromEnv(kLinkAttributeCountLimitEnv, limits.link_attribute_count_limit); + + return limits; +} + +} // namespace span_limits_env +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/tracer_provider_factory.cc b/sdk/src/trace/tracer_provider_factory.cc index 06f792c659..6ae418430c 100644 --- a/sdk/src/trace/tracer_provider_factory.cc +++ b/sdk/src/trace/tracer_provider_factory.cc @@ -71,7 +71,7 @@ std::unique_ptr TracerProviderFactory std::unique_ptr> tracer_configurator) { return Create(std::move(processor), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator), SpanLimits::NoLimits()); + std::move(tracer_configurator), span_limits_env::GetSpanLimitsFromEnv()); } std::unique_ptr TracerProviderFactory::Create( @@ -135,7 +135,7 @@ std::unique_ptr TracerProviderFactory std::unique_ptr> tracer_configurator) { return Create(std::move(processors), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator), SpanLimits::NoLimits()); + std::move(tracer_configurator), span_limits_env::GetSpanLimitsFromEnv()); } std::unique_ptr TracerProviderFactory::Create( diff --git a/sdk/test/trace/tracer_provider_test.cc b/sdk/test/trace/tracer_provider_test.cc index 66efbbf522..bd50879c98 100644 --- a/sdk/test/trace/tracer_provider_test.cc +++ b/sdk/test/trace/tracer_provider_test.cc @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include #include @@ -37,6 +38,12 @@ #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/tracer.h" +#if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +#endif + #if OPENTELEMETRY_ABI_VERSION_NO >= 2 # include # include @@ -569,6 +576,102 @@ TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreate) EXPECT_EQ(stored.link_attribute_count_limit, limits.link_attribute_count_limit); } +namespace +{ + +void UnsetSpanLimitsEnv() +{ + unsetenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"); + unsetenv("OTEL_ATTRIBUTE_COUNT_LIMIT"); + unsetenv("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT"); + unsetenv("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"); + unsetenv("OTEL_SPAN_EVENT_COUNT_LIMIT"); + unsetenv("OTEL_SPAN_LINK_COUNT_LIMIT"); + unsetenv("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"); + unsetenv("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"); +} + +} // namespace + +TEST(TracerProvider, SpanLimitsFromEnvUnsetIsNoLimits) +{ + UnsetSpanLimitsEnv(); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + const SpanLimits no_limits = SpanLimits::NoLimits(); + EXPECT_EQ(limits.attribute_count_limit, no_limits.attribute_count_limit); + EXPECT_EQ(limits.attribute_value_length_limit, no_limits.attribute_value_length_limit); + EXPECT_EQ(limits.event_count_limit, no_limits.event_count_limit); + EXPECT_EQ(limits.link_count_limit, no_limits.link_count_limit); + EXPECT_EQ(limits.event_attribute_count_limit, no_limits.event_attribute_count_limit); + EXPECT_EQ(limits.link_attribute_count_limit, no_limits.link_attribute_count_limit); +} + +TEST(TracerProvider, SpanLimitsFromEnvReadsVariables) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "100", 1); + setenv("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "5", 1); + setenv("OTEL_SPAN_EVENT_COUNT_LIMIT", "3", 1); + setenv("OTEL_SPAN_LINK_COUNT_LIMIT", "2", 1); + setenv("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", "4", 1); + setenv("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", "6", 1); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + EXPECT_EQ(limits.attribute_value_length_limit, 100u); + EXPECT_EQ(limits.attribute_count_limit, 5u); + EXPECT_EQ(limits.event_count_limit, 3u); + EXPECT_EQ(limits.link_count_limit, 2u); + EXPECT_EQ(limits.event_attribute_count_limit, 4u); + EXPECT_EQ(limits.link_attribute_count_limit, 6u); + + UnsetSpanLimitsEnv(); +} + +TEST(TracerProvider, SpanLimitsFromEnvGeneralAttributeVariablesApply) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "50", 1); + setenv("OTEL_ATTRIBUTE_COUNT_LIMIT", "10", 1); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + EXPECT_EQ(limits.attribute_value_length_limit, 50u); + EXPECT_EQ(limits.attribute_count_limit, 10u); + EXPECT_EQ(limits.event_count_limit, (std::numeric_limits::max)()); + + UnsetSpanLimitsEnv(); +} + +TEST(TracerProvider, SpanLimitsFromEnvSpanSpecificTakesPrecedence) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "50", 1); + setenv("OTEL_ATTRIBUTE_COUNT_LIMIT", "10", 1); + setenv("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "100", 1); + setenv("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "20", 1); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + EXPECT_EQ(limits.attribute_value_length_limit, 100u); + EXPECT_EQ(limits.attribute_count_limit, 20u); + + UnsetSpanLimitsEnv(); +} + +TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreateDefaultReadsEnv) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_SPAN_EVENT_COUNT_LIMIT", "42", 1); + + auto provider = TracerProviderFactory::Create(std::make_unique(nullptr)); + + const auto &limits = provider->GetSpanLimits(); + EXPECT_EQ(limits.event_count_limit, 42u); + EXPECT_EQ(limits.attribute_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.link_count_limit, (std::numeric_limits::max)()); + + UnsetSpanLimitsEnv(); +} + TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreateDefault) { auto provider = TracerProviderFactory::Create(std::make_unique(nullptr));