From 0948734559a26f61b7ba32d615f2d0ca3cea19bc Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Thu, 9 Jul 2026 15:06:46 -0400 Subject: [PATCH 1/5] add span limits configuration --- .../otlp/otlp_grpc_exporter_options.h | 6 +- .../otlp/otlp_http_exporter_options.h | 6 +- .../exporters/otlp/otlp_recordable.h | 37 ++- exporters/otlp/src/otlp_recordable.cc | 40 ++- exporters/otlp/test/otlp_recordable_test.cc | 272 ++++++++++++++++++ .../sdk/common/attribute_utils.h | 80 +++++- .../sdk/trace/multi_recordable.h | 2 + .../opentelemetry/sdk/trace/recordable.h | 8 + .../opentelemetry/sdk/trace/span_data.h | 45 ++- .../opentelemetry/sdk/trace/span_limits.h | 68 +++++ sdk/include/opentelemetry/sdk/trace/tracer.h | 4 + .../opentelemetry/sdk/trace/tracer_context.h | 11 +- .../opentelemetry/sdk/trace/tracer_provider.h | 14 +- .../sdk/trace/tracer_provider_factory.h | 17 ++ sdk/src/configuration/sdk_builder.cc | 37 ++- sdk/src/trace/multi_recordable.cc | 8 + sdk/src/trace/span.cc | 1 + sdk/src/trace/span_data.cc | 123 +++++++- sdk/src/trace/tracer_context.cc | 23 +- sdk/src/trace/tracer_provider.cc | 23 +- sdk/src/trace/tracer_provider_factory.cc | 29 +- sdk/test/common/attribute_utils_test.cc | 20 ++ sdk/test/configuration/BUILD | 20 ++ sdk/test/configuration/CMakeLists.txt | 1 + sdk/test/configuration/sdk_builder_test.cc | 68 +++++ sdk/test/trace/span_data_test.cc | 139 +++++++++ sdk/test/trace/tracer_provider_test.cc | 74 ++++- sdk/test/trace/tracer_test.cc | 54 +++- 28 files changed, 1157 insertions(+), 73 deletions(-) create mode 100644 sdk/include/opentelemetry/sdk/trace/span_limits.h create mode 100644 sdk/test/configuration/sdk_builder_test.cc diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h index 2bb4ab6542..94d028978f 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h @@ -35,7 +35,11 @@ struct OPENTELEMETRY_EXPORT OtlpGrpcExporterOptions : public OtlpGrpcClientOptio OtlpGrpcExporterOptions &operator=(OtlpGrpcExporterOptions &&) = default; ~OtlpGrpcExporterOptions() override; - /** Collection Limits. No limit by default. */ + /** Collection Limits. No limit by default. + * @deprecated Configure span limits via TracerProviderFactory::Create(), SpanLimitsConfiguration, + * or the YAML `tracer_provider.limits` node instead. These fields will be + * removed in a future release. + */ std::uint32_t max_attributes = (std::numeric_limits::max)(); std::uint32_t max_events = (std::numeric_limits::max)(); std::uint32_t max_links = (std::numeric_limits::max)(); diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h index 58fad0485e..b146fd3b0a 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h @@ -127,7 +127,11 @@ struct OPENTELEMETRY_EXPORT OtlpHttpExporterOptions /** The backoff will be multiplied by this value after each retry attempt. */ float retry_policy_backoff_multiplier{}; - /** Collection Limits. No limit by default. */ + /** Collection Limits. No limit by default. + * @deprecated Configure span limits via TracerProviderFactory::Create(), SpanLimitsConfiguration, + * or the YAML `tracer_provider.limits` node instead. These fields will be + * removed in a future release. + */ std::uint32_t max_attributes = (std::numeric_limits::max)(); std::uint32_t max_events = (std::numeric_limits::max)(); std::uint32_t max_links = (std::numeric_limits::max)(); diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h index b63bc5ff7d..d4c30483f8 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h @@ -23,6 +23,7 @@ #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -37,17 +38,25 @@ namespace otlp class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable { public: + /** + * Construct with optional span limits parameters. + * + * @deprecated Configure span limits via TracerProviderFactory::Create(), SpanLimitsConfiguration, + * or the YAML `tracer_provider.limits` node instead. These optional span limit params will be + * removed in a future release. + */ explicit OtlpRecordable( std::uint32_t max_attributes = (std::numeric_limits::max)(), std::uint32_t max_events = (std::numeric_limits::max)(), std::uint32_t max_links = (std::numeric_limits::max)(), std::uint32_t max_attributes_per_event = (std::numeric_limits::max)(), std::uint32_t max_attributes_per_link = (std::numeric_limits::max)()) - : max_attributes_(max_attributes), - max_events_(max_events), - max_links_(max_links), - max_attributes_per_event_(max_attributes_per_event), - max_attributes_per_link_(max_attributes_per_link) + : span_limits_{max_attributes, + (std::numeric_limits::max)(), + max_events, + max_links, + max_attributes_per_event, + max_attributes_per_link} {} proto::trace::v1::Span &span() noexcept { return span_; } @@ -92,6 +101,17 @@ class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable void SetDuration(std::chrono::nanoseconds duration) noexcept override; + /** + * Set span limits. + * + * Until the deprecated OTLP exporter option values are removed, the effective limit for each + * field is the minimum (most restrictive) of the deprecated otlp exporter option values (passed + * to the constructor) and the values set by calling this method. + * + * @param limits The span limits to set. + */ + void SetSpanLimits(const opentelemetry::sdk::trace::SpanLimits &limits) noexcept override; + void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) noexcept override; @@ -100,11 +120,8 @@ class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable const opentelemetry::sdk::resource::Resource *resource_ = nullptr; const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_ = nullptr; - std::uint32_t max_attributes_; - std::uint32_t max_events_; - std::uint32_t max_links_; - std::uint32_t max_attributes_per_event_; - std::uint32_t max_attributes_per_link_; + opentelemetry::sdk::trace::SpanLimits span_limits_{ + opentelemetry::sdk::trace::SpanLimits::NoLimits()}; }; } // namespace otlp } // namespace exporter diff --git a/exporters/otlp/src/otlp_recordable.cc b/exporters/otlp/src/otlp_recordable.cc index 9270e17383..017fc51905 100644 --- a/exporters/otlp/src/otlp_recordable.cc +++ b/exporters/otlp/src/otlp_recordable.cc @@ -1,8 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include +#include #include +#include #include #include "opentelemetry/common/attribute_value.h" @@ -15,6 +16,7 @@ #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -114,24 +116,40 @@ void OtlpRecordable::SetResource(const sdk::resource::Resource &resource) noexce resource_ = &resource; } +void OtlpRecordable::SetSpanLimits(const opentelemetry::sdk::trace::SpanLimits &limits) noexcept +{ + // Update the span limits to be the minimum of the current limits and the new limits + // The initial limits are set on construction from the deprecated span limits otlp options. + // TODO: replace with a simple copy once the deprecated options are removed. + span_limits_ = { + (std::min)(span_limits_.attribute_count_limit, limits.attribute_count_limit), + (std::min)(span_limits_.attribute_value_length_limit, limits.attribute_value_length_limit), + (std::min)(span_limits_.event_count_limit, limits.event_count_limit), + (std::min)(span_limits_.link_count_limit, limits.link_count_limit), + (std::min)(span_limits_.event_attribute_count_limit, limits.event_attribute_count_limit), + (std::min)(span_limits_.link_attribute_count_limit, limits.link_attribute_count_limit), + }; +} + void OtlpRecordable::SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept { - if (static_cast(span_.attributes_size()) >= max_attributes_) + if (static_cast(span_.attributes_size()) >= span_limits_.attribute_count_limit) { span_.set_dropped_attributes_count(span_.dropped_attributes_count() + 1); return; } auto *attribute = span_.add_attributes(); - OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value, false); + OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value, false, + span_limits_.attribute_value_length_limit); } void OtlpRecordable::AddEvent(nostd::string_view name, common::SystemTimestamp timestamp, const common::KeyValueIterable &attributes) noexcept { - if (static_cast(span_.events_size()) >= max_events_) + if (static_cast(span_.events_size()) >= span_limits_.event_count_limit) { span_.set_dropped_events_count(span_.dropped_events_count() + 1); return; @@ -142,12 +160,14 @@ void OtlpRecordable::AddEvent(nostd::string_view name, event->set_time_unix_nano(timestamp.time_since_epoch().count()); attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { - if (static_cast(event->attributes_size()) >= max_attributes_per_event_) + if (static_cast(event->attributes_size()) >= + span_limits_.event_attribute_count_limit) { event->set_dropped_attributes_count(event->dropped_attributes_count() + 1); return true; } - OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value, false); + OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value, false, + span_limits_.attribute_value_length_limit); return true; }); } @@ -155,7 +175,7 @@ void OtlpRecordable::AddEvent(nostd::string_view name, void OtlpRecordable::AddLink(const trace::SpanContext &span_context, const common::KeyValueIterable &attributes) noexcept { - if (static_cast(span_.links_size()) >= max_links_) + if (static_cast(span_.links_size()) >= span_limits_.link_count_limit) { span_.set_dropped_links_count(span_.dropped_links_count() + 1); return; @@ -167,12 +187,14 @@ void OtlpRecordable::AddLink(const trace::SpanContext &span_context, trace::SpanId::kSize); link->set_trace_state(span_context.trace_state()->ToHeader()); attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { - if (static_cast(link->attributes_size()) >= max_attributes_per_link_) + if (static_cast(link->attributes_size()) >= + span_limits_.link_attribute_count_limit) { link->set_dropped_attributes_count(link->dropped_attributes_count() + 1); return true; } - OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value, false); + OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value, false, + span_limits_.attribute_value_length_limit); return true; }); } diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index 56c4015fe2..bb97dbe8ef 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -24,6 +24,7 @@ #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -657,6 +658,7 @@ TEST(OtlpRecordable, SetUint64ArrayOverflowAsStringPerSpec) EXPECT_EQ(array_v.values(1).string_value(), std::to_string(overflow_val)); } +// TODO: remove this test case once the deprecated otlp options span limit fields are removed. TEST(OtlpRecordableTest, TestCollectionLimits) { // Initialize recordable with strict limits: @@ -698,6 +700,7 @@ TEST(OtlpRecordableTest, TestCollectionLimits) EXPECT_EQ(event_list[0].dropped_attributes_count(), 1); } +// TODO: remove this test case once the deprecated otlp options span limit fields are removed. TEST(OtlpRecordableTest, TestLinkLimits) { // Limits: Max Links: 2, Max Attributes Per Link: 1 @@ -779,6 +782,275 @@ TEST(OtlpRecordable, PopulateRequestSameScope) EXPECT_EQ(req.resource_spans(0).scope_spans(0).spans_size(), 2); EXPECT_EQ(req.resource_spans(0).scope_spans(0).scope().name(), "lib"); } + +TEST(OtlpRecordable, SpanLimits) +{ + OtlpRecordable data; + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 1; + limits.attribute_value_length_limit = 2; + limits.link_count_limit = 1; + limits.link_attribute_count_limit = 1; + limits.event_count_limit = 1; + limits.event_attribute_count_limit = 1; + + constexpr const char *kKey1 = "one"; + constexpr const char *kKey2 = "two"; + constexpr const char *kValue1 = "1234"; + constexpr const char *kValue2 = "5678"; + + std::map attribute_collection{{kKey1, kValue1}, {kKey2, kValue2}}; + + const auto &proto_span = data.span(); + + data.SetSpanLimits(limits); + + // span attribute count and length limits + data.SetAttribute(kKey1, kValue1); + data.SetAttribute(kKey2, kValue2); + + EXPECT_EQ(proto_span.attributes_size(), 1); + EXPECT_EQ(proto_span.dropped_attributes_count(), 1); + EXPECT_EQ(proto_span.attributes(0).value().string_value(), "12"); + + // event count and per-event attribute count limits + data.AddEvent("event1", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + data.AddEvent("event2", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + + ASSERT_EQ(proto_span.events_size(), 1); + EXPECT_EQ(proto_span.dropped_events_count(), 1); + const auto &event = proto_span.events(0); + EXPECT_EQ(event.dropped_attributes_count(), 1); + EXPECT_EQ(event.name(), "event1"); + const auto &event_attributes = event.attributes(); + EXPECT_EQ(event_attributes.size(), 1); + EXPECT_EQ(event_attributes.at(0).value().string_value(), "12"); + + // link count and per-link attribute count limits + data.AddLink(trace_api::SpanContext(true, false), common::MakeAttributes(attribute_collection)); + data.AddLink(trace_api::SpanContext(true, false), common::MakeAttributes(attribute_collection)); + ASSERT_EQ(proto_span.links_size(), 1); + EXPECT_EQ(proto_span.dropped_links_count(), 1); + const auto &link = proto_span.links(0); + EXPECT_EQ(link.dropped_attributes_count(), 1); + const auto &link_attributes = link.attributes(); + EXPECT_EQ(link_attributes.size(), 1); + EXPECT_EQ(link_attributes.at(0).value().string_value(), "12"); +} + +TEST(OtlpRecordable, SpanLimitsNoLimitDefault) +{ + constexpr std::uint32_t kMaxCount = 500; + OtlpRecordable recordable; + std::map attributes; + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + attributes["attribute_" + std::to_string(i)] = std::to_string(i); + } + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + recordable.SetAttribute("attribute_" + std::to_string(i), i); + recordable.AddEvent("event_" + std::to_string(i), std::chrono::system_clock::now(), + common::MakeAttributes(attributes)); + recordable.AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + } + + EXPECT_EQ(recordable.span().attributes_size(), kMaxCount); + EXPECT_EQ(recordable.span().dropped_attributes_count(), 0u); + EXPECT_EQ(recordable.span().events_size(), kMaxCount); + EXPECT_EQ(recordable.span().dropped_events_count(), 0u); + EXPECT_EQ(recordable.span().links_size(), kMaxCount); + EXPECT_EQ(recordable.span().dropped_links_count(), 0u); + EXPECT_EQ(recordable.span().events(0).dropped_attributes_count(), 0u); + EXPECT_EQ(recordable.span().links(0).dropped_attributes_count(), 0u); +} + +// TODO: remove this test case once the deprecated otlp options span limit fields are removed. +TEST(OtlpRecordable, SpanLimitsFieldsMerged) +{ + constexpr std::uint32_t kMaxCount = 10; + + constexpr std::uint32_t kOtlpAttributeCount = 2; + constexpr std::uint32_t kOtlpEventCount = 3; + constexpr std::uint32_t kOtlpLinkCount = 4; + constexpr std::uint32_t kOtlpEventAttributeCount = 5; + constexpr std::uint32_t kOtlpLinkAttributeCount = 6; + + trace_sdk::SpanLimits more_restricitve_limits; + more_restricitve_limits.attribute_count_limit = 1; + more_restricitve_limits.event_count_limit = 2; + more_restricitve_limits.link_count_limit = 3; + more_restricitve_limits.event_attribute_count_limit = 4; + more_restricitve_limits.link_attribute_count_limit = 5; + more_restricitve_limits.attribute_value_length_limit = 2; + + trace_sdk::SpanLimits less_restricitve_limits; + less_restricitve_limits.attribute_count_limit = 3; + less_restricitve_limits.event_count_limit = 4; + less_restricitve_limits.link_count_limit = 5; + less_restricitve_limits.event_attribute_count_limit = 6; + less_restricitve_limits.link_attribute_count_limit = 7; + less_restricitve_limits.attribute_value_length_limit = 6; + + auto make_recordable = [&](const trace_sdk::SpanLimits &limits) { + auto recordable = + std::make_unique(kOtlpAttributeCount, kOtlpEventCount, kOtlpLinkCount, + kOtlpEventAttributeCount, kOtlpLinkAttributeCount); + recordable->SetSpanLimits(limits); + return recordable; + }; + + auto make_attributes = []() { + std::map attributes; + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + attributes["attribute_" + std::to_string(i)] = static_cast(i); + } + return attributes; + }; + + const std::map empty_attributes; + const auto event_time = std::chrono::system_clock::time_point{std::chrono::seconds{1000000000}}; + + // attribute_count_limit: config limits are more restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(more_restricitve_limits); + const auto attributes = make_attributes(); + for (const auto &attr : attributes) + { + recordable->SetAttribute(attr.first, attr.second); + } + EXPECT_EQ(recordable->span().attributes_size(), + static_cast(more_restricitve_limits.attribute_count_limit)); + EXPECT_EQ(recordable->span().dropped_attributes_count(), + attributes.size() - more_restricitve_limits.attribute_count_limit); + } + // attribute_count_limit: config limits are less restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(less_restricitve_limits); + const auto attributes = make_attributes(); + for (const auto &attr : attributes) + { + recordable->SetAttribute(attr.first, attr.second); + } + EXPECT_EQ(recordable->span().attributes_size(), static_cast(kOtlpAttributeCount)); + EXPECT_EQ(recordable->span().dropped_attributes_count(), + attributes.size() - kOtlpAttributeCount); + } + + // attribute_value_length_limit: config limits are more restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(more_restricitve_limits); + recordable->SetAttribute("value", nostd::string_view("abcdefghij")); + EXPECT_EQ(recordable->span().attributes(0).value().string_value().size(), + more_restricitve_limits.attribute_value_length_limit); + } + // attribute_value_length_limit: config limits are less restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(less_restricitve_limits); + recordable->SetAttribute("value", nostd::string_view("abcdefghij")); + EXPECT_EQ(recordable->span().attributes(0).value().string_value().size(), + less_restricitve_limits.attribute_value_length_limit); + } + + // event_count_limit: config limits are more restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(more_restricitve_limits); + recordable->AddEvent("event_one", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_two", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_three", event_time, common::MakeAttributes(empty_attributes)); + EXPECT_EQ(recordable->span().events_size(), + static_cast(more_restricitve_limits.event_count_limit)); + EXPECT_EQ(recordable->span().dropped_events_count(), 1u); + } + // event_count_limit: config limits are less restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(less_restricitve_limits); + recordable->AddEvent("event_one", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_two", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_three", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_four", event_time, common::MakeAttributes(empty_attributes)); + EXPECT_EQ(recordable->span().events_size(), static_cast(kOtlpEventCount)); + EXPECT_EQ(recordable->span().dropped_events_count(), 1u); + } + + // link_count_limit: config limits are more restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(more_restricitve_limits); + for (std::uint32_t i = 0; i < kOtlpLinkCount; ++i) + { + recordable->AddLink(trace::SpanContext::GetInvalid(), + common::MakeAttributes(empty_attributes)); + } + EXPECT_EQ(recordable->span().links_size(), + static_cast(more_restricitve_limits.link_count_limit)); + EXPECT_EQ(recordable->span().dropped_links_count(), 1u); + } + // link_count_limit: config limits are less restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(less_restricitve_limits); + for (std::uint32_t i = 0; i < less_restricitve_limits.link_count_limit; ++i) + { + recordable->AddLink(trace::SpanContext::GetInvalid(), + common::MakeAttributes(empty_attributes)); + } + EXPECT_EQ(recordable->span().links_size(), static_cast(kOtlpLinkCount)); + EXPECT_EQ(recordable->span().dropped_links_count(), 1u); + } + + // event_attribute_count_limit: config limits are more restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(more_restricitve_limits); + const auto attributes = make_attributes(); + recordable->AddEvent("test_event", event_time, common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().events(0).attributes_size(), + static_cast(more_restricitve_limits.event_attribute_count_limit)); + EXPECT_EQ(recordable->span().events(0).dropped_attributes_count(), + attributes.size() - more_restricitve_limits.event_attribute_count_limit); + } + // event_attribute_count_limit: config limits are less restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(less_restricitve_limits); + const auto attributes = make_attributes(); + recordable->AddEvent("test_event", event_time, common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().events(0).attributes_size(), + static_cast(kOtlpEventAttributeCount)); + EXPECT_EQ(recordable->span().events(0).dropped_attributes_count(), + attributes.size() - kOtlpEventAttributeCount); + } + + // link_attribute_count_limit: config limits are more restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(more_restricitve_limits); + const auto attributes = make_attributes(); + recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().links(0).attributes_size(), + static_cast(more_restricitve_limits.link_attribute_count_limit)); + EXPECT_EQ(recordable->span().links(0).dropped_attributes_count(), + attributes.size() - more_restricitve_limits.link_attribute_count_limit); + } + // link_attribute_count_limit: config limits are less restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(less_restricitve_limits); + const auto attributes = make_attributes(); + recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().links(0).attributes_size(), + static_cast(kOtlpLinkAttributeCount)); + EXPECT_EQ(recordable->span().links(0).dropped_attributes_count(), + attributes.size() - kOtlpLinkAttributeCount); + } +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/common/attribute_utils.h b/sdk/include/opentelemetry/sdk/common/attribute_utils.h index c2e75a0f97..0a977f5f84 100644 --- a/sdk/include/opentelemetry/sdk/common/attribute_utils.h +++ b/sdk/include/opentelemetry/sdk/common/attribute_utils.h @@ -14,6 +14,10 @@ #include #include +#if OPENTELEMETRY_HAVE_EXCEPTIONS +# include +#endif + #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/nostd/span.h" @@ -276,6 +280,62 @@ struct AttributeEqualToVisitor } }; +/** + * Insert or assign a key-value pair into a map using map.insert_or_assign if available, or + * map.emplace otherwise. + * @param map The map to insert or assign the key-value pair into. + * @param key The key to insert or assign. + * @param value The value to insert or assign. + * @return A pair of an iterator to the element and a bool (true if the insertion took place). + */ +template +OPENTELEMETRY_MAYBE_UNUSED inline std::pair +AttributeInsertOrAssign(Map &map, opentelemetry::nostd::string_view key, Value &&value) noexcept +{ +#if __cplusplus >= 201703L + // Use insert_or_assign if C++17 is available + return map.insert_or_assign(std::string(key), std::forward(value)); +#else + auto result = map.emplace(std::string(key), typename Map::mapped_type{}); + result.first->second = std::forward(value); + return result; +#endif +} + +/** + * VisitVariant + * + * Invokes nostd::visit(visitor, value) with exception-safe handling. + * Returns pair: second=true on success, false if an exception was caught + * On exception the first element is default-constructed. + */ +template +inline auto VisitVariant(Visitor &&visitor, const Variant &value) noexcept + -> std::pair(visitor), value)), bool> +{ + using ReturnType = decltype(opentelemetry::nostd::visit(std::forward(visitor), value)); +#if OPENTELEMETRY_HAVE_EXCEPTIONS + try + { +#endif + return {opentelemetry::nostd::visit(std::forward(visitor), value), true}; +#if OPENTELEMETRY_HAVE_EXCEPTIONS + } +# if defined(OPENTELEMETRY_HAVE_STD_VARIANT) + catch (const std::bad_variant_access &) +# else + catch (const opentelemetry::nostd::bad_variant_access &) +# endif + { + return {ReturnType{}, false}; + } + catch (const std::bad_alloc &) + { + return {ReturnType{}, false}; + } +#endif +} + /** * Class for storing attributes. */ @@ -329,9 +389,15 @@ class AttributeMap : public std::unordered_map // Convert non-owning key-value to owning std::string(key) and OwnedAttributeValue(value) void SetAttribute(nostd::string_view key, - const opentelemetry::common::AttributeValue &value) noexcept + const opentelemetry::common::AttributeValue &value, + std::size_t max_length = (std::numeric_limits::max)()) noexcept { - (*this)[std::string(key)] = nostd::visit(AttributeConverter(), value); + std::pair result = + VisitVariant(AttributeConverter(max_length), value); + if (result.second) + { + AttributeInsertOrAssign(*this, key, std::move(result.first)); + } } // Compare the attributes of this map with another KeyValueIterable @@ -403,9 +469,15 @@ class OrderedAttributeMap : public std::map // Convert non-owning key-value to owning std::string(key) and OwnedAttributeValue(value) void SetAttribute(nostd::string_view key, - const opentelemetry::common::AttributeValue &value) noexcept + const opentelemetry::common::AttributeValue &value, + std::size_t max_length = (std::numeric_limits::max)()) noexcept { - (*this)[std::string(key)] = nostd::visit(AttributeConverter(), value); + std::pair result = + VisitVariant(AttributeConverter(max_length), value); + if (result.second) + { + AttributeInsertOrAssign(*this, key, std::move(result.first)); + } } }; diff --git a/sdk/include/opentelemetry/sdk/trace/multi_recordable.h b/sdk/include/opentelemetry/sdk/trace/multi_recordable.h index 1b1798a807..84c4e6be81 100644 --- a/sdk/include/opentelemetry/sdk/trace/multi_recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/multi_recordable.h @@ -58,6 +58,8 @@ class MultiRecordable : public Recordable void SetDuration(std::chrono::nanoseconds duration) noexcept override; + void SetSpanLimits(const SpanLimits &limits) noexcept override; + void SetInstrumentationScope(const InstrumentationScope &instrumentation_scope) noexcept override; private: diff --git a/sdk/include/opentelemetry/sdk/trace/recordable.h b/sdk/include/opentelemetry/sdk/trace/recordable.h index 4f6a895170..d08eebf24d 100644 --- a/sdk/include/opentelemetry/sdk/trace/recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/recordable.h @@ -10,6 +10,7 @@ #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/empty_attributes.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -171,6 +172,13 @@ class Recordable */ virtual void SetDuration(std::chrono::nanoseconds duration) noexcept = 0; + /** + * Set the span limits for the span. + * This method must be called before any SetAttribute / AddEvent / AddLink call. + * @param limits the span limits to set + */ + virtual void SetSpanLimits(const SpanLimits & /* limits */) noexcept {} + /** * Get the SpanData object for this Recordable. * diff --git a/sdk/include/opentelemetry/sdk/trace/span_data.h b/sdk/include/opentelemetry/sdk/trace/span_data.h index 6a7d570afb..e5869d8fc6 100644 --- a/sdk/include/opentelemetry/sdk/trace/span_data.h +++ b/sdk/include/opentelemetry/sdk/trace/span_data.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -38,9 +40,12 @@ namespace trace class SpanDataEvent { public: - SpanDataEvent(std::string name, - opentelemetry::common::SystemTimestamp timestamp, - const opentelemetry::common::KeyValueIterable &attributes); + SpanDataEvent( + std::string name, + opentelemetry::common::SystemTimestamp timestamp, + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit = (std::numeric_limits::max)(), + std::size_t attribute_value_length_limit = (std::numeric_limits::max)()); /** * Get the name for this event @@ -61,10 +66,17 @@ class SpanDataEvent const std::unordered_map & GetAttributes() const noexcept; + /** + * Get the number of attributes dropped due to the per-event attribute count limit. + * @return the number of attributes dropped + */ + std::uint32_t GetDroppedAttributesCount() const noexcept { return dropped_attributes_count_; } + private: std::string name_; opentelemetry::common::SystemTimestamp timestamp_; opentelemetry::sdk::common::AttributeMap attribute_map_; + std::uint32_t dropped_attributes_count_{0}; }; /** @@ -73,8 +85,11 @@ class SpanDataEvent class SpanDataLink { public: - SpanDataLink(opentelemetry::trace::SpanContext span_context, - const opentelemetry::common::KeyValueIterable &attributes); + SpanDataLink( + opentelemetry::trace::SpanContext span_context, + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit = (std::numeric_limits::max)(), + std::size_t attribute_value_length_limit = (std::numeric_limits::max)()); /** * Get the attributes for this link @@ -89,9 +104,17 @@ class SpanDataLink */ const opentelemetry::trace::SpanContext &GetSpanContext() const noexcept { return span_context_; } + /** + * Get the number of attributes dropped due to the per-link + * attribute count limit. + * @return the number of attributes dropped + */ + std::uint32_t GetDroppedAttributesCount() const noexcept { return dropped_attributes_count_; } + private: opentelemetry::trace::SpanContext span_context_; opentelemetry::sdk::common::AttributeMap attribute_map_; + std::uint32_t dropped_attributes_count_{0}; }; /** @@ -235,8 +258,16 @@ class SpanData final : public Recordable void SetDuration(std::chrono::nanoseconds duration) noexcept override; + void SetSpanLimits(const SpanLimits &limits) noexcept override; + void SetInstrumentationScope(const InstrumentationScope &instrumentation_scope) noexcept override; + std::uint32_t GetDroppedAttributesCount() const noexcept { return dropped_attributes_count_; } + + std::uint32_t GetDroppedEventsCount() const noexcept { return dropped_events_count_; } + + std::uint32_t GetDroppedLinksCount() const noexcept { return dropped_links_count_; } + private: opentelemetry::trace::SpanContext span_context_{false, false}; opentelemetry::trace::SpanId parent_span_id_; @@ -252,6 +283,10 @@ class SpanData final : public Recordable opentelemetry::trace::SpanKind span_kind_{opentelemetry::trace::SpanKind::kInternal}; const opentelemetry::sdk::resource::Resource *resource_{nullptr}; const InstrumentationScope *instrumentation_scope_{nullptr}; + SpanLimits limits_{SpanLimits::NoLimits()}; + std::uint32_t dropped_attributes_count_{0}; + std::uint32_t dropped_events_count_{0}; + std::uint32_t dropped_links_count_{0}; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/span_limits.h b/sdk/include/opentelemetry/sdk/trace/span_limits.h new file mode 100644 index 0000000000..d374f9aa5b --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/span_limits.h @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ + +/** + * Span limits set on the TracerProvider and used by supporting recordables. + * See https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-limits. + */ +struct SpanLimits +{ + static constexpr std::uint32_t kDefaultAttributeCountLimit = 128; + static constexpr std::uint32_t kDefaultEventCountLimit = 128; + static constexpr std::uint32_t kDefaultLinkCountLimit = 128; + static constexpr std::uint32_t kDefaultEventAttributeCountLimit = 128; + static constexpr std::uint32_t kDefaultLinkAttributeCountLimit = 128; + static constexpr std::size_t kDefaultAttributeValueLengthLimit = + (std::numeric_limits::max)(); + + /// Maximum number of attributes per span. + std::uint32_t attribute_count_limit{kDefaultAttributeCountLimit}; + + /// Maximum byte length of string and byte array attribute values. + std::size_t attribute_value_length_limit{kDefaultAttributeValueLengthLimit}; + + /// Maximum number of events per span. + std::uint32_t event_count_limit{kDefaultEventCountLimit}; + + /// Maximum number of links per span. + std::uint32_t link_count_limit{kDefaultLinkCountLimit}; + + /// Maximum number of attributes per span event. + std::uint32_t event_attribute_count_limit{kDefaultEventAttributeCountLimit}; + + /// Maximum number of attributes per span link. + std::uint32_t link_attribute_count_limit{kDefaultLinkAttributeCountLimit}; + + /** + * Limit values that impose no cap on any field + */ + static constexpr SpanLimits NoLimits() noexcept + { + SpanLimits limits; + limits.attribute_count_limit = (std::numeric_limits::max)(); + limits.attribute_value_length_limit = (std::numeric_limits::max)(); + limits.event_count_limit = (std::numeric_limits::max)(); + limits.link_count_limit = (std::numeric_limits::max)(); + limits.event_attribute_count_limit = (std::numeric_limits::max)(); + limits.link_attribute_count_limit = (std::numeric_limits::max)(); + return limits; + } +}; + +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index dcaf683395..601cfc1b7c 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -15,6 +15,7 @@ #include "opentelemetry/sdk/trace/id_generator.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/trace/noop.h" @@ -83,6 +84,9 @@ class Tracer final : public opentelemetry::trace::Tracer, /** Returns the configured span processor. */ SpanProcessor &GetProcessor() noexcept { return context_->GetProcessor(); } + /** Returns the configured span limits. */ + const SpanLimits &GetSpanLimits() const noexcept { return context_->GetSpanLimits(); } + /** Returns the configured Id generator */ IdGenerator &GetIdGenerator() const noexcept { return context_->GetIdGenerator(); } diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_context.h b/sdk/include/opentelemetry/sdk/trace/tracer_context.h index 518c1cbdf6..f94972b8fe 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_context.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_context.h @@ -14,6 +14,7 @@ #include "opentelemetry/sdk/trace/random_id_generator.h" #include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/version.h" @@ -50,7 +51,8 @@ class TracerContext std::make_unique>( instrumentationscope::ScopeConfigurator::Builder( TracerConfig::Default()) - .Build())) noexcept; + .Build()), + SpanLimits span_limits = SpanLimits::NoLimits()) noexcept; TracerContext(const TracerContext &) = delete; TracerContext(TracerContext &&) = delete; @@ -102,6 +104,12 @@ class TracerContext */ opentelemetry::sdk::trace::IdGenerator &GetIdGenerator() const noexcept; + /** + * Obtain the SpanLimits associated with this tracer context. + * @return The SpanLimits for this tracer context. + */ + const SpanLimits &GetSpanLimits() const noexcept; + /** * Replace the TracerConfigurator for this context. * @@ -129,6 +137,7 @@ class TracerContext std::unique_ptr id_generator_; std::unique_ptr processor_; std::unique_ptr> tracer_configurator_; + SpanLimits span_limits_; }; } // namespace trace diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h index b58f46dc20..5b9986a4c3 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h @@ -15,6 +15,7 @@ #include "opentelemetry/sdk/trace/random_id_generator.h" #include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/trace/tracer.h" @@ -41,6 +42,7 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T * not be a nullptr * @param tracer_configurator Provides access to a function that computes the TracerConfig for * Tracers provided by this TracerProvider. + * @param span_limits The span limits for this tracer provider. */ explicit TracerProvider( std::unique_ptr processor, @@ -53,7 +55,8 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T std::make_unique>( instrumentationscope::ScopeConfigurator::Builder( TracerConfig::Default()) - .Build())) noexcept; + .Build()), + SpanLimits span_limits = SpanLimits::NoLimits()) noexcept; explicit TracerProvider( std::vector> &&processors, @@ -66,7 +69,8 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T std::make_unique>( instrumentationscope::ScopeConfigurator::Builder( TracerConfig::Default()) - .Build())) noexcept; + .Build()), + SpanLimits span_limits = SpanLimits::NoLimits()) noexcept; /** * Initialize a new tracer provider with a specified context @@ -130,6 +134,12 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T */ const opentelemetry::sdk::resource::Resource &GetResource() const noexcept; + /** + * Obtain the span limits configured for this tracer provider. + * @return The SpanLimits for this tracer provider. + */ + const opentelemetry::sdk::trace::SpanLimits &GetSpanLimits() const noexcept; + /** * Shutdown the span processor associated with this tracer provider. */ diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h b/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h index 85177f6565..81e0e36f01 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h @@ -10,6 +10,7 @@ #include "opentelemetry/sdk/trace/id_generator.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/trace/tracer_provider.h" @@ -55,6 +56,14 @@ class OPENTELEMETRY_EXPORT TracerProviderFactory std::unique_ptr id_generator, std::unique_ptr> tracer_configurator); + static std::unique_ptr Create( + std::unique_ptr processor, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits); + /* Series of creator methods with a vector of processors. */ static std::unique_ptr Create( @@ -82,6 +91,14 @@ class OPENTELEMETRY_EXPORT TracerProviderFactory std::unique_ptr id_generator, std::unique_ptr> tracer_configurator); + static std::unique_ptr Create( + std::vector> &&processors, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits); + /* Create with a tracer context. */ static std::unique_ptr Create( diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index 98f5fde13b..929fc730de 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -1,9 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include -#include #include +#include +#include #include #include #include @@ -115,6 +115,7 @@ #include "opentelemetry/sdk/configuration/simple_span_processor_configuration.h" #include "opentelemetry/sdk/configuration/span_exporter_configuration.h" #include "opentelemetry/sdk/configuration/span_exporter_configuration_visitor.h" +#include "opentelemetry/sdk/configuration/span_limits_configuration.h" #include "opentelemetry/sdk/configuration/span_processor_configuration.h" #include "opentelemetry/sdk/configuration/span_processor_configuration_visitor.h" #include "opentelemetry/sdk/configuration/string_array_attribute_value_configuration.h" @@ -172,6 +173,7 @@ #include "opentelemetry/sdk/trace/samplers/parent_factory.h" #include "opentelemetry/sdk/trace/samplers/trace_id_ratio_factory.h" #include "opentelemetry/sdk/trace/simple_processor_factory.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/sdk/trace/tracer_provider_factory.h" @@ -1104,20 +1106,41 @@ std::unique_ptr SdkBuilder::CreateTra sdk_processors.push_back(CreateSpanProcessor(processor_model)); } - // FIXME-SDK: https://github.com/open-telemetry/opentelemetry-cpp/issues/3303 - // FIXME-SDK: use limits, id_generator, ... + opentelemetry::sdk::trace::SpanLimits span_limits; + if (model->limits) + { + span_limits.attribute_value_length_limit = model->limits->attribute_value_length_limit; + span_limits.attribute_count_limit = + static_cast(model->limits->attribute_count_limit); + span_limits.event_count_limit = static_cast(model->limits->event_count_limit); + span_limits.link_count_limit = static_cast(model->limits->link_count_limit); + span_limits.event_attribute_count_limit = + static_cast(model->limits->event_attribute_count_limit); + span_limits.link_attribute_count_limit = + static_cast(model->limits->link_attribute_count_limit); + } + if (model->tracer_configurator) { auto tracer_configurator = CreateTracerConfigurator(model->tracer_configurator); auto id_generator = opentelemetry::sdk::trace::RandomIdGeneratorFactory::Create(); sdk = opentelemetry::sdk::trace::TracerProviderFactory::Create( std::move(sdk_processors), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator)); + std::move(tracer_configurator), span_limits); } else { - sdk = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sdk_processors), - resource, std::move(sampler)); + auto id_generator = opentelemetry::sdk::trace::RandomIdGeneratorFactory::Create(); + auto tracer_configurator = + std::make_unique>( + opentelemetry::sdk::instrumentationscope:: + ScopeConfigurator::Builder( + opentelemetry::sdk::trace::TracerConfig::Default()) + .Build()); + sdk = opentelemetry::sdk::trace::TracerProviderFactory::Create( + std::move(sdk_processors), resource, std::move(sampler), std::move(id_generator), + std::move(tracer_configurator), span_limits); } return sdk; diff --git a/sdk/src/trace/multi_recordable.cc b/sdk/src/trace/multi_recordable.cc index 5d74fd2403..37959a8e1f 100644 --- a/sdk/src/trace/multi_recordable.cc +++ b/sdk/src/trace/multi_recordable.cc @@ -69,6 +69,14 @@ void MultiRecordable::SetIdentity(const opentelemetry::trace::SpanContext &span_ } } +void MultiRecordable::SetSpanLimits(const SpanLimits &limits) noexcept +{ + for (auto &recordable : recordables_) + { + recordable.second->SetSpanLimits(limits); + } +} + void MultiRecordable::SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index d764540875..1233e3361d 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -66,6 +66,7 @@ Span::Span(std::shared_ptr &&tracer, { return; } + recordable_->SetSpanLimits(tracer_->GetSpanLimits()); recordable_->SetName(name); recordable_->SetInstrumentationScope(tracer_->GetInstrumentationScope()); recordable_->SetIdentity(*span_context_, parent_span_context.IsValid() diff --git a/sdk/src/trace/span_data.cc b/sdk/src/trace/span_data.cc index 9969b44c30..c626553691 100644 --- a/sdk/src/trace/span_data.cc +++ b/sdk/src/trace/span_data.cc @@ -1,7 +1,10 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#include #include +#include +#include #include #include #include @@ -11,11 +14,14 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/common/timestamp.h" +#include "opentelemetry/nostd/function_ref.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_flags.h" @@ -27,10 +33,66 @@ namespace sdk namespace trace { +namespace +{ + +// Populate from attributes enforcing count and value-length limits. +opentelemetry::sdk::common::AttributeMap PopulateAttributeMap( + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t count_limit, + std::size_t value_length_limit) +{ + opentelemetry::sdk::common::AttributeMap map; + map.reserve((std::min)(static_cast(count_limit), attributes.size())); + + // Insert attributes until the count limit of unique keys is reached. + std::uint32_t inserted = 0; + attributes.ForEachKeyValue( + [count_limit, value_length_limit, &map, &inserted]( + nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { + if (inserted >= count_limit) + { + return false; // The attribute count limit has been reached. Stop iterating. + } + + std::pair convert_result = + opentelemetry::sdk::common::VisitVariant( + opentelemetry::sdk::common::AttributeConverter(value_length_limit), value); + if (!convert_result.second) + { + return true; // There was an exception converting. Skip this attribute and continue. + } + + auto result = opentelemetry::sdk::common::AttributeInsertOrAssign( + map, key, std::move(convert_result.first)); + if (result.second) + { + ++inserted; // A unique key was inserted. + } + return true; + }); + return map; +} + +std::uint32_t CountDroppedAttributes(const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t count_limit) +{ + const std::size_t total = attributes.size(); + return static_cast(total > count_limit ? total - count_limit : 0); +} + +} // namespace + SpanDataEvent::SpanDataEvent(std::string name, opentelemetry::common::SystemTimestamp timestamp, - const opentelemetry::common::KeyValueIterable &attributes) - : name_(std::move(name)), timestamp_(timestamp), attribute_map_(attributes) + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit, + std::size_t attribute_value_length_limit) + : name_(std::move(name)), + timestamp_(timestamp), + attribute_map_( + PopulateAttributeMap(attributes, attribute_count_limit, attribute_value_length_limit)), + dropped_attributes_count_(CountDroppedAttributes(attributes, attribute_count_limit)) {} const std::unordered_map & @@ -40,8 +102,13 @@ SpanDataEvent::GetAttributes() const noexcept } SpanDataLink::SpanDataLink(opentelemetry::trace::SpanContext span_context, - const opentelemetry::common::KeyValueIterable &attributes) - : span_context_(std::move(span_context)), attribute_map_(attributes) + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit, + std::size_t attribute_value_length_limit) + : span_context_(std::move(span_context)), + attribute_map_( + PopulateAttributeMap(attributes, attribute_count_limit, attribute_value_length_limit)), + dropped_attributes_count_(CountDroppedAttributes(attributes, attribute_count_limit)) {} const std::unordered_map & @@ -93,22 +160,60 @@ void SpanData::SetIdentity(const opentelemetry::trace::SpanContext &span_context void SpanData::SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { - attribute_map_.SetAttribute(key, value); + if (attribute_map_.size() >= limits_.attribute_count_limit) + { + // The map is at the limit. Can only update existing keys. + auto it = attribute_map_.find(std::string(key)); + if (it == attribute_map_.end()) + { + ++dropped_attributes_count_; + return; + } + // try to convert the value. + std::pair convert_result = + opentelemetry::sdk::common::VisitVariant( + opentelemetry::sdk::common::AttributeConverter(limits_.attribute_value_length_limit), + value); + if (!convert_result.second) + { + return; // There was an exception converting the value. Skip this attribute. + } + it->second = std::move(convert_result.first); + return; + } + + // The map is under the limit. Set the attribute (insert or assign). + attribute_map_.SetAttribute(key, value, limits_.attribute_value_length_limit); } void SpanData::AddEvent(nostd::string_view name, opentelemetry::common::SystemTimestamp timestamp, const opentelemetry::common::KeyValueIterable &attributes) noexcept { - SpanDataEvent event(std::string(name), timestamp, attributes); - events_.push_back(event); + if (events_.size() >= limits_.event_count_limit) + { + ++dropped_events_count_; + return; + } + events_.emplace_back(std::string(name), timestamp, attributes, + limits_.event_attribute_count_limit, limits_.attribute_value_length_limit); } void SpanData::AddLink(const opentelemetry::trace::SpanContext &span_context, const opentelemetry::common::KeyValueIterable &attributes) noexcept { - SpanDataLink link(span_context, attributes); - links_.push_back(link); + if (links_.size() >= limits_.link_count_limit) + { + ++dropped_links_count_; + return; + } + links_.emplace_back(span_context, attributes, limits_.link_attribute_count_limit, + limits_.attribute_value_length_limit); +} + +void SpanData::SetSpanLimits(const SpanLimits &limits) noexcept +{ + limits_ = limits; } void SpanData::SetStatus(opentelemetry::trace::StatusCode code, diff --git a/sdk/src/trace/tracer_context.cc b/sdk/src/trace/tracer_context.cc index adb3cfe538..43bdabc93b 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/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/version.h" @@ -24,17 +25,19 @@ namespace trace { namespace resource = opentelemetry::sdk::resource; -TracerContext::TracerContext(std::vector> &&processors, - const resource::Resource &resource, - std::unique_ptr sampler, - std::unique_ptr id_generator, - std::unique_ptr> - tracer_configurator) noexcept +TracerContext::TracerContext( + std::vector> &&processors, + const resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) noexcept : resource_(resource), sampler_(std::move(sampler)), id_generator_(std::move(id_generator)), processor_(std::unique_ptr(new MultiSpanProcessor(std::move(processors)))), - tracer_configurator_(std::move(tracer_configurator)) + tracer_configurator_(std::move(tracer_configurator)), + span_limits_(span_limits) {} Sampler &TracerContext::GetSampler() const noexcept @@ -58,9 +61,13 @@ opentelemetry::sdk::trace::IdGenerator &TracerContext::GetIdGenerator() const no return *id_generator_; } -void TracerContext::AddProcessor(std::unique_ptr processor) noexcept +const SpanLimits &TracerContext::GetSpanLimits() const noexcept { + return span_limits_; +} +void TracerContext::AddProcessor(std::unique_ptr processor) noexcept +{ auto multi_processor = static_cast(processor_.get()); multi_processor->AddProcessor(std::move(processor)); } diff --git a/sdk/src/trace/tracer_provider.cc b/sdk/src/trace/tracer_provider.cc index 7f71a1ec47..47d3bb98b9 100644 --- a/sdk/src/trace/tracer_provider.cc +++ b/sdk/src/trace/tracer_provider.cc @@ -16,6 +16,7 @@ #include "opentelemetry/sdk/trace/id_generator.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -41,14 +42,14 @@ TracerProvider::TracerProvider( const resource::Resource &resource, std::unique_ptr sampler, std::unique_ptr id_generator, - std::unique_ptr> - tracer_configurator) noexcept + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) noexcept { std::vector> processors; processors.push_back(std::move(processor)); - context_ = - std::make_shared(std::move(processors), resource, std::move(sampler), - std::move(id_generator), std::move(tracer_configurator)); + context_ = std::make_shared(std::move(processors), resource, std::move(sampler), + std::move(id_generator), + std::move(tracer_configurator), span_limits); } TracerProvider::TracerProvider( @@ -56,13 +57,14 @@ TracerProvider::TracerProvider( const resource::Resource &resource, std::unique_ptr sampler, std::unique_ptr id_generator, - std::unique_ptr> - tracer_configurator) noexcept + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) noexcept : context_(std::make_shared(std::move(processors), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator))) + std::move(tracer_configurator), + span_limits)) {} TracerProvider::~TracerProvider() @@ -163,6 +165,11 @@ const resource::Resource &TracerProvider::GetResource() const noexcept return context_->GetResource(); } +const opentelemetry::sdk::trace::SpanLimits &TracerProvider::GetSpanLimits() const noexcept +{ + return context_->GetSpanLimits(); +} + bool TracerProvider::Shutdown(std::chrono::microseconds timeout) noexcept { return context_->Shutdown(timeout); diff --git a/sdk/src/trace/tracer_provider_factory.cc b/sdk/src/trace/tracer_provider_factory.cc index 49d2aac12d..06f792c659 100644 --- a/sdk/src/trace/tracer_provider_factory.cc +++ b/sdk/src/trace/tracer_provider_factory.cc @@ -12,6 +12,7 @@ #include "opentelemetry/sdk/trace/random_id_generator_factory.h" #include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/always_on_factory.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/sdk/trace/tracer_provider.h" @@ -68,11 +69,23 @@ std::unique_ptr TracerProviderFactory std::unique_ptr sampler, std::unique_ptr id_generator, 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::unique_ptr TracerProviderFactory::Create( + std::unique_ptr processor, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) { std::unique_ptr provider( new opentelemetry::sdk::trace::TracerProvider(std::move(processor), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator))); + std::move(tracer_configurator), span_limits)); return provider; } @@ -120,11 +133,23 @@ std::unique_ptr TracerProviderFactory std::unique_ptr sampler, std::unique_ptr id_generator, 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::unique_ptr TracerProviderFactory::Create( + std::vector> &&processors, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) { std::unique_ptr provider( new opentelemetry::sdk::trace::TracerProvider(std::move(processors), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator))); + std::move(tracer_configurator), span_limits)); return provider; } diff --git a/sdk/test/common/attribute_utils_test.cc b/sdk/test/common/attribute_utils_test.cc index c4b6abe378..848ce86bc4 100644 --- a/sdk/test/common/attribute_utils_test.cc +++ b/sdk/test/common/attribute_utils_test.cc @@ -195,6 +195,26 @@ TEST(AttributeMapTest, EqualTo) EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_all)); } +TEST(AttributeMapTest, SetAttributeTruncation) +{ + opentelemetry::sdk::common::AttributeMap attribute_map; + opentelemetry::nostd::string_view value("abcdefghijk"); + attribute_map.SetAttribute("key", value, 5); + auto stored_value = attribute_map.GetAttributes().at("key"); + auto string_value = opentelemetry::nostd::get(stored_value); + EXPECT_EQ(string_value, "abcde"); +} + +TEST(OrderedAttributeMapTest, SetAttributeTruncation) +{ + opentelemetry::sdk::common::OrderedAttributeMap attribute_map; + opentelemetry::nostd::string_view value("abcdefghijk"); + attribute_map.SetAttribute("key", value, 5); + auto stored_value = attribute_map.GetAttributes().at("key"); + auto string_value = opentelemetry::nostd::get(stored_value); + EXPECT_EQ(string_value, "abcde"); +} + // --------------------------------------------------------------------------- // AttributeConverter truncation // --------------------------------------------------------------------------- diff --git a/sdk/test/configuration/BUILD b/sdk/test/configuration/BUILD index f9cd843105..6e9b677f5a 100644 --- a/sdk/test/configuration/BUILD +++ b/sdk/test/configuration/BUILD @@ -3,6 +3,26 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test") +cc_test( + name = "sdk_builder_test", + srcs = [ + "sdk_builder_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk:headers", + "//sdk/src/configuration", + "//sdk/src/logs", + "//sdk/src/metrics", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "yaml_logs_test", srcs = [ diff --git a/sdk/test/configuration/CMakeLists.txt b/sdk/test/configuration/CMakeLists.txt index 38ae21b19d..5385ddf0de 100644 --- a/sdk/test/configuration/CMakeLists.txt +++ b/sdk/test/configuration/CMakeLists.txt @@ -3,6 +3,7 @@ foreach( testname + sdk_builder_test yaml_logs_test yaml_metrics_test yaml_propagator_test diff --git a/sdk/test/configuration/sdk_builder_test.cc b/sdk/test/configuration/sdk_builder_test.cc new file mode 100644 index 0000000000..0dc05aeb40 --- /dev/null +++ b/sdk/test/configuration/sdk_builder_test.cc @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/sdk_builder.h" +#include "opentelemetry/sdk/configuration/span_limits_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_provider_configuration.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/span_limits.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" + +using opentelemetry::sdk::configuration::Registry; +using opentelemetry::sdk::configuration::SdkBuilder; +using opentelemetry::sdk::configuration::SpanLimitsConfiguration; +using opentelemetry::sdk::configuration::TracerProviderConfiguration; + +TEST(SdkBuilder, SpanLimitsDefaults) +{ + auto model = std::make_unique(); + model->limits = nullptr; + + SdkBuilder builder(std::make_shared()); + auto resource = opentelemetry::sdk::resource::Resource::Create({}); + auto provider = builder.CreateTracerProvider(model, resource); + ASSERT_NE(provider, nullptr); + + auto limits = provider->GetSpanLimits(); + EXPECT_EQ(limits.attribute_count_limit, + opentelemetry::sdk::trace::SpanLimits::kDefaultAttributeCountLimit); + EXPECT_EQ(limits.event_count_limit, + opentelemetry::sdk::trace::SpanLimits::kDefaultEventCountLimit); + EXPECT_EQ(limits.link_count_limit, opentelemetry::sdk::trace::SpanLimits::kDefaultLinkCountLimit); + EXPECT_EQ(limits.event_attribute_count_limit, + opentelemetry::sdk::trace::SpanLimits::kDefaultEventAttributeCountLimit); + EXPECT_EQ(limits.link_attribute_count_limit, + opentelemetry::sdk::trace::SpanLimits::kDefaultLinkAttributeCountLimit); + EXPECT_EQ(limits.attribute_value_length_limit, + opentelemetry::sdk::trace::SpanLimits::kDefaultAttributeValueLengthLimit); +} + +TEST(SdkBuilder, SpanLimitsConfiguration) +{ + auto model = std::make_unique(); + model->limits = std::make_unique(); + model->limits->attribute_value_length_limit = 1111; + model->limits->attribute_count_limit = 2222; + model->limits->event_count_limit = 3333; + model->limits->link_count_limit = 4444; + model->limits->event_attribute_count_limit = 5555; + model->limits->link_attribute_count_limit = 6666; + + SdkBuilder builder(std::make_shared()); + auto resource = opentelemetry::sdk::resource::Resource::Create({}); + auto provider = builder.CreateTracerProvider(model, resource); + ASSERT_NE(provider, nullptr); + + auto limits = provider->GetSpanLimits(); + EXPECT_EQ(limits.attribute_value_length_limit, model->limits->attribute_value_length_limit); + EXPECT_EQ(limits.attribute_count_limit, model->limits->attribute_count_limit); + EXPECT_EQ(limits.event_count_limit, model->limits->event_count_limit); + EXPECT_EQ(limits.link_count_limit, model->limits->link_count_limit); + EXPECT_EQ(limits.event_attribute_count_limit, model->limits->event_attribute_count_limit); + EXPECT_EQ(limits.link_attribute_count_limit, model->limits->link_attribute_count_limit); +} diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc index 3e3c27ce4d..12717306a3 100644 --- a/sdk/test/trace/span_data_test.cc +++ b/sdk/test/trace/span_data_test.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "opentelemetry/common/key_value_iterable_view.h" @@ -16,6 +17,7 @@ #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -149,3 +151,140 @@ TEST(SpanData, Links) values[i]); } } + +TEST(SpanData, SpanLimits) +{ + SpanData recordable; + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 1; + limits.attribute_value_length_limit = 2; + limits.link_count_limit = 1; + limits.link_attribute_count_limit = 1; + limits.event_count_limit = 1; + limits.event_attribute_count_limit = 1; + + constexpr const char *kKey1 = "one"; + constexpr const char *kKey2 = "two"; + constexpr const char *kValue1 = "1234"; + constexpr const char *kValue2 = "5678"; + + std::map attribute_collection{{kKey1, kValue1}, {kKey2, kValue2}}; + + recordable.SetSpanLimits(limits); + + // span attribute count and length limits + recordable.SetAttribute(kKey1, kValue1); + recordable.SetAttribute(kKey2, kValue2); + + EXPECT_EQ(recordable.GetAttributes().size(), 1); + EXPECT_EQ(recordable.GetDroppedAttributesCount(), 1); + EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at(kKey1)), "12"); + + // event count and per-event attribute count limits + recordable.AddEvent("event1", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + recordable.AddEvent("event2", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + + ASSERT_EQ(recordable.GetEvents().size(), 1); + EXPECT_EQ(recordable.GetDroppedEventsCount(), 1); + const auto event = recordable.GetEvents().at(0); + EXPECT_EQ(event.GetDroppedAttributesCount(), 1); + EXPECT_EQ(event.GetName(), "event1"); + const auto &event_attributes = event.GetAttributes(); + EXPECT_EQ(event_attributes.size(), 1); + EXPECT_EQ(opentelemetry::nostd::get(event_attributes.at(kKey1)), "12"); + + // link count and per-link attribute count limits + recordable.AddLink(trace_api::SpanContext::GetInvalid(), + common::MakeAttributes(attribute_collection)); + recordable.AddLink(trace_api::SpanContext::GetInvalid(), + common::MakeAttributes(attribute_collection)); + ASSERT_EQ(recordable.GetLinks().size(), 1); + EXPECT_EQ(recordable.GetDroppedLinksCount(), 1); + const auto link = recordable.GetLinks().at(0); + EXPECT_EQ(link.GetDroppedAttributesCount(), 1); + const auto &link_attributes = link.GetAttributes(); + EXPECT_EQ(link_attributes.size(), 1); + EXPECT_EQ(opentelemetry::nostd::get(link_attributes.at(kKey1)), "12"); +} + +TEST(SpanData, SpanLimitsNoLimitDefault) +{ + constexpr std::uint32_t kMaxCount = 500; + SpanData recordable; + std::map attributes; + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + attributes["attribute_" + std::to_string(i)] = std::to_string(i); + } + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + recordable.SetAttribute("attribute_" + std::to_string(i), i); + recordable.AddEvent("event_" + std::to_string(i), std::chrono::system_clock::now(), + common::MakeAttributes(attributes)); + recordable.AddLink(trace_api::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + } + + EXPECT_EQ(recordable.GetAttributes().size(), kMaxCount); + EXPECT_EQ(recordable.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(recordable.GetEvents().size(), kMaxCount); + EXPECT_EQ(recordable.GetDroppedEventsCount(), 0u); + EXPECT_EQ(recordable.GetLinks().size(), kMaxCount); + EXPECT_EQ(recordable.GetDroppedLinksCount(), 0u); + EXPECT_EQ(recordable.GetEvents().at(0).GetDroppedAttributesCount(), 0u); + EXPECT_EQ(recordable.GetLinks().at(0).GetDroppedAttributesCount(), 0u); +} + +TEST(SpanData, SpanLimitsDuplicateAttributeKeys) +{ + SpanData recordable; + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 2; + limits.attribute_value_length_limit = 2; + limits.link_count_limit = 1; + limits.link_attribute_count_limit = 2; + limits.event_count_limit = 1; + limits.event_attribute_count_limit = 2; + + // Create three attributes. One has a duplicate key. + std::vector> attributes{ + {"attribute_one", "AA"}, {"attribute_two", "BB"}, {"attribute_one", "CC"}}; + + for (const auto &keyvalue : attributes) + { + recordable.SetAttribute(keyvalue.first, keyvalue.second); + } + + recordable.AddEvent("event", std::chrono::system_clock::now(), + common::MakeAttributes(attributes)); + + recordable.AddLink(trace_api::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + + EXPECT_EQ(recordable.GetAttributes().size(), 2u); + EXPECT_EQ(recordable.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at("attribute_one")), + "CC"); + EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at("attribute_two")), + "BB"); + + EXPECT_EQ(recordable.GetEvents().size(), 1u); + EXPECT_EQ(recordable.GetDroppedEventsCount(), 0u); + const auto &event = recordable.GetEvents().at(0); + EXPECT_EQ(event.GetAttributes().size(), 2u); + EXPECT_EQ(event.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(opentelemetry::nostd::get(event.GetAttributes().at("attribute_one")), + "CC"); + EXPECT_EQ(opentelemetry::nostd::get(event.GetAttributes().at("attribute_two")), + "BB"); + + EXPECT_EQ(recordable.GetLinks().size(), 1u); + EXPECT_EQ(recordable.GetDroppedLinksCount(), 0u); + const auto &link = recordable.GetLinks().at(0); + EXPECT_EQ(link.GetAttributes().size(), 2u); + EXPECT_EQ(link.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(opentelemetry::nostd::get(link.GetAttributes().at("attribute_one")), "CC"); + EXPECT_EQ(opentelemetry::nostd::get(link.GetAttributes().at("attribute_two")), "BB"); +} diff --git a/sdk/test/trace/tracer_provider_test.cc b/sdk/test/trace/tracer_provider_test.cc index 687f63d07f..adde636f03 100644 --- a/sdk/test/trace/tracer_provider_test.cc +++ b/sdk/test/trace/tracer_provider_test.cc @@ -2,13 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include -#include -#include - #include +#include +#include #include +#include +#include #include +#include +#include #include "opentelemetry/common/macros.h" #include "opentelemetry/exporters/memory/in_memory_span_exporter.h" @@ -26,6 +28,7 @@ #include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/simple_processor_factory.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -518,3 +521,66 @@ TEST(TracerProvider, UpdateTracerConfiguratorConcurrentStartSpan) EXPECT_TRUE(tracer->Enabled()); #endif } + +TEST(TracerProvider, SpanLimitsSpecDefaults) +{ + SpanLimits limits; + EXPECT_EQ(limits.attribute_count_limit, 128u); + EXPECT_EQ(limits.event_count_limit, 128u); + EXPECT_EQ(limits.link_count_limit, 128u); + EXPECT_EQ(limits.event_attribute_count_limit, 128u); + EXPECT_EQ(limits.link_attribute_count_limit, 128u); + EXPECT_EQ(limits.attribute_value_length_limit, (std::numeric_limits::max)()); +} + +TEST(TracerProvider, SpanLimitsNoLimits) +{ + SpanLimits limits = SpanLimits::NoLimits(); + EXPECT_EQ(limits.attribute_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.attribute_value_length_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.event_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.link_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.event_attribute_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.link_attribute_count_limit, (std::numeric_limits::max)()); +} + +TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreate) +{ + SpanLimits limits; + limits.attribute_count_limit = 5; + limits.attribute_value_length_limit = 10; + limits.event_count_limit = 3; + limits.link_count_limit = 2; + limits.event_attribute_count_limit = 4; + limits.link_attribute_count_limit = 4; + + auto provider = TracerProviderFactory::Create( + std::make_unique(nullptr), Resource::Create({}), + std::make_unique(), std::make_unique(), + std::make_unique>( + ScopeConfigurator::Builder(TracerConfig::Default()).Build()), + limits); + + const auto &stored = provider->GetSpanLimits(); + EXPECT_EQ(stored.attribute_count_limit, limits.attribute_count_limit); + EXPECT_EQ(stored.attribute_value_length_limit, limits.attribute_value_length_limit); + EXPECT_EQ(stored.event_count_limit, limits.event_count_limit); + EXPECT_EQ(stored.link_count_limit, limits.link_count_limit); + EXPECT_EQ(stored.event_attribute_count_limit, limits.event_attribute_count_limit); + EXPECT_EQ(stored.link_attribute_count_limit, limits.link_attribute_count_limit); +} + +TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreateDefault) +{ + auto provider = TracerProviderFactory::Create(std::make_unique(nullptr)); + + const auto no_limits = SpanLimits::NoLimits(); + + const auto &limits = provider->GetSpanLimits(); + EXPECT_EQ(limits.attribute_count_limit, no_limits.attribute_count_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); + EXPECT_EQ(limits.attribute_value_length_limit, no_limits.attribute_value_length_limit); +} diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index 8a2fbcd5fd..d3107db47a 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -38,6 +38,7 @@ #include "opentelemetry/sdk/trace/samplers/parent.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -188,7 +189,8 @@ std::shared_ptr initTracer( IdGenerator *id_generator = new RandomIdGenerator, const ScopeConfigurator &tracer_configurator = ScopeConfigurator::Builder(TracerConfig::Default()).Build(), - std::unique_ptr scope = InstrumentationScope::Create("")) + std::unique_ptr scope = InstrumentationScope::Create(""), + opentelemetry::sdk::trace::SpanLimits span_limits = SpanLimits::NoLimits()) { auto processor = std::unique_ptr(new SimpleSpanProcessor(std::move(exporter))); std::vector> processors; @@ -197,7 +199,7 @@ std::shared_ptr initTracer( auto context = std::make_shared( std::move(processors), resource, std::unique_ptr(sampler), std::unique_ptr(id_generator), - std::make_unique>(tracer_configurator)); + std::make_unique>(tracer_configurator), span_limits); return std::shared_ptr(new Tracer(context, std::move(scope))); } @@ -1482,3 +1484,51 @@ TEST(Tracer, SpanSamplerDecision) } EXPECT_EQ(3, span_data->GetSpans().size()); } + +TEST(Tracer, SpanLimits) +{ + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 2; + limits.attribute_value_length_limit = 5; + limits.event_count_limit = 2; + limits.event_attribute_count_limit = 2; + + InMemorySpanExporter *exporter = new InMemorySpanExporter(); + std::shared_ptr span_data = exporter->GetData(); + auto tracer = initTracer( + std::unique_ptr{exporter}, new AlwaysOnSampler(), new RandomIdGenerator, + ScopeConfigurator::Builder(TracerConfig::Default()).Build(), + InstrumentationScope::Create(""), limits); + + std::vector> attributes = { + {"one", "1_value"}, {"two", "2_value"}, {"three", "3_value"}}; + + { + auto span = tracer->StartSpan("span_with_limits", attributes); + span->SetAttribute("four", "4_value"); + span->AddEvent("event1", attributes); + span->AddEvent("event2", attributes); + span->AddEvent("event3", attributes); + span->End(); + } + + const auto span_batch = span_data->GetSpans(); + ASSERT_EQ(1, span_batch.size()); + + const auto &recordable = span_batch.at(0); + + ASSERT_EQ(limits.attribute_count_limit, recordable->GetAttributes().size()); + EXPECT_EQ(limits.attribute_value_length_limit, + nostd::get(recordable->GetAttributes().at("one")).size()); + EXPECT_EQ("1_val", nostd::get(recordable->GetAttributes().at("one"))); + EXPECT_EQ("2_val", nostd::get(recordable->GetAttributes().at("two"))); + + const auto &events = recordable->GetEvents(); + ASSERT_EQ(limits.event_count_limit, events.size()); + EXPECT_EQ("event1", events.at(0).GetName()); + EXPECT_EQ("event2", events.at(1).GetName()); + EXPECT_EQ(limits.event_attribute_count_limit, events.at(0).GetAttributes().size()); + EXPECT_EQ(limits.event_attribute_count_limit, events.at(1).GetAttributes().size()); + EXPECT_EQ(limits.attribute_value_length_limit, + nostd::get(events.at(0).GetAttributes().at("one")).size()); +} From 386f2f569e2f55e98c702f50ca5e23ad5e51659a Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Thu, 9 Jul 2026 15:35:18 -0400 Subject: [PATCH 2/5] fix ci issues: format, spelling, missing decl, unused definition. --- exporters/otlp/test/otlp_recordable_test.cc | 76 +++++++++---------- .../sdk/common/attribute_utils.h | 10 +-- .../opentelemetry/sdk/trace/recordable.h | 1 - sdk/src/trace/span_data.cc | 2 +- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index bb97dbe8ef..644cbffbe5 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -880,21 +880,21 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) constexpr std::uint32_t kOtlpEventAttributeCount = 5; constexpr std::uint32_t kOtlpLinkAttributeCount = 6; - trace_sdk::SpanLimits more_restricitve_limits; - more_restricitve_limits.attribute_count_limit = 1; - more_restricitve_limits.event_count_limit = 2; - more_restricitve_limits.link_count_limit = 3; - more_restricitve_limits.event_attribute_count_limit = 4; - more_restricitve_limits.link_attribute_count_limit = 5; - more_restricitve_limits.attribute_value_length_limit = 2; - - trace_sdk::SpanLimits less_restricitve_limits; - less_restricitve_limits.attribute_count_limit = 3; - less_restricitve_limits.event_count_limit = 4; - less_restricitve_limits.link_count_limit = 5; - less_restricitve_limits.event_attribute_count_limit = 6; - less_restricitve_limits.link_attribute_count_limit = 7; - less_restricitve_limits.attribute_value_length_limit = 6; + trace_sdk::SpanLimits more_restrictive_limits; + more_restrictive_limits.attribute_count_limit = 1; + more_restrictive_limits.event_count_limit = 2; + more_restrictive_limits.link_count_limit = 3; + more_restrictive_limits.event_attribute_count_limit = 4; + more_restrictive_limits.link_attribute_count_limit = 5; + more_restrictive_limits.attribute_value_length_limit = 2; + + trace_sdk::SpanLimits less_restrictive_limits; + less_restrictive_limits.attribute_count_limit = 3; + less_restrictive_limits.event_count_limit = 4; + less_restrictive_limits.link_count_limit = 5; + less_restrictive_limits.event_attribute_count_limit = 6; + less_restrictive_limits.link_attribute_count_limit = 7; + less_restrictive_limits.attribute_value_length_limit = 6; auto make_recordable = [&](const trace_sdk::SpanLimits &limits) { auto recordable = @@ -918,20 +918,20 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) // attribute_count_limit: config limits are more restrictive than the deprecated otlp options. { - auto recordable = make_recordable(more_restricitve_limits); + auto recordable = make_recordable(more_restrictive_limits); const auto attributes = make_attributes(); for (const auto &attr : attributes) { recordable->SetAttribute(attr.first, attr.second); } EXPECT_EQ(recordable->span().attributes_size(), - static_cast(more_restricitve_limits.attribute_count_limit)); + static_cast(more_restrictive_limits.attribute_count_limit)); EXPECT_EQ(recordable->span().dropped_attributes_count(), - attributes.size() - more_restricitve_limits.attribute_count_limit); + attributes.size() - more_restrictive_limits.attribute_count_limit); } // attribute_count_limit: config limits are less restrictive than the deprecated otlp options. { - auto recordable = make_recordable(less_restricitve_limits); + auto recordable = make_recordable(less_restrictive_limits); const auto attributes = make_attributes(); for (const auto &attr : attributes) { @@ -945,33 +945,33 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) // attribute_value_length_limit: config limits are more restrictive than the deprecated otlp // options. { - auto recordable = make_recordable(more_restricitve_limits); + auto recordable = make_recordable(more_restrictive_limits); recordable->SetAttribute("value", nostd::string_view("abcdefghij")); EXPECT_EQ(recordable->span().attributes(0).value().string_value().size(), - more_restricitve_limits.attribute_value_length_limit); + more_restrictive_limits.attribute_value_length_limit); } // attribute_value_length_limit: config limits are less restrictive than the deprecated otlp // options. { - auto recordable = make_recordable(less_restricitve_limits); + auto recordable = make_recordable(less_restrictive_limits); recordable->SetAttribute("value", nostd::string_view("abcdefghij")); EXPECT_EQ(recordable->span().attributes(0).value().string_value().size(), - less_restricitve_limits.attribute_value_length_limit); + less_restrictive_limits.attribute_value_length_limit); } // event_count_limit: config limits are more restrictive than the deprecated otlp options. { - auto recordable = make_recordable(more_restricitve_limits); + auto recordable = make_recordable(more_restrictive_limits); recordable->AddEvent("event_one", event_time, common::MakeAttributes(empty_attributes)); recordable->AddEvent("event_two", event_time, common::MakeAttributes(empty_attributes)); recordable->AddEvent("event_three", event_time, common::MakeAttributes(empty_attributes)); EXPECT_EQ(recordable->span().events_size(), - static_cast(more_restricitve_limits.event_count_limit)); + static_cast(more_restrictive_limits.event_count_limit)); EXPECT_EQ(recordable->span().dropped_events_count(), 1u); } // event_count_limit: config limits are less restrictive than the deprecated otlp options. { - auto recordable = make_recordable(less_restricitve_limits); + auto recordable = make_recordable(less_restrictive_limits); recordable->AddEvent("event_one", event_time, common::MakeAttributes(empty_attributes)); recordable->AddEvent("event_two", event_time, common::MakeAttributes(empty_attributes)); recordable->AddEvent("event_three", event_time, common::MakeAttributes(empty_attributes)); @@ -982,20 +982,20 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) // link_count_limit: config limits are more restrictive than the deprecated otlp options. { - auto recordable = make_recordable(more_restricitve_limits); + auto recordable = make_recordable(more_restrictive_limits); for (std::uint32_t i = 0; i < kOtlpLinkCount; ++i) { recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(empty_attributes)); } EXPECT_EQ(recordable->span().links_size(), - static_cast(more_restricitve_limits.link_count_limit)); + static_cast(more_restrictive_limits.link_count_limit)); EXPECT_EQ(recordable->span().dropped_links_count(), 1u); } // link_count_limit: config limits are less restrictive than the deprecated otlp options. { - auto recordable = make_recordable(less_restricitve_limits); - for (std::uint32_t i = 0; i < less_restricitve_limits.link_count_limit; ++i) + auto recordable = make_recordable(less_restrictive_limits); + for (std::uint32_t i = 0; i < less_restrictive_limits.link_count_limit; ++i) { recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(empty_attributes)); @@ -1007,18 +1007,18 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) // event_attribute_count_limit: config limits are more restrictive than the deprecated otlp // options. { - auto recordable = make_recordable(more_restricitve_limits); + auto recordable = make_recordable(more_restrictive_limits); const auto attributes = make_attributes(); recordable->AddEvent("test_event", event_time, common::MakeAttributes(attributes)); EXPECT_EQ(recordable->span().events(0).attributes_size(), - static_cast(more_restricitve_limits.event_attribute_count_limit)); + static_cast(more_restrictive_limits.event_attribute_count_limit)); EXPECT_EQ(recordable->span().events(0).dropped_attributes_count(), - attributes.size() - more_restricitve_limits.event_attribute_count_limit); + attributes.size() - more_restrictive_limits.event_attribute_count_limit); } // event_attribute_count_limit: config limits are less restrictive than the deprecated otlp // options. { - auto recordable = make_recordable(less_restricitve_limits); + auto recordable = make_recordable(less_restrictive_limits); const auto attributes = make_attributes(); recordable->AddEvent("test_event", event_time, common::MakeAttributes(attributes)); EXPECT_EQ(recordable->span().events(0).attributes_size(), @@ -1030,18 +1030,18 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) // link_attribute_count_limit: config limits are more restrictive than the deprecated otlp // options. { - auto recordable = make_recordable(more_restricitve_limits); + auto recordable = make_recordable(more_restrictive_limits); const auto attributes = make_attributes(); recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); EXPECT_EQ(recordable->span().links(0).attributes_size(), - static_cast(more_restricitve_limits.link_attribute_count_limit)); + static_cast(more_restrictive_limits.link_attribute_count_limit)); EXPECT_EQ(recordable->span().links(0).dropped_attributes_count(), - attributes.size() - more_restricitve_limits.link_attribute_count_limit); + attributes.size() - more_restrictive_limits.link_attribute_count_limit); } // link_attribute_count_limit: config limits are less restrictive than the deprecated otlp // options. { - auto recordable = make_recordable(less_restricitve_limits); + auto recordable = make_recordable(less_restrictive_limits); const auto attributes = make_attributes(); recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); EXPECT_EQ(recordable->span().links(0).attributes_size(), diff --git a/sdk/include/opentelemetry/sdk/common/attribute_utils.h b/sdk/include/opentelemetry/sdk/common/attribute_utils.h index 0a977f5f84..a7f1020717 100644 --- a/sdk/include/opentelemetry/sdk/common/attribute_utils.h +++ b/sdk/include/opentelemetry/sdk/common/attribute_utils.h @@ -14,10 +14,6 @@ #include #include -#if OPENTELEMETRY_HAVE_EXCEPTIONS -# include -#endif - #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/nostd/span.h" @@ -25,6 +21,10 @@ #include "opentelemetry/nostd/variant.h" #include "opentelemetry/version.h" +#if OPENTELEMETRY_HAVE_EXCEPTIONS +# include +#endif + OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk { @@ -313,8 +313,8 @@ template inline auto VisitVariant(Visitor &&visitor, const Variant &value) noexcept -> std::pair(visitor), value)), bool> { - using ReturnType = decltype(opentelemetry::nostd::visit(std::forward(visitor), value)); #if OPENTELEMETRY_HAVE_EXCEPTIONS + using ReturnType = decltype(opentelemetry::nostd::visit(std::forward(visitor), value)); try { #endif diff --git a/sdk/include/opentelemetry/sdk/trace/recordable.h b/sdk/include/opentelemetry/sdk/trace/recordable.h index d08eebf24d..e12d069f80 100644 --- a/sdk/include/opentelemetry/sdk/trace/recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/recordable.h @@ -175,7 +175,6 @@ class Recordable /** * Set the span limits for the span. * This method must be called before any SetAttribute / AddEvent / AddLink call. - * @param limits the span limits to set */ virtual void SetSpanLimits(const SpanLimits & /* limits */) noexcept {} diff --git a/sdk/src/trace/span_data.cc b/sdk/src/trace/span_data.cc index c626553691..86bc4d4865 100644 --- a/sdk/src/trace/span_data.cc +++ b/sdk/src/trace/span_data.cc @@ -67,7 +67,7 @@ opentelemetry::sdk::common::AttributeMap PopulateAttributeMap( map, key, std::move(convert_result.first)); if (result.second) { - ++inserted; // A unique key was inserted. + ++inserted; // A unique key was inserted. } return true; }); From 1fb76ebad1aecf60ce12567d50f1ead35b6bb3a8 Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Thu, 9 Jul 2026 16:53:52 -0400 Subject: [PATCH 3/5] fix iwyu warning for abiv2. fix sdk_builder_test link error with c++14 and shared libs. fix lambda capture issue in test --- exporters/otlp/test/otlp_recordable_test.cc | 8 ++++---- sdk/test/configuration/sdk_builder_test.cc | 21 +++++++++------------ sdk/test/trace/tracer_provider_test.cc | 1 - 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index 644cbffbe5..501cbc94ec 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -872,8 +872,6 @@ TEST(OtlpRecordable, SpanLimitsNoLimitDefault) // TODO: remove this test case once the deprecated otlp options span limit fields are removed. TEST(OtlpRecordable, SpanLimitsFieldsMerged) { - constexpr std::uint32_t kMaxCount = 10; - constexpr std::uint32_t kOtlpAttributeCount = 2; constexpr std::uint32_t kOtlpEventCount = 3; constexpr std::uint32_t kOtlpLinkCount = 4; @@ -896,7 +894,8 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) less_restrictive_limits.link_attribute_count_limit = 7; less_restrictive_limits.attribute_value_length_limit = 6; - auto make_recordable = [&](const trace_sdk::SpanLimits &limits) { + auto make_recordable = + [&](const trace_sdk::SpanLimits &limits) -> std::unique_ptr { auto recordable = std::make_unique(kOtlpAttributeCount, kOtlpEventCount, kOtlpLinkCount, kOtlpEventAttributeCount, kOtlpLinkAttributeCount); @@ -904,7 +903,8 @@ TEST(OtlpRecordable, SpanLimitsFieldsMerged) return recordable; }; - auto make_attributes = []() { + auto make_attributes = []() -> std::map { + constexpr std::uint32_t kMaxCount = 10; std::map attributes; for (std::uint32_t i = 0; i < kMaxCount; ++i) { diff --git a/sdk/test/configuration/sdk_builder_test.cc b/sdk/test/configuration/sdk_builder_test.cc index 0dc05aeb40..74a135df3a 100644 --- a/sdk/test/configuration/sdk_builder_test.cc +++ b/sdk/test/configuration/sdk_builder_test.cc @@ -28,18 +28,15 @@ TEST(SdkBuilder, SpanLimitsDefaults) auto provider = builder.CreateTracerProvider(model, resource); ASSERT_NE(provider, nullptr); - auto limits = provider->GetSpanLimits(); - EXPECT_EQ(limits.attribute_count_limit, - opentelemetry::sdk::trace::SpanLimits::kDefaultAttributeCountLimit); - EXPECT_EQ(limits.event_count_limit, - opentelemetry::sdk::trace::SpanLimits::kDefaultEventCountLimit); - EXPECT_EQ(limits.link_count_limit, opentelemetry::sdk::trace::SpanLimits::kDefaultLinkCountLimit); - EXPECT_EQ(limits.event_attribute_count_limit, - opentelemetry::sdk::trace::SpanLimits::kDefaultEventAttributeCountLimit); - EXPECT_EQ(limits.link_attribute_count_limit, - opentelemetry::sdk::trace::SpanLimits::kDefaultLinkAttributeCountLimit); - EXPECT_EQ(limits.attribute_value_length_limit, - opentelemetry::sdk::trace::SpanLimits::kDefaultAttributeValueLengthLimit); + const auto limits = provider->GetSpanLimits(); + const auto default_limits = opentelemetry::sdk::trace::SpanLimits{}; + + EXPECT_EQ(limits.attribute_count_limit, default_limits.attribute_count_limit); + EXPECT_EQ(limits.event_count_limit, default_limits.event_count_limit); + EXPECT_EQ(limits.link_count_limit, default_limits.link_count_limit); + EXPECT_EQ(limits.event_attribute_count_limit, default_limits.event_attribute_count_limit); + EXPECT_EQ(limits.link_attribute_count_limit, default_limits.link_attribute_count_limit); + EXPECT_EQ(limits.attribute_value_length_limit, default_limits.attribute_value_length_limit); } TEST(SdkBuilder, SpanLimitsConfiguration) diff --git a/sdk/test/trace/tracer_provider_test.cc b/sdk/test/trace/tracer_provider_test.cc index adde636f03..66efbbf522 100644 --- a/sdk/test/trace/tracer_provider_test.cc +++ b/sdk/test/trace/tracer_provider_test.cc @@ -38,7 +38,6 @@ #include "opentelemetry/trace/tracer.h" #if OPENTELEMETRY_ABI_VERSION_NO >= 2 -# include # include # include # include From f4bd1000cff7067a2d40032ffdcb6b113f8a519d Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Thu, 9 Jul 2026 17:05:50 -0400 Subject: [PATCH 4/5] add missing test case for span data --- sdk/test/trace/span_data_test.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc index 12717306a3..ecb8687137 100644 --- a/sdk/test/trace/span_data_test.cc +++ b/sdk/test/trace/span_data_test.cc @@ -167,6 +167,7 @@ TEST(SpanData, SpanLimits) constexpr const char *kKey2 = "two"; constexpr const char *kValue1 = "1234"; constexpr const char *kValue2 = "5678"; + constexpr const char *kValue3 = "9012"; std::map attribute_collection{{kKey1, kValue1}, {kKey2, kValue2}}; @@ -175,10 +176,11 @@ TEST(SpanData, SpanLimits) // span attribute count and length limits recordable.SetAttribute(kKey1, kValue1); recordable.SetAttribute(kKey2, kValue2); + recordable.SetAttribute(kKey1, kValue3); // overwrite existing attribute EXPECT_EQ(recordable.GetAttributes().size(), 1); EXPECT_EQ(recordable.GetDroppedAttributesCount(), 1); - EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at(kKey1)), "12"); + EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at(kKey1)), "90"); // event count and per-event attribute count limits recordable.AddEvent("event1", std::chrono::system_clock::now(), From 43cf325153b80e82ddb71c0d74a080c782bcc77b Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Fri, 10 Jul 2026 12:00:09 -0400 Subject: [PATCH 5/5] add changelog entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 456c9439e3..4a864e0e15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,11 @@ Increment the: * [CMAKE] Fix and test WITH_API_ONLY option [#4201](https://github.com/open-telemetry/opentelemetry-cpp/pull/4201) +* [SDK] Span limits can now be configured through the TracerProvider interface + and declaritive configuration. When set, limits are enforced by the + SpanData and OtlpRecordables recordables used by the standard exporters + [#4232](https://github.com/open-telemetry/opentelemetry-cpp/pull/4232) + * [API] Fix `TraceState::IsValidKey()` to comply with the W3C Trace Context Level 2, where keys containing `@` and keys with more than 241 characters before `@` or more than 14 characters after `@` are now accepted.