Skip to content
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Increment the:
* [ETW] Fix ETW Log Exporter: incorrect timestamp, traceId, and spanId
[#3836](https://github.com/open-telemetry/opentelemetry-cpp/pull/3836)

* [EXPORTER] Add support for otlp exporter collection limits
[#3816](https://github.com/open-telemetry/opentelemetry-cpp/pull/3816)

* [ETW] Fix infinite loop in ETWProvider::close
[#3827](https://github.com/open-telemetry/opentelemetry-cpp/pull/3827)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#pragma once

#include <cstdint>
#include <limits>
#include "opentelemetry/exporters/otlp/otlp_grpc_client_options.h"
#include "opentelemetry/version.h"

Expand Down Expand Up @@ -32,6 +34,13 @@ struct OPENTELEMETRY_EXPORT OtlpGrpcExporterOptions : public OtlpGrpcClientOptio
OtlpGrpcExporterOptions &operator=(const OtlpGrpcExporterOptions &) = default;
OtlpGrpcExporterOptions &operator=(OtlpGrpcExporterOptions &&) = default;
~OtlpGrpcExporterOptions() override;

/** Collection Limits. No limit by default. */
std::uint32_t max_attributes = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_events = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_links = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_attributes_per_event = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_attributes_per_link = std::numeric_limits<std::uint32_t>::max();
};

} // namespace otlp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <chrono>
#include <cstdint>
#include <limits>
#include <string>

#include "opentelemetry/exporters/otlp/otlp_environment.h"
Expand Down Expand Up @@ -125,6 +126,13 @@ 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. */
std::uint32_t max_attributes = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_events = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_links = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_attributes_per_event = std::numeric_limits<std::uint32_t>::max();
std::uint32_t max_attributes_per_link = std::numeric_limits<std::uint32_t>::max();
};

} // namespace otlp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#pragma once

#include <chrono>
#include <cstdint> // For std::uint32_t
#include <limits> // For std::numeric_limits
#include <string>

// clang-format off
Expand Down Expand Up @@ -35,6 +37,19 @@ namespace otlp
class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable
{
public:
explicit OtlpRecordable(
std::uint32_t max_attributes = std::numeric_limits<std::uint32_t>::max(),
std::uint32_t max_events = std::numeric_limits<std::uint32_t>::max(),
std::uint32_t max_links = std::numeric_limits<std::uint32_t>::max(),
std::uint32_t max_attributes_per_event = std::numeric_limits<std::uint32_t>::max(),
std::uint32_t max_attributes_per_link = std::numeric_limits<std::uint32_t>::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)
{}

proto::trace::v1::Span &span() noexcept { return span_; }
const proto::trace::v1::Span &span() const noexcept { return span_; }

Expand Down Expand Up @@ -85,6 +100,11 @@ 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_;
};
} // namespace otlp
} // namespace exporter
Expand Down
4 changes: 3 additions & 1 deletion exporters/otlp/src/otlp_grpc_exporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ OtlpGrpcExporter::~OtlpGrpcExporter()

std::unique_ptr<sdk::trace::Recordable> OtlpGrpcExporter::MakeRecordable() noexcept
{
return std::unique_ptr<sdk::trace::Recordable>(new OtlpRecordable);
return std::unique_ptr<sdk::trace::Recordable>(
new OtlpRecordable(options_.max_attributes, options_.max_events, options_.max_links,
options_.max_attributes_per_event, options_.max_attributes_per_link));
}

sdk::common::ExportResult OtlpGrpcExporter::Export(
Expand Down
5 changes: 3 additions & 2 deletions exporters/otlp/src/otlp_http_exporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ OtlpHttpExporter::OtlpHttpExporter(std::unique_ptr<OtlpHttpClient> http_client)

std::unique_ptr<opentelemetry::sdk::trace::Recordable> OtlpHttpExporter::MakeRecordable() noexcept
{
return std::unique_ptr<opentelemetry::sdk::trace::Recordable>(
new exporter::otlp::OtlpRecordable());
return std::unique_ptr<opentelemetry::sdk::trace::Recordable>(new exporter::otlp::OtlpRecordable(
options_.max_attributes, options_.max_events, options_.max_links,
options_.max_attributes_per_event, options_.max_attributes_per_link));
}

opentelemetry::sdk::common::ExportResult OtlpHttpExporter::Export(
Expand Down
27 changes: 27 additions & 0 deletions exporters/otlp/src/otlp_recordable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ void OtlpRecordable::SetResource(const sdk::resource::Resource &resource) noexce
void OtlpRecordable::SetAttribute(nostd::string_view key,
const common::AttributeValue &value) noexcept
{
if (static_cast<uint32_t>(span_.attributes_size()) >= max_attributes_)
{
span_.set_dropped_attributes_count(span_.dropped_attributes_count() + 1);
return;
}

auto *attribute = span_.add_attributes();
OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value, false);
}
Expand All @@ -128,11 +134,22 @@ void OtlpRecordable::AddEvent(nostd::string_view name,
common::SystemTimestamp timestamp,
const common::KeyValueIterable &attributes) noexcept
{
if (static_cast<uint32_t>(span_.events_size()) >= max_events_)
{
span_.set_dropped_events_count(span_.dropped_events_count() + 1);
return;
}

auto *event = span_.add_events();
event->set_name(name.data(), name.size());
event->set_time_unix_nano(timestamp.time_since_epoch().count());

attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept {
if (static_cast<uint32_t>(event->attributes_size()) >= max_attributes_per_event_)
{
event->set_dropped_attributes_count(event->dropped_attributes_count() + 1);
return true;
}
OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value, false);
return true;
});
Expand All @@ -141,13 +158,23 @@ void OtlpRecordable::AddEvent(nostd::string_view name,
void OtlpRecordable::AddLink(const trace::SpanContext &span_context,
const common::KeyValueIterable &attributes) noexcept
{
if (static_cast<uint32_t>(span_.links_size()) >= max_links_)
{
span_.set_dropped_links_count(span_.dropped_links_count() + 1);
return;
}
auto *link = span_.add_links();
link->set_trace_id(reinterpret_cast<const char *>(span_context.trace_id().Id().data()),
trace::TraceId::kSize);
link->set_span_id(reinterpret_cast<const char *>(span_context.span_id().Id().data()),
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<uint32_t>(link->attributes_size()) >= max_attributes_per_link_)
{
link->set_dropped_attributes_count(link->dropped_attributes_count() + 1);
return true;
}
OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value, false);
return true;
});
Expand Down
77 changes: 77 additions & 0 deletions exporters/otlp/test/otlp_recordable_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,83 @@ TYPED_TEST(IntAttributeTest, SetIntArrayAttribute)
EXPECT_EQ(rec.span().attributes(0).value().array_value().values(i).int_value(), int_span[i]);
}
}

