diff --git a/CHANGELOG.md b/CHANGELOG.md index 536a90487a..a76b91692b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Increment the: [#2371](https://github.com/open-telemetry/opentelemetry-cpp/pull/2371) * [BUILD] enum CanonicalCode names too generic... conflict with old C defines [#2385](https://github.com/open-telemetry/opentelemetry-cpp/pull/2385) +* [API] Add a new AddLink() operation to Span + [#2380](https://github.com/open-telemetry/opentelemetry-cpp/pull/2380) Important changes: @@ -34,6 +36,15 @@ Important changes: * When building with `CMake` option `WITH_ABI_VERSION_1=ON` (by default) the `ABI` is unchanged, and the fix is not available. +* [API] Add a new AddLink() operation to Span + [#2380](https://github.com/open-telemetry/opentelemetry-cpp/pull/2380) + * New `API` Span::AddLink() adds a single link to a span. + * New `API` Span::AddLinks() adds multiple links to a span. + * Because this is an `ABI` breaking change, the fix is only available + with the `CMake` option `WITH_ABI_VERSION_2=ON`. + * When building with `CMake` option `WITH_ABI_VERSION_1=ON` (by default) + the `ABI` is unchanged, and the fix is not available. + * [BUILD] Make WITH_OTLP_HTTP_SSL_PREVIEW mainstream [#2378](https://github.com/open-telemetry/opentelemetry-cpp/pull/2378) * The experimental `CMake` option `WITH_OTLP_HTTP_SSL_PREVIEW` diff --git a/api/include/opentelemetry/plugin/tracer.h b/api/include/opentelemetry/plugin/tracer.h index b87f9e889f..068b08071d 100644 --- a/api/include/opentelemetry/plugin/tracer.h +++ b/api/include/opentelemetry/plugin/tracer.h @@ -7,6 +7,7 @@ #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/plugin/detail/tracer_handle.h" +#include "opentelemetry/trace/span_context_kv_iterable.h" #include "opentelemetry/trace/tracer.h" #include "opentelemetry/version.h" @@ -49,6 +50,19 @@ class Span final : public trace::Span span_->AddEvent(name, timestamp, attributes); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + void AddLink(const trace::SpanContext &target, + const common::KeyValueIterable &attrs) noexcept override + { + span_->AddLink(target, attrs); + } + + void AddLinks(const trace::SpanContextKeyValueIterable &links) noexcept override + { + span_->AddLinks(links); + } +#endif + void SetStatus(trace::StatusCode code, nostd::string_view description) noexcept override { span_->SetStatus(code, description); diff --git a/api/include/opentelemetry/trace/default_span.h b/api/include/opentelemetry/trace/default_span.h index cccc7951ad..7e3979501e 100644 --- a/api/include/opentelemetry/trace/default_span.h +++ b/api/include/opentelemetry/trace/default_span.h @@ -47,6 +47,14 @@ class DefaultSpan : public Span const common::KeyValueIterable & /* attributes */) noexcept override {} +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + void AddLink(const SpanContext & /* target */, + const common::KeyValueIterable & /* attrs */) noexcept override + {} + + void AddLinks(const SpanContextKeyValueIterable & /* links */) noexcept override {} +#endif + void SetStatus(StatusCode /* status */, nostd::string_view /* description */) noexcept override {} void UpdateName(nostd::string_view /* name */) noexcept override {} diff --git a/api/include/opentelemetry/trace/noop.h b/api/include/opentelemetry/trace/noop.h index c6a5838867..407f3c1ac9 100644 --- a/api/include/opentelemetry/trace/noop.h +++ b/api/include/opentelemetry/trace/noop.h @@ -15,6 +15,7 @@ #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_context_kv_iterable.h" #include "opentelemetry/trace/tracer.h" #include "opentelemetry/trace/tracer_provider.h" #include "opentelemetry/version.h" @@ -58,6 +59,14 @@ class OPENTELEMETRY_EXPORT NoopSpan final : public Span const common::KeyValueIterable & /*attributes*/) noexcept override {} +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + void AddLink(const SpanContext & /* target */, + const common::KeyValueIterable & /* attrs */) noexcept override + {} + + void AddLinks(const SpanContextKeyValueIterable & /* links */) noexcept override {} +#endif + void SetStatus(StatusCode /*code*/, nostd::string_view /*description*/) noexcept override {} void UpdateName(nostd::string_view /*name*/) noexcept override {} diff --git a/api/include/opentelemetry/trace/span.h b/api/include/opentelemetry/trace/span.h index 8fb371d48c..27e91ceec4 100644 --- a/api/include/opentelemetry/trace/span.h +++ b/api/include/opentelemetry/trace/span.h @@ -11,6 +11,8 @@ #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/type_traits.h" #include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_context_kv_iterable.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" #include "opentelemetry/trace/span_metadata.h" #include "opentelemetry/version.h" @@ -23,6 +25,31 @@ class Tracer; /** * A Span represents a single operation within a Trace. + * + * Span attributes can be provided: + * - at span creation time, using Tracer::StartSpan(), + * - during the span lifetime, using Span::SetAttribute() + * + * Please note that head samplers, + * in the SDK (@ref opentelemetry::sdk::trace::Sampler), + * can only make sampling decisions based on data known + * at span creation time. + * + * When attributes are known early, adding attributes + * with @ref opentelemetry::trace::Tracer::StartSpan() is preferable. + * + * Attributes added or changed with Span::SetAttribute() + * can not change a sampler decision. + * + * Likewise, links can be provided: + * - at span creation time, using Tracer::StartSpan(), + * - during the span lifetime, using Span::AddLink() or Span::AddLinks(). + * + * When links are known early, adding links + * with @ref opentelemetry::trace::Tracer::StartSpan() is preferable. + * + * Links added with Span::AddLink() or Span::AddLinks() + * can not change a sampler decision. */ class Span { @@ -40,9 +67,14 @@ class Span Span &operator=(const Span &) = delete; Span &operator=(Span &&) = delete; - // Sets an attribute on the Span. If the Span previously contained a mapping - // for - // the key, the old value is replaced. + /** + * Sets an attribute on the Span (ABI). + * + * If the Span previously contained a mapping for the key, + * the old value is replaced. + * + * See comments about sampling in @ref opentelemetry::trace::Span + */ virtual void SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept = 0; @@ -98,6 +130,106 @@ class Span attributes.begin(), attributes.end()}); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + + /** + * Add link (ABI). + * + * See comments about sampling in @ref opentelemetry::trace::Span + * + * @since ABI_VERSION 2 + */ + virtual void AddLink(const SpanContext &target, + const common::KeyValueIterable &attrs) noexcept = 0; + + /** + * Add links (ABI). + * + * See comments about sampling in @ref opentelemetry::trace::Span + * + * @since ABI_VERSION 2 + */ + virtual void AddLinks(const SpanContextKeyValueIterable &links) noexcept = 0; + + /** + * Add link (API helper). + * + * See comments about sampling in @ref opentelemetry::trace::Span + * + * @since ABI_VERSION 2 + */ + template ::value> * = nullptr> + void AddLink(const SpanContext &target, const U &attrs) + { + common::KeyValueIterableView view(attrs); + this->AddLink(target, view); + } + + /** + * Add link (API helper). + * + * See comments about sampling in @ref opentelemetry::trace::Span + * + * @since ABI_VERSION 2 + */ + void AddLink(const SpanContext &target, + std::initializer_list> attrs) + { + /* Build a container from std::initializer_list. */ + nostd::span> container{ + attrs.begin(), attrs.end()}; + + /* Build a view on the container. */ + common::KeyValueIterableView< + nostd::span>> + view(container); + + return this->AddLink(target, view); + } + + /** + * Add links (API helper). + * + * See comments about sampling in @ref opentelemetry::trace::Span + * + * @since ABI_VERSION 2 + */ + template ::value> * = nullptr> + void AddLinks(const U &links) + { + SpanContextKeyValueIterableView view(links); + this->AddLinks(view); + } + + /** + * Add links (API helper). + * + * See comments about sampling in @ref opentelemetry::trace::Span + * + * @since ABI_VERSION 2 + */ + void AddLinks( + std::initializer_list< + std::pair>>> + links) + { + /* Build a container from std::initializer_list. */ + nostd::span>>> + container{links.begin(), links.end()}; + + /* Build a view on the container. */ + SpanContextKeyValueIterableView>>>> + view(container); + + return this->AddLinks(view); + } + +#endif /* OPENTELEMETRY_ABI_VERSION_NO */ + // Sets the status of the span. The default status is Unset. Only the value of // the last call will be // recorded, and implementations are free to ignore previous calls. diff --git a/api/include/opentelemetry/trace/span_context_kv_iterable.h b/api/include/opentelemetry/trace/span_context_kv_iterable.h index 8f3010ce9d..aed3474272 100644 --- a/api/include/opentelemetry/trace/span_context_kv_iterable.h +++ b/api/include/opentelemetry/trace/span_context_kv_iterable.h @@ -6,6 +6,7 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable_view.h" #include "opentelemetry/nostd/function_ref.h" +#include "opentelemetry/trace/span_context.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE diff --git a/api/test/trace/noop_test.cc b/api/test/trace/noop_test.cc index 130496faf0..b47700d442 100644 --- a/api/test/trace/noop_test.cc +++ b/api/test/trace/noop_test.cc @@ -46,6 +46,21 @@ TEST(NoopTest, UseNoopTracers) s1->GetContext(); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +TEST(NoopTest, UseNoopTracersAbiv2) +{ + std::shared_ptr tracer{new trace_api::NoopTracer{}}; + auto s1 = tracer->StartSpan("abc"); + + EXPECT_EQ(s1->IsRecording(), false); + + trace_api::SpanContext target(false, false); + s1->AddLink(target, {{"noop1", 1}}); + + s1->AddLinks({{trace_api::SpanContext(false, false), {{"noop2", 2}}}}); +} +#endif /* OPENTELEMETRY_ABI_VERSION_NO >= 2 */ + TEST(NoopTest, StartSpan) { std::shared_ptr tracer{new trace_api::NoopTracer{}}; diff --git a/examples/plugin/plugin/tracer.cc b/examples/plugin/plugin/tracer.cc index 55de64438d..14d1a746df 100644 --- a/examples/plugin/plugin/tracer.cc +++ b/examples/plugin/plugin/tracer.cc @@ -49,6 +49,14 @@ class Span final : public trace::Span const common::KeyValueIterable & /*attributes*/) noexcept override {} +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + void AddLink(const trace::SpanContext & /* target */, + const common::KeyValueIterable & /* attrs */) noexcept override + {} + + void AddLinks(const trace::SpanContextKeyValueIterable & /* links */) noexcept override {} +#endif + void SetStatus(trace::StatusCode /*code*/, nostd::string_view /*description*/) noexcept override {} diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index c7524d43aa..25708cddd9 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -146,6 +146,35 @@ void Span::AddEvent(nostd::string_view name, recordable_->AddEvent(name, timestamp, attributes); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +void Span::AddLink(const opentelemetry::trace::SpanContext &target, + const opentelemetry::common::KeyValueIterable &attrs) noexcept +{ + std::lock_guard lock_guard{mu_}; + if (recordable_ == nullptr) + { + return; + } + + recordable_->AddLink(target, attrs); +} + +void Span::AddLinks(const opentelemetry::trace::SpanContextKeyValueIterable &links) noexcept +{ + std::lock_guard lock_guard{mu_}; + if (recordable_ == nullptr) + { + return; + } + + links.ForEachKeyValue([&](opentelemetry::trace::SpanContext span_context, + const common::KeyValueIterable &attributes) { + recordable_->AddLink(span_context, attributes); + return true; + }); +} +#endif + void Span::SetStatus(opentelemetry::trace::StatusCode code, nostd::string_view description) noexcept { std::lock_guard lock_guard{mu_}; diff --git a/sdk/src/trace/span.h b/sdk/src/trace/span.h index 75e31e9500..eb603b9025 100644 --- a/sdk/src/trace/span.h +++ b/sdk/src/trace/span.h @@ -42,6 +42,13 @@ class Span final : public opentelemetry::trace::Span opentelemetry::common::SystemTimestamp timestamp, const opentelemetry::common::KeyValueIterable &attributes) noexcept override; +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + void AddLink(const opentelemetry::trace::SpanContext &target, + const opentelemetry::common::KeyValueIterable &attrs) noexcept override; + + void AddLinks(const opentelemetry::trace::SpanContextKeyValueIterable &links) noexcept override; +#endif + void SetStatus(opentelemetry::trace::StatusCode code, nostd::string_view description) noexcept override; diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index 45b5fc010f..36cc135a1d 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -519,6 +519,392 @@ TEST(Tracer, SpanSetLinks) } } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +TEST(Tracer, SpanAddLinkAbiv2) +{ + InMemorySpanExporter *exporter = new InMemorySpanExporter(); + std::shared_ptr span_data = exporter->GetData(); + auto tracer = initTracer(std::unique_ptr{exporter}); + + { + auto span = tracer->StartSpan("span"); + SpanContext target(false, false); + // Single link attribute passed through Initialization list + span->AddLink(target, {{"attr2", 2}}); + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(1, span_data_links.size()); + auto link = span_data_links.at(0); + auto attrs = link.GetAttributes(); + ASSERT_EQ(nostd::get(attrs.at("attr2")), 2); + } + + { + auto span = tracer->StartSpan("span"); + SpanContext target(false, false); + // Multiple link attributes passed through Initialization list + span->AddLink(target, {{"attr2", 2}, {"attr3", 3}}); + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(1, span_data_links.size()); + auto link = span_data_links.at(0); + auto attrs = link.GetAttributes(); + ASSERT_EQ(nostd::get(attrs.at("attr2")), 2); + ASSERT_EQ(nostd::get(attrs.at("attr3")), 3); + } + + { + std::map attrs_map = {{"attr1", "1"}, {"attr2", "2"}}; + + auto span = tracer->StartSpan("span"); + SpanContext target(false, false); + span->AddLink(target, attrs_map); + span->End(); + + auto spans = span_data->GetSpans(); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(1, span_data_links.size()); + auto link = span_data_links.at(0); + auto attrs = link.GetAttributes(); + ASSERT_EQ(nostd::get(attrs.at("attr1")), "1"); + ASSERT_EQ(nostd::get(attrs.at("attr2")), "2"); + } + + { + auto span = tracer->StartSpan("span"); + SpanContext target(false, false); + + // Single link attribute passed through Initialization list + span->AddLink(target, {{"attr1", 1}}); + + // Multiple link attributes passed through Initialization list + span->AddLink(target, {{"attr2", 2}, {"attr3", 3}}); + + std::map attrs_map = {{"attr4", "4"}, {"attr5", "5"}}; + span->AddLink(target, attrs_map); + + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(3, span_data_links.size()); + + { + auto link = span_data_links.at(0); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr1")), 1); + } + + { + auto link = span_data_links.at(1); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attr2")), 2); + ASSERT_EQ(nostd::get(attrs.at("attr3")), 3); + } + + { + auto link = span_data_links.at(2); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attr4")), "4"); + ASSERT_EQ(nostd::get(attrs.at("attr5")), "5"); + } + } +} + +TEST(Tracer, SpanAddLinksAbiv2) +{ + InMemorySpanExporter *exporter = new InMemorySpanExporter(); + std::shared_ptr span_data = exporter->GetData(); + auto tracer = initTracer(std::unique_ptr{exporter}); + + { + auto span = tracer->StartSpan("span"); + // Single span link passed through Initialization list + span->AddLinks({{SpanContext(false, false), {{"attr2", 2}}}}); + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(1, span_data_links.size()); + auto link = span_data_links.at(0); + ASSERT_EQ(nostd::get(link.GetAttributes().at("attr2")), 2); + } + + { + auto span = tracer->StartSpan("span"); + // Multiple span links passed through Initialization list + span->AddLinks( + {{SpanContext(false, false), {{"attr2", 2}}}, {SpanContext(false, false), {{"attr3", 3}}}}); + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(2, span_data_links.size()); + auto link1 = span_data_links.at(0); + ASSERT_EQ(nostd::get(link1.GetAttributes().at("attr2")), 2); + auto link2 = span_data_links.at(1); + ASSERT_EQ(nostd::get(link2.GetAttributes().at("attr3")), 3); + } + + { + auto span = tracer->StartSpan("span"); + // Multiple links, each with multiple attributes passed through Initialization list + span->AddLinks({{SpanContext(false, false), {{"attr2", 2}, {"attr3", 3}}}, + {SpanContext(false, false), {{"attr4", 4}}}}); + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(2, span_data_links.size()); + auto link1 = span_data_links.at(0); + ASSERT_EQ(nostd::get(link1.GetAttributes().at("attr2")), 2); + ASSERT_EQ(nostd::get(link1.GetAttributes().at("attr3")), 3); + auto link2 = span_data_links.at(1); + ASSERT_EQ(nostd::get(link2.GetAttributes().at("attr4")), 4); + } + + { + std::map attrs1 = {{"attr1", "1"}, {"attr2", "2"}}; + std::map attrs2 = {{"attr3", "3"}, {"attr4", "4"}}; + + std::vector>> links = { + {SpanContext(false, false), attrs1}, {SpanContext(false, false), attrs2}}; + + auto span = tracer->StartSpan("span"); + span->AddLinks(links); + span->End(); + + auto spans = span_data->GetSpans(); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(2, span_data_links.size()); + auto link1 = span_data_links.at(0); + ASSERT_EQ(nostd::get(link1.GetAttributes().at("attr1")), "1"); + ASSERT_EQ(nostd::get(link1.GetAttributes().at("attr2")), "2"); + auto link2 = span_data_links.at(1); + ASSERT_EQ(nostd::get(link2.GetAttributes().at("attr3")), "3"); + ASSERT_EQ(nostd::get(link2.GetAttributes().at("attr4")), "4"); + } + + { + auto span = tracer->StartSpan("span"); + + // Single span link passed through Initialization list + span->AddLinks({{SpanContext(false, false), {{"attr10", 10}}}}); + span->AddLinks({{SpanContext(false, false), {{"attr11", 11}}}}); + + // Multiple span links passed through Initialization list + span->AddLinks({{SpanContext(false, false), {{"attr12", 12}}}, + {SpanContext(false, false), {{"attr13", 13}}}}); + span->AddLinks({{SpanContext(false, false), {{"attr14", 14}}}, + {SpanContext(false, false), {{"attr15", 15}}}}); + + // Multiple links, each with multiple attributes passed through Initialization list + span->AddLinks({{SpanContext(false, false), {{"attr16", 16}, {"attr17", 17}}}, + {SpanContext(false, false), {{"attr18", 18}}}}); + span->AddLinks({{SpanContext(false, false), {{"attr19", 19}, {"attr20", 20}}}, + {SpanContext(false, false), {{"attr21", 21}}}}); + + std::map attrsa1 = {{"attra1", "1"}, {"attra2", "2"}}; + std::map attrsa2 = {{"attra3", "3"}, {"attra4", "4"}}; + + std::vector>> linksa = { + {SpanContext(false, false), attrsa1}, {SpanContext(false, false), attrsa2}}; + + span->AddLinks(linksa); + + std::map attrsb1 = {{"attrb1", "1"}, {"attrb2", "2"}}; + std::map attrsb2 = {{"attrb3", "3"}, {"attrb4", "4"}}; + + std::vector>> linksb = { + {SpanContext(false, false), attrsb1}, {SpanContext(false, false), attrsb2}}; + + span->AddLinks(linksb); + + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(14, span_data_links.size()); + + { + auto link = span_data_links.at(0); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr10")), 10); + } + + { + auto link = span_data_links.at(1); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr11")), 11); + } + + { + auto link = span_data_links.at(2); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr12")), 12); + } + + { + auto link = span_data_links.at(3); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr13")), 13); + } + + { + auto link = span_data_links.at(4); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr14")), 14); + } + + { + auto link = span_data_links.at(5); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr15")), 15); + } + + { + auto link = span_data_links.at(6); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attr16")), 16); + ASSERT_EQ(nostd::get(attrs.at("attr17")), 17); + } + + { + auto link = span_data_links.at(7); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr18")), 18); + } + + { + auto link = span_data_links.at(8); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attr19")), 19); + ASSERT_EQ(nostd::get(attrs.at("attr20")), 20); + } + + { + auto link = span_data_links.at(9); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr21")), 21); + } + + { + auto link = span_data_links.at(10); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attra1")), "1"); + ASSERT_EQ(nostd::get(attrs.at("attra2")), "2"); + } + + { + auto link = span_data_links.at(11); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attra3")), "3"); + ASSERT_EQ(nostd::get(attrs.at("attra4")), "4"); + } + + { + auto link = span_data_links.at(12); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attrb1")), "1"); + ASSERT_EQ(nostd::get(attrs.at("attrb2")), "2"); + } + + { + auto link = span_data_links.at(13); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 2); + ASSERT_EQ(nostd::get(attrs.at("attrb3")), "3"); + ASSERT_EQ(nostd::get(attrs.at("attrb4")), "4"); + } + } +} + +TEST(Tracer, SpanMixLinksAbiv2) +{ + InMemorySpanExporter *exporter = new InMemorySpanExporter(); + std::shared_ptr span_data = exporter->GetData(); + auto tracer = initTracer(std::unique_ptr{exporter}); + + { + // Link 1 added at StartSpan + auto span = + tracer->StartSpan("span", {{"attr1", 1}}, {{SpanContext(false, false), {{"attr2", 2}}}}); + + SpanContext target(false, false); + // Link 2 added with AddLink + span->AddLink(target, {{"attr3", 3}}); + + // Link 3 added with AddLinks + span->AddLinks({{SpanContext(false, false), {{"attr4", 4}}}}); + + span->End(); + + auto spans = span_data->GetSpans(); + ASSERT_EQ(1, spans.size()); + + auto &span_data_links = spans.at(0)->GetLinks(); + ASSERT_EQ(3, span_data_links.size()); + + { + auto link = span_data_links.at(0); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr2")), 2); + } + + { + auto link = span_data_links.at(1); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr3")), 3); + } + + { + auto link = span_data_links.at(2); + auto attrs = link.GetAttributes(); + ASSERT_EQ(attrs.size(), 1); + ASSERT_EQ(nostd::get(attrs.at("attr4")), 4); + } + } +} +#endif /* OPENTELEMETRY_ABI_VERSION_NO >= 2 */ + TEST(Tracer, TestAlwaysOnSampler) { InMemorySpanExporter *exporter = new InMemorySpanExporter();