diff --git a/api/include/opentelemetry/plugin/tracer.h b/api/include/opentelemetry/plugin/tracer.h index ffd865031..842112375 100644 --- a/api/include/opentelemetry/plugin/tracer.h +++ b/api/include/opentelemetry/plugin/tracer.h @@ -68,9 +68,10 @@ public: nostd::shared_ptr StartSpan( nostd::string_view name, const common::KeyValueIterable &attributes, + const trace::SpanContextKeyValueIterable &links, const trace::StartSpanOptions &options = {}) noexcept override { - auto span = tracer_handle_->tracer().StartSpan(name, attributes, options); + auto span = tracer_handle_->tracer().StartSpan(name, attributes, links, options); if (span == nullptr) { return nostd::shared_ptr(nullptr); diff --git a/api/include/opentelemetry/trace/noop.h b/api/include/opentelemetry/trace/noop.h index 2997e9579..4a7de3255 100644 --- a/api/include/opentelemetry/trace/noop.h +++ b/api/include/opentelemetry/trace/noop.h @@ -9,6 +9,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" @@ -66,6 +67,7 @@ public: // Tracer nostd::shared_ptr StartSpan(nostd::string_view /*name*/, const common::KeyValueIterable & /*attributes*/, + const SpanContextKeyValueIterable & /*links*/, const StartSpanOptions & /*options*/) noexcept override { // Don't allocate a no-op span for every StartSpan call, but use a static diff --git a/api/include/opentelemetry/trace/span_context_kv_iterable.h b/api/include/opentelemetry/trace/span_context_kv_iterable.h new file mode 100644 index 000000000..bcc7465b8 --- /dev/null +++ b/api/include/opentelemetry/trace/span_context_kv_iterable.h @@ -0,0 +1,34 @@ +#pragma once + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/nostd/function_ref.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace trace +{ +/** + * Supports internal iteration over a collection of SpanContext/key-value pairs. + */ +class SpanContextKeyValueIterable +{ +public: + virtual ~SpanContextKeyValueIterable() = default; + + /** + * Iterate over SpanContext/key-value pairs + * @param callback a callback to invoke for each key-value for each SpanContext. + * If the callback returns false, the iteration is aborted. + * @return true if every SpanContext/key-value pair was iterated over + */ + virtual bool ForEachKeyValue( + nostd::function_ref + callback) const noexcept = 0; + /** + * @return the number of key-value pairs + */ + virtual size_t size() const noexcept = 0; +}; +} // namespace trace +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/trace/span_context_kv_iterable_view.h b/api/include/opentelemetry/trace/span_context_kv_iterable_view.h new file mode 100644 index 000000000..09aba9321 --- /dev/null +++ b/api/include/opentelemetry/trace/span_context_kv_iterable_view.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/nostd/utility.h" +#include "opentelemetry/trace/span_context_kv_iterable.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace trace +{ +namespace detail +{ +template +inline void take_span_context_kv(SpanContext, common::KeyValueIterableView) +{} + +template ::value> * = nullptr> +inline void take_span_context_kv(SpanContext, T &) +{} + +inline void take_span_context_kv( + SpanContext, + std::initializer_list>) +{} + +template +auto is_span_context_kv_iterable_impl(T iterable) + -> decltype(take_span_context_kv(std::begin(iterable)->first, std::begin(iterable)->second), + nostd::size(iterable), + std::true_type{}); + +std::false_type is_span_context_kv_iterable_impl(...); + +template +struct is_span_context_kv_iterable +{ + static const bool value = + decltype(detail::is_span_context_kv_iterable_impl(std::declval()))::value; +}; +} // namespace detail + +template +class SpanContextKeyValueIterableView final : public SpanContextKeyValueIterable +{ + static_assert(detail::is_span_context_kv_iterable::value, + "Must be a context/key-value iterable"); + +public: + explicit SpanContextKeyValueIterableView(const T &links) noexcept : container_{&links} {} + + bool ForEachKeyValue( + nostd::function_ref + callback) const noexcept override + { + auto iter = std::begin(*container_); + auto last = std::end(*container_); + for (; iter != last; ++iter) + { + if (!this->do_callback(iter->first, iter->second, callback)) + { + return false; + } + } + return true; + } + + size_t size() const noexcept override { return nostd::size(*container_); } + +private: + const T *container_; + + bool do_callback( + SpanContext span_context, + const common::KeyValueIterable &attributes, + nostd::function_ref + callback) const noexcept + { + if (!callback(span_context, attributes)) + { + return false; + } + return true; + } + + template ::value> * = nullptr> + bool do_callback( + SpanContext span_context, + const U &attributes, + nostd::function_ref callback) const + noexcept + { + return do_callback(span_context, common::KeyValueIterableView(attributes), callback); + } + + bool do_callback( + SpanContext span_context, + std::initializer_list> attributes, + nostd::function_ref callback) const + noexcept + { + return do_callback(span_context, + nostd::span>{ + attributes.begin(), attributes.end()}, + callback); + } +}; +} // namespace trace +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/trace/tracer.h b/api/include/opentelemetry/trace/tracer.h index 9ca8b94b8..09cf389d8 100644 --- a/api/include/opentelemetry/trace/tracer.h +++ b/api/include/opentelemetry/trace/tracer.h @@ -6,6 +6,7 @@ #include "opentelemetry/trace/default_span.h" #include "opentelemetry/trace/scope.h" #include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" #include "opentelemetry/version.h" #include @@ -33,12 +34,13 @@ public: */ virtual nostd::shared_ptr StartSpan(nostd::string_view name, const common::KeyValueIterable &attributes, + const SpanContextKeyValueIterable &links, const StartSpanOptions &options = {}) noexcept = 0; nostd::shared_ptr StartSpan(nostd::string_view name, const StartSpanOptions &options = {}) noexcept { - return this->StartSpan(name, {}, options); + return this->StartSpan(name, {}, {}, options); } template StartSpan(name, common::KeyValueIterableView(attributes), options); + return this->StartSpan(name, attributes, {}, options); + } + + template ::value> * = nullptr, + nostd::enable_if_t::value> * = nullptr> + nostd::shared_ptr StartSpan(nostd::string_view name, + const T &attributes, + const U &links, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan(name, common::KeyValueIterableView(attributes), + SpanContextKeyValueIterableView(links), options); } nostd::shared_ptr StartSpan( @@ -55,10 +70,60 @@ public: std::initializer_list> attributes, const StartSpanOptions &options = {}) noexcept { + + return this->StartSpan(name, attributes, {}, options); + } + + template ::value> * = nullptr> + nostd::shared_ptr StartSpan( + nostd::string_view name, + const T &attributes, + std::initializer_list< + std::pair>>> + links, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan( + name, attributes, + nostd::span>>>{ + links.begin(), links.end()}, + options); + } + + template ::value> * = nullptr> + nostd::shared_ptr StartSpan( + nostd::string_view name, + std::initializer_list> attributes, + const T &links, + const StartSpanOptions &options = {}) noexcept + { return this->StartSpan(name, nostd::span>{ attributes.begin(), attributes.end()}, - options); + links, options); + } + + nostd::shared_ptr StartSpan( + nostd::string_view name, + std::initializer_list> attributes, + std::initializer_list< + std::pair>>> + links, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan( + name, + nostd::span>{attributes.begin(), + attributes.end()}, + nostd::span>>>{ + links.begin(), links.end()}, + options); } /** diff --git a/api/test/trace/noop_test.cc b/api/test/trace/noop_test.cc index 4a069430e..e623e109f 100644 --- a/api/test/trace/noop_test.cc +++ b/api/test/trace/noop_test.cc @@ -43,3 +43,15 @@ TEST(NoopTest, UseNoopTracers) s1->GetContext(); } + +TEST(NoopTest, StartSpan) +{ + std::shared_ptr tracer{new NoopTracer{}}; + + std::map attrs = {{"a", "3"}}; + std::vector>> links = { + {SpanContext(false, false), attrs}}; + auto s1 = tracer->StartSpan("abc", attrs, links); + + auto s2 = tracer->StartSpan("efg", {{"a", 3}}, {{SpanContext(false, false), {{"b", 4}}}}); +} diff --git a/examples/plugin/plugin/tracer.cc b/examples/plugin/plugin/tracer.cc index 14703c706..cd7d19a1e 100644 --- a/examples/plugin/plugin/tracer.cc +++ b/examples/plugin/plugin/tracer.cc @@ -19,6 +19,7 @@ public: Span(std::shared_ptr &&tracer, nostd::string_view name, const opentelemetry::common::KeyValueIterable & /*attributes*/, + const opentelemetry::trace::SpanContextKeyValueIterable & /*links*/, const trace::StartSpanOptions & /*options*/) noexcept : tracer_{std::move(tracer)}, name_{name}, span_context_{trace::SpanContext::GetInvalid()} { @@ -66,8 +67,9 @@ Tracer::Tracer(nostd::string_view /*output*/) {} nostd::shared_ptr Tracer::StartSpan( nostd::string_view name, const opentelemetry::common::KeyValueIterable &attributes, + const opentelemetry::trace::SpanContextKeyValueIterable &links, const trace::StartSpanOptions &options) noexcept { return nostd::shared_ptr{ - new (std::nothrow) Span{this->shared_from_this(), name, attributes, options}}; + new (std::nothrow) Span{this->shared_from_this(), name, attributes, links, options}}; } diff --git a/examples/plugin/plugin/tracer.h b/examples/plugin/plugin/tracer.h index 7d6048c88..0af97928f 100644 --- a/examples/plugin/plugin/tracer.h +++ b/examples/plugin/plugin/tracer.h @@ -14,6 +14,7 @@ public: opentelemetry::nostd::shared_ptr StartSpan( opentelemetry::nostd::string_view name, const opentelemetry::common::KeyValueIterable & /*attributes*/, + const opentelemetry::trace::SpanContextKeyValueIterable & /*links*/, const opentelemetry::trace::StartSpanOptions & /*options */) noexcept override; void ForceFlushWithMicroseconds(uint64_t /*timeout*/) noexcept override {} diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index 42d16ce21..f633c8ed6 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -47,6 +47,7 @@ public: nostd::shared_ptr StartSpan( nostd::string_view name, const opentelemetry::common::KeyValueIterable &attributes, + const trace_api::SpanContextKeyValueIterable &links, const trace_api::StartSpanOptions &options = {}) noexcept override; void ForceFlushWithMicroseconds(uint64_t timeout) noexcept override; diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index 417051e46..25219453e 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -61,6 +61,7 @@ Span::Span(std::shared_ptr &&tracer, std::shared_ptr processor, nostd::string_view name, const opentelemetry::common::KeyValueIterable &attributes, + const trace_api::SpanContextKeyValueIterable &links, const trace_api::StartSpanOptions &options, const trace_api::SpanContext &parent_span_context) noexcept : tracer_{std::move(tracer)}, @@ -98,6 +99,12 @@ Span::Span(std::shared_ptr &&tracer, return true; }); + links.ForEachKeyValue([&](opentelemetry::trace::SpanContext span_context, + const opentelemetry::common::KeyValueIterable &attributes) { + recordable_->AddLink(span_context, attributes); + return true; + }); + recordable_->SetStartTime(NowOr(options.start_system_time)); start_steady_time = NowOr(options.start_steady_time); processor_->OnStart(*recordable_); diff --git a/sdk/src/trace/span.h b/sdk/src/trace/span.h index f11711e2a..d4499985f 100644 --- a/sdk/src/trace/span.h +++ b/sdk/src/trace/span.h @@ -19,6 +19,7 @@ public: std::shared_ptr processor, nostd::string_view name, const opentelemetry::common::KeyValueIterable &attributes, + const trace_api::SpanContextKeyValueIterable &links, const trace_api::StartSpanOptions &options, const trace_api::SpanContext &parent_span_context) noexcept; diff --git a/sdk/src/trace/tracer.cc b/sdk/src/trace/tracer.cc index db88cc2b4..0854f8b5e 100644 --- a/sdk/src/trace/tracer.cc +++ b/sdk/src/trace/tracer.cc @@ -54,6 +54,7 @@ trace_api::SpanContext GetCurrentSpanContext(const trace_api::SpanContext &expli nostd::shared_ptr Tracer::StartSpan( nostd::string_view name, const opentelemetry::common::KeyValueIterable &attributes, + const trace_api::SpanContextKeyValueIterable &links, const trace_api::StartSpanOptions &options) noexcept { trace_api::SpanContext parent = GetCurrentSpanContext(options.parent); @@ -72,7 +73,7 @@ nostd::shared_ptr Tracer::StartSpan( else { auto span = nostd::shared_ptr{new (std::nothrow) Span{ - this->shared_from_this(), processor_.load(), name, attributes, options, parent}}; + this->shared_from_this(), processor_.load(), name, attributes, links, options, parent}}; // if the attributes is not nullptr, add attributes to the span. if (sampling_result.attributes) diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index cc21a15b2..22160617d 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -13,6 +13,7 @@ using opentelemetry::core::SteadyTimestamp; using opentelemetry::core::SystemTimestamp; namespace nostd = opentelemetry::nostd; namespace common = opentelemetry::common; +using opentelemetry::common::KeyValueIterableView; using opentelemetry::exporter::memory::InMemorySpanData; using opentelemetry::exporter::memory::InMemorySpanExporter; using opentelemetry::trace::SpanContext; @@ -332,6 +333,83 @@ TEST(Tracer, SpanSetEvents) ASSERT_EQ(1, span_data_events[2].GetAttributes().size()); } +TEST(Tracer, SpanSetLinks) +{ + std::unique_ptr exporter(new InMemorySpanExporter()); + std::shared_ptr span_data = exporter->GetData(); + auto tracer = initTracer(std::move(exporter)); + + { + + // Single span link passed through Initialization list + tracer->StartSpan("efg", {{"attr1", 1}}, {{SpanContext(false, false), {{"attr2", 2}}}})->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); + } + { + + // Multiple span links passed through Initialization list + tracer + ->StartSpan("efg", {{"attr1", 1}}, + {{SpanContext(false, false), {{"attr2", 2}}}, + {SpanContext(false, false), {{"attr3", 3}}}}) + ->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); + } + + { + + // Multiple links, each with multiple attributes passed through Initialization list + tracer + ->StartSpan("efg", {{"attr1", 1}}, + {{SpanContext(false, false), {{"attr2", 2}, {"attr3", 3}}}, + {SpanContext(false, false), {{"attr4", 4}}}}) + ->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}}; + tracer->StartSpan("efg", attrs1, links)->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"); + } +} + TEST(Tracer, TestAlwaysOnSampler) { std::unique_ptr exporter(new InMemorySpanExporter());