TEST(OtlpRecordableTest, TestCollectionLimits)
{
// Initialize recordable with strict limits:
// Max Attributes: 2, Max Events: 2, Max Links: 2
// Max Attributes Per Event: 1, Max Attributes Per Link: 1
OtlpRecordable recordable(2, 2, 2, 1, 1);

// Test Attribute Limits
recordable.SetAttribute("attr1", 1);
recordable.SetAttribute("attr2", 2);
recordable.SetAttribute("attr3", 3); // Should be dropped

EXPECT_EQ(recordable.span().attributes_size(), 2);
EXPECT_EQ(recordable.span().dropped_attributes_count(), 1);

// Test Event Limits
std::map<std::string, int> empty_map;
common::KeyValueIterableView<std::map<std::string, int>> empty_attrs(empty_map);

recordable.AddEvent("event1", std::chrono::system_clock::now(), empty_attrs);
recordable.AddEvent("event2", std::chrono::system_clock::now(), empty_attrs);
recordable.AddEvent("event3", std::chrono::system_clock::now(),
empty_attrs); // Should be dropped

EXPECT_EQ(recordable.span().events_size(), 2);
EXPECT_EQ(recordable.span().dropped_events_count(), 1);

// Test Per-Event Attribute Limits
std::map<std::string, int> attr_map = {{"e_attr1", 1}, {"e_attr2", 2}};
common::KeyValueIterableView<std::map<std::string, int>> event_attrs(attr_map);

OtlpRecordable event_recordable(10, 10, 10, 1, 1);
event_recordable.AddEvent("event_with_attrs", std::chrono::system_clock::now(), event_attrs);

auto &event_list = event_recordable.span().events();
ASSERT_EQ(event_list.size(), 1);
// The event itself should have 1 attribute, and 1 dropped
EXPECT_EQ(event_list[0].attributes_size(), 1);
EXPECT_EQ(event_list[0].dropped_attributes_count(), 1);
}
Comment thread
marcalff marked this conversation as resolved.

TEST(OtlpRecordableTest, TestLinkLimits)
{
// Limits: Max Links: 2, Max Attributes Per Link: 1
OtlpRecordable recordable(10, 10, 2, 10, 1);

// Dummy SpanContext for the links
trace::SpanContext context = trace::SpanContext::GetInvalid();

// Test Link Limits
// Create an empty attribute map for the links
std::map<std::string, int> empty_map;
common::KeyValueIterableView<std::map<std::string, int>> empty_attrs(empty_map);

recordable.AddLink(context, empty_attrs);
recordable.AddLink(context, empty_attrs);
recordable.AddLink(context, empty_attrs); // Should be dropped (Max 2)

EXPECT_EQ(recordable.span().links_size(), 2);
EXPECT_EQ(recordable.span().dropped_links_count(), 1);

// Test Per-Link Attribute Limits
// Create a map with 2 attributes. Limit is 1
std::map<std::string, int> attr_map = {{"l_attr1", 1}, {"l_attr2", 2}};
common::KeyValueIterableView<std::map<std::string, int>> link_attrs(attr_map);

// Create a new recordable for this sub-test
OtlpRecordable link_recordable(10, 10, 10, 10, 1);
link_recordable.AddLink(context, link_attrs);

auto &link_list = link_recordable.span().links();
ASSERT_EQ(link_list.size(), 1);
// The link itself should have 1 attribute kept, 1 dropped
EXPECT_EQ(link_list[0].attributes_size(), 1);
EXPECT_EQ(link_list[0].dropped_attributes_count(), 1);
}
} // namespace otlp
} // namespace exporter
OPENTELEMETRY_END_NAMESPACE
Loading