diff --git a/api/include/opentelemetry/plugin/factory.h b/api/include/opentelemetry/plugin/factory.h index dd849335e7..abec73867a 100644 --- a/api/include/opentelemetry/plugin/factory.h +++ b/api/include/opentelemetry/plugin/factory.h @@ -36,8 +36,9 @@ class Factory final * @param error_message on failure this will contain an error message. * @return a Tracer on success or nullptr on failure. */ - std::shared_ptr MakeTracer(nostd::string_view tracer_config, - std::string &error_message) const noexcept + std::shared_ptr MakeTracer(nostd::string_view tracer_config, + std::string &error_message) const + noexcept { nostd::unique_ptr plugin_error_message; auto tracer_handle = factory_impl_->MakeTracerHandle(tracer_config, plugin_error_message); @@ -46,8 +47,8 @@ class Factory final detail::CopyErrorMessage(plugin_error_message.get(), error_message); return nullptr; } - return std::shared_ptr{new (std::nothrow) - Tracer{library_handle_, std::move(tracer_handle)}}; + return std::shared_ptr{ + new (std::nothrow) Tracer{library_handle_, std::move(tracer_handle)}}; } private: diff --git a/api/include/opentelemetry/plugin/tracer.h b/api/include/opentelemetry/plugin/tracer.h index 3d308e5826..9970e5a533 100644 --- a/api/include/opentelemetry/plugin/tracer.h +++ b/api/include/opentelemetry/plugin/tracer.h @@ -18,6 +18,11 @@ class Span final : public trace::Span {} // trace::Span + void SetAttribute(nostd::string_view name, const common::AttributeValue &&value) noexcept override + { + span_->SetAttribute(name, std::move(value)); + } + void AddEvent(nostd::string_view name) noexcept override { span_->AddEvent(name); } void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override @@ -61,9 +66,10 @@ class Tracer final : public trace::Tracer, public std::enable_shared_from_this StartSpan( nostd::string_view name, + const trace::KeyValueIterable &attributes, const trace::StartSpanOptions &options = {}) noexcept override { - auto span = tracer_handle_->tracer().StartSpan(name, options); + auto span = tracer_handle_->tracer().StartSpan(name, attributes, options); if (span == nullptr) { return nullptr; diff --git a/api/include/opentelemetry/trace/noop.h b/api/include/opentelemetry/trace/noop.h index 50dcc8b558..fbb0e0cb68 100644 --- a/api/include/opentelemetry/trace/noop.h +++ b/api/include/opentelemetry/trace/noop.h @@ -24,6 +24,10 @@ class NoopSpan final : public Span public: explicit NoopSpan(const std::shared_ptr &tracer) noexcept : tracer_{tracer} {} + void SetAttribute(nostd::string_view /*key*/, + const common::AttributeValue && /*value*/) noexcept override + {} + void AddEvent(nostd::string_view /*name*/) noexcept override {} void AddEvent(nostd::string_view /*name*/, core::SystemTimestamp /*timestamp*/) noexcept override @@ -56,6 +60,7 @@ class NoopTracer final : public Tracer, public std::enable_shared_from_this StartSpan(nostd::string_view /*name*/, + const KeyValueIterable & /*attributes*/, const StartSpanOptions & /*options*/) noexcept override { return nostd::unique_ptr{new (std::nothrow) NoopSpan{this->shared_from_this()}}; diff --git a/api/include/opentelemetry/trace/span.h b/api/include/opentelemetry/trace/span.h index 21ed1aaaff..77265387e6 100644 --- a/api/include/opentelemetry/trace/span.h +++ b/api/include/opentelemetry/trace/span.h @@ -2,6 +2,7 @@ #include +#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" @@ -40,7 +41,6 @@ struct StartSpanOptions // Span(Context?) parent; // SpanContext remote_parent; // Links - // Attributes SpanKind kind = SpanKind::kInternal; }; /** @@ -74,13 +74,10 @@ class Span Span &operator=(const Span &) = delete; Span &operator=(Span &&) = delete; - // TODO // Sets an attribute on the Span. If the Span previously contained a mapping for // the key, the old value is replaced. - // - // If an empty string is used as the value, the attribute will be silently - // dropped. Note: this behavior could change in the future. - // virtual void SetAttribute(nostd::string_view key, AttributeValue&& value) = 0; + virtual void SetAttribute(nostd::string_view key, + const common::AttributeValue &&value) noexcept = 0; // Adds an event to the Span. virtual void AddEvent(nostd::string_view name) noexcept = 0; diff --git a/api/include/opentelemetry/trace/tracer.h b/api/include/opentelemetry/trace/tracer.h index 29aa288123..825ff39fb1 100644 --- a/api/include/opentelemetry/trace/tracer.h +++ b/api/include/opentelemetry/trace/tracer.h @@ -22,10 +22,41 @@ class Tracer virtual ~Tracer() = default; /** * Starts a span. + * + * Optionally sets attributes at Span creation from the given key/value pairs. + * + * Attributes will be processed in order, previous attributes with the same + * key will be overwritten. */ virtual nostd::unique_ptr StartSpan(nostd::string_view name, + const KeyValueIterable &attributes, const StartSpanOptions &options = {}) noexcept = 0; + nostd::unique_ptr StartSpan(nostd::string_view name, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan(name, {}, options); + } + + template ::value> * = nullptr> + nostd::unique_ptr StartSpan(nostd::string_view name, + const T &attributes, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan(name, KeyValueIterableView(attributes), options); + } + + nostd::unique_ptr StartSpan( + nostd::string_view name, + std::initializer_list> attributes, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan(name, + nostd::span>{ + attributes.begin(), attributes.end()}, + options); + } + /** * Force any buffered spans to flush. * @param timeout to complete the flush diff --git a/examples/plugin/plugin/tracer.cc b/examples/plugin/plugin/tracer.cc index c6e646d5d0..3aa68e8b15 100644 --- a/examples/plugin/plugin/tracer.cc +++ b/examples/plugin/plugin/tracer.cc @@ -2,9 +2,10 @@ #include -namespace nostd = opentelemetry::nostd; -namespace core = opentelemetry::core; -namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace common = opentelemetry::common; +namespace core = opentelemetry::core; +namespace trace = opentelemetry::trace; namespace { @@ -13,6 +14,7 @@ class Span final : public trace::Span public: Span(std::shared_ptr &&tracer, nostd::string_view name, + const opentelemetry::trace::KeyValueIterable & /*attributes*/, const trace::StartSpanOptions & /*options*/) noexcept : tracer_{std::move(tracer)}, name_{name} { @@ -22,6 +24,10 @@ class Span final : public trace::Span ~Span() { std::cout << "~Span\n"; } // opentelemetry::trace::Span + void SetAttribute(nostd::string_view /*name*/, + const common::AttributeValue && /*value*/) noexcept override + {} + void AddEvent(nostd::string_view /*name*/) noexcept override {} void AddEvent(nostd::string_view /*name*/, core::SystemTimestamp /*timestamp*/) noexcept override @@ -52,9 +58,11 @@ class Span final : public trace::Span Tracer::Tracer(nostd::string_view /*output*/) {} -nostd::unique_ptr Tracer::StartSpan(nostd::string_view name, - const trace::StartSpanOptions &options) noexcept +nostd::unique_ptr Tracer::StartSpan( + nostd::string_view name, + const opentelemetry::trace::KeyValueIterable &attributes, + const trace::StartSpanOptions &options) noexcept { return nostd::unique_ptr{ - new (std::nothrow) Span{this->shared_from_this(), name, options}}; + new (std::nothrow) Span{this->shared_from_this(), name, attributes, options}}; } diff --git a/examples/plugin/plugin/tracer.h b/examples/plugin/plugin/tracer.h index 4d653f32e9..ad1b98633d 100644 --- a/examples/plugin/plugin/tracer.h +++ b/examples/plugin/plugin/tracer.h @@ -13,7 +13,8 @@ class Tracer final : public opentelemetry::trace::Tracer, // opentelemetry::trace::Tracer opentelemetry::nostd::unique_ptr StartSpan( opentelemetry::nostd::string_view name, - const opentelemetry::trace::StartSpanOptions &options) noexcept override; + const opentelemetry::trace::KeyValueIterable & /*attributes*/, + const opentelemetry::trace::StartSpanOptions & /*options */) noexcept override; void ForceFlushWithMicroseconds(uint64_t /*timeout*/) noexcept override {} diff --git a/sdk/include/opentelemetry/sdk/trace/recordable.h b/sdk/include/opentelemetry/sdk/trace/recordable.h index c102958fe3..d901dbfa54 100644 --- a/sdk/include/opentelemetry/sdk/trace/recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/recordable.h @@ -1,5 +1,6 @@ #pragma once +#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/trace/canonical_code.h" @@ -33,6 +34,14 @@ class Recordable opentelemetry::trace::SpanId span_id, opentelemetry::trace::SpanId parent_span_id) noexcept = 0; + /** + * Set an attribute of a span. + * @param name the name of the attribute + * @param value the attribute value + */ + virtual void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &&value) noexcept = 0; + /** * Add an event to a span. * @param name the name of the event diff --git a/sdk/include/opentelemetry/sdk/trace/span_data.h b/sdk/include/opentelemetry/sdk/trace/span_data.h index 8ac620ad1b..c3b557dc9b 100644 --- a/sdk/include/opentelemetry/sdk/trace/span_data.h +++ b/sdk/include/opentelemetry/sdk/trace/span_data.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/trace/recordable.h" @@ -67,6 +68,15 @@ class SpanData final : public Recordable */ std::chrono::nanoseconds GetDuration() const noexcept { return duration_; } + /** + * Get the attributes for this span + * @return the attributes for this span + */ + const std::unordered_map &GetAttributes() const noexcept + { + return attributes_; + } + void SetIds(opentelemetry::trace::TraceId trace_id, opentelemetry::trace::SpanId span_id, opentelemetry::trace::SpanId parent_span_id) noexcept override @@ -76,6 +86,11 @@ class SpanData final : public Recordable parent_span_id_ = parent_span_id; } + void SetAttribute(nostd::string_view key, const common::AttributeValue &&value) noexcept override + { + attributes_[std::string(key)] = value; + } + void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override { (void)name; @@ -106,6 +121,7 @@ class SpanData final : public Recordable std::string name_; opentelemetry::trace::CanonicalCode status_code_{opentelemetry::trace::CanonicalCode::OK}; std::string status_desc_; + std::unordered_map attributes_; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index 06c83b1058..577785878a 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -37,6 +37,7 @@ class Tracer final : public trace_api::Tracer, public std::enable_shared_from_th nostd::unique_ptr StartSpan( nostd::string_view name, + const trace_api::KeyValueIterable &attributes, 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 a69e6673b0..dde8710ec2 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -41,6 +41,7 @@ SteadyTimestamp NowOr(const SteadyTimestamp &steady) Span::Span(std::shared_ptr &&tracer, std::shared_ptr processor, nostd::string_view name, + const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options) noexcept : tracer_{std::move(tracer)}, processor_{processor}, @@ -55,6 +56,11 @@ Span::Span(std::shared_ptr &&tracer, processor_->OnStart(*recordable_); recordable_->SetName(name); + attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + recordable_->SetAttribute(key, std::move(value)); + return true; + }); + recordable_->SetStartTime(NowOr(options.start_system_time)); start_steady_time = NowOr(options.start_steady_time); } @@ -64,6 +70,13 @@ Span::~Span() End(); } +void Span::SetAttribute(nostd::string_view key, const common::AttributeValue &&value) noexcept +{ + std::lock_guard lock_guard{mu_}; + + recordable_->SetAttribute(key, std::move(value)); +} + void Span::AddEvent(nostd::string_view name) noexcept { (void)name; diff --git a/sdk/src/trace/span.h b/sdk/src/trace/span.h index e1a6f2084a..db8d01ab4e 100644 --- a/sdk/src/trace/span.h +++ b/sdk/src/trace/span.h @@ -18,11 +18,14 @@ class Span final : public trace_api::Span explicit Span(std::shared_ptr &&tracer, std::shared_ptr processor, nostd::string_view name, + const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options) noexcept; ~Span() override; // trace_api::Span + void SetAttribute(nostd::string_view key, const common::AttributeValue &&value) noexcept override; + void AddEvent(nostd::string_view name) noexcept override; void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override; diff --git a/sdk/src/trace/tracer.cc b/sdk/src/trace/tracer.cc index 15e89ceffd..311459060b 100644 --- a/sdk/src/trace/tracer.cc +++ b/sdk/src/trace/tracer.cc @@ -21,10 +21,11 @@ std::shared_ptr Tracer::GetProcessor() const noexcept nostd::unique_ptr Tracer::StartSpan( nostd::string_view name, + const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options) noexcept { - return nostd::unique_ptr{ - new (std::nothrow) Span{this->shared_from_this(), processor_.load(), name, options}}; + return nostd::unique_ptr{new (std::nothrow) Span{ + this->shared_from_this(), processor_.load(), name, attributes, options}}; } void Tracer::ForceFlushWithMicroseconds(uint64_t timeout) noexcept diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc index ea8aab446c..7bf6c34ec1 100644 --- a/sdk/test/trace/span_data_test.cc +++ b/sdk/test/trace/span_data_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/nostd/variant.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_id.h" @@ -20,6 +21,7 @@ TEST(SpanData, DefaultValues) ASSERT_EQ(data.GetDescription(), ""); ASSERT_EQ(data.GetStartTime().time_since_epoch(), std::chrono::nanoseconds(0)); ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(0)); + ASSERT_EQ(data.GetAttributes().size(), 0); } TEST(SpanData, Set) @@ -35,6 +37,7 @@ TEST(SpanData, Set) data.SetStatus(opentelemetry::trace::CanonicalCode::UNKNOWN, "description"); data.SetStartTime(now); data.SetDuration(std::chrono::nanoseconds(1000000)); + data.SetAttribute("attr1", 314159); data.AddEvent("event1", now); ASSERT_EQ(data.GetTraceId(), trace_id); @@ -45,4 +48,5 @@ TEST(SpanData, Set) ASSERT_EQ(data.GetDescription(), "description"); ASSERT_EQ(data.GetStartTime().time_since_epoch(), now.time_since_epoch()); ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(1000000)); + ASSERT_EQ(opentelemetry::nostd::get(data.GetAttributes().at("attr1")), 314159); } diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index e0bc2afde6..056227aa8d 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -7,6 +7,8 @@ using namespace opentelemetry::sdk::trace; using opentelemetry::core::SteadyTimestamp; using opentelemetry::core::SystemTimestamp; +namespace nostd = opentelemetry::nostd; +namespace common = opentelemetry::common; /** * A mock exporter that switches a flag once a valid recordable was received. @@ -23,8 +25,7 @@ class MockSpanExporter final : public SpanExporter return std::unique_ptr(new SpanData); } - ExportResult Export( - const opentelemetry::nostd::span> &recordables) noexcept override + ExportResult Export(const nostd::span> &recordables) noexcept override { for (auto &recordable : recordables) { @@ -47,12 +48,12 @@ class MockSpanExporter final : public SpanExporter namespace { -std::shared_ptr initTracer( +std::shared_ptr initTracer( std::shared_ptr>> &received) { std::unique_ptr exporter(new MockSpanExporter(received)); std::shared_ptr processor(new SimpleSpanProcessor(std::move(exporter))); - return std::shared_ptr(new Tracer(processor)); + return std::shared_ptr(new Tracer(processor)); } } // namespace @@ -91,7 +92,7 @@ TEST(Tracer, StartSpan) ASSERT_LT(std::chrono::nanoseconds(0), span_data->GetDuration()); } -TEST(Tracer, StartSpanWithOptions) +TEST(Tracer, StartSpanWithOptionsTime) { std::shared_ptr>> spans_received( new std::vector>); @@ -112,3 +113,29 @@ TEST(Tracer, StartSpanWithOptions) ASSERT_EQ(std::chrono::nanoseconds(300), span_data->GetStartTime().time_since_epoch()); ASSERT_EQ(std::chrono::nanoseconds(30), span_data->GetDuration()); } + +TEST(Tracer, StartSpanWithAttributes) +{ + std::shared_ptr>> spans_received( + new std::vector>); + auto tracer = initTracer(spans_received); + + { + tracer->StartSpan("span 1", {{"attr1", 314159}, {"attr2", false}, {"attr1", "string"}}); + + std::map m; + m["attr3"] = 3.0; + tracer->StartSpan("span 2", m); + } + + ASSERT_EQ(2, spans_received->size()); + + auto &span_data = spans_received->at(0); + ASSERT_EQ(2, span_data->GetAttributes().size()); + ASSERT_EQ("string", nostd::get(span_data->GetAttributes().at("attr1"))); + ASSERT_EQ(false, nostd::get(span_data->GetAttributes().at("attr2"))); + + auto &span_data2 = spans_received->at(1); + ASSERT_EQ(1, span_data2->GetAttributes().size()); + ASSERT_EQ(3.0, nostd::get(span_data2->GetAttributes().at("attr3"))); +}