diff --git a/sdk/core/azure-core-tracing-opentelemetry/test/ut/service_support_test.cpp b/sdk/core/azure-core-tracing-opentelemetry/test/ut/service_support_test.cpp index b183fcf3f..c28ecafa4 100644 --- a/sdk/core/azure-core-tracing-opentelemetry/test/ut/service_support_test.cpp +++ b/sdk/core/azure-core-tracing-opentelemetry/test/ut/service_support_test.cpp @@ -1,36 +1,22 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT -#define USE_MEMORY_EXPORTER 1 -#include "azure/core/internal/tracing/service_tracing.hpp" -#include "azure/core/tracing/opentelemetry/opentelemetry.hpp" +#include + +#include +#include + #include #include +#include #include +#include -#if defined(_MSC_VER) -// The OpenTelemetry headers generate a couple of warnings on MSVC in the OTel 1.2 package, suppress -// the warnings across the includes. -#pragma warning(push) -#pragma warning(disable : 4100) -#pragma warning(disable : 4244) -#pragma warning(disable : 6323) // Disable "Use of arithmetic operator on Boolean type" warning. -#endif -#include -#include -#include +#include "test_exporter.hpp" // Span Exporter used for OpenTelemetry tests. #include -#include #include #include #include -#include -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -#include -#include -#include using namespace Azure::Core::Http::Policies; using namespace Azure::Core::Http::Policies::_internal; @@ -95,18 +81,13 @@ class CustomLogHandler : public opentelemetry::sdk::common::internal_log::LogHan class OpenTelemetryServiceTests : public Azure::Core::Test::TestBase { private: protected: - std::shared_ptr m_spanData; + std::shared_ptr m_spanData; opentelemetry::nostd::shared_ptr CreateOpenTelemetryProvider() { -#if USE_MEMORY_EXPORTER - auto exporter = std::make_unique(); - m_spanData = exporter->GetData(); -#else - // logging exporter - auto exporter = std::make_unique(); -#endif + auto exporter = std::make_unique(); + m_spanData = exporter->GetTestData(); // simple processor auto simple_processor = std::unique_ptr( @@ -143,7 +124,7 @@ protected: } bool VerifySpan( - std::unique_ptr const& span, + std::unique_ptr const& span, std::string const& expectedSpanContentsJson) { Azure::Core::Json::_internal::json expectedSpanContents( @@ -199,7 +180,7 @@ protected: EXPECT_EQ(expectedAttributes.size(), attributes.size()); - for (const auto& foundAttribute : attributes) + for (auto const& foundAttribute : attributes) { EXPECT_TRUE(expectedAttributes.contains(foundAttribute.first)); switch (foundAttribute.second.index()) @@ -219,7 +200,7 @@ protected: std::regex expectedRegex(expectedVal); GTEST_LOG_(INFO) << "expected Regex: " << expectedVal << std::endl; GTEST_LOG_(INFO) << "actual val: " << actualVal << std::endl; - EXPECT_TRUE(std::regex_match(actualVal, expectedRegex)); + EXPECT_TRUE(std::regex_match(std::string(actualVal), expectedRegex)); break; } case opentelemetry::common::kTypeDouble: { @@ -327,7 +308,7 @@ TEST_F(OpenTelemetryServiceTests, CreateWithExplicitProvider) EXPECT_FALSE(contextAndSpan.Context.IsCancelled()); } // Now let's verify what was logged via OpenTelemetry. - auto spans = m_spanData->GetSpans(); + auto const& spans = m_spanData->ExtractSpans(); EXPECT_EQ(1ul, spans.size()); VerifySpan(spans[0], R"( @@ -367,7 +348,7 @@ TEST_F(OpenTelemetryServiceTests, CreateWithImplicitProvider) } // Now let's verify what was logged via OpenTelemetry. - auto spans = m_spanData->GetSpans(); + auto const& spans = m_spanData->ExtractSpans(); EXPECT_EQ(1ul, spans.size()); VerifySpan(spans[0], R"( @@ -415,7 +396,7 @@ TEST_F(OpenTelemetryServiceTests, CreateSpanWithOptions) } // Now let's verify what was logged via OpenTelemetry. - auto spans = m_spanData->GetSpans(); + auto const& spans = m_spanData->ExtractSpans(); EXPECT_EQ(1ul, spans.size()); VerifySpan(spans[0], R"( @@ -476,7 +457,7 @@ TEST_F(OpenTelemetryServiceTests, NestSpans) } } // Now let's verify what was logged via OpenTelemetry. - auto spans = m_spanData->GetSpans(); + auto const& spans = m_spanData->ExtractSpans(); EXPECT_EQ(2ul, spans.size()); // Because Nested API goes out of scope before My API, it will be logged first in the @@ -676,7 +657,7 @@ TEST_F(OpenTelemetryServiceTests, ServiceApiImplementation) myServiceClient.GetConfigurationString("Fred"); } // Now let's verify what was logged via OpenTelemetry. - auto spans = m_spanData->GetSpans(); + auto const& spans = m_spanData->ExtractSpans(); EXPECT_EQ(2ul, spans.size()); VerifySpan(spans[0], R"( @@ -722,7 +703,7 @@ TEST_F(OpenTelemetryServiceTests, ServiceApiImplementation) myServiceClient.GetConfigurationString("George"); } // Now let's verify what was logged via OpenTelemetry. - auto spans = m_spanData->GetSpans(); + auto const& spans = m_spanData->ExtractSpans(); EXPECT_EQ(0ul, spans.size()); } } diff --git a/sdk/core/azure-core-tracing-opentelemetry/test/ut/test_exporter.hpp b/sdk/core/azure-core-tracing-opentelemetry/test/ut/test_exporter.hpp new file mode 100644 index 000000000..b3f19084a --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/test/ut/test_exporter.hpp @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +class RecordedSpan : public opentelemetry::sdk::trace::Recordable { + struct Event + { + std::string Name; + std::chrono::system_clock::time_point Timestamp; + opentelemetry::sdk::common::AttributeMap Attributes; + }; + opentelemetry::trace::SpanId m_parentSpan; + opentelemetry::trace::SpanId m_spanId; + opentelemetry::sdk::common::AttributeMap m_attributes; + std::vector m_events; + opentelemetry::trace::StatusCode m_statusCode{}; + std::string m_statusDescription; + std::string m_name; + opentelemetry::trace::SpanKind m_spanKind{}; + std::chrono::system_clock::time_point m_startTime; + std::chrono::nanoseconds m_duration{}; + std::unique_ptr m_scope; + std::unique_ptr m_resource; + +public: + ~RecordedSpan() = default; + + /** + * Set the span context and parent span id + * @param span_context the span context to set + * @param parent_span_id the parent span id to set + */ + void SetIdentity( + const opentelemetry::trace::SpanContext& span_context, + opentelemetry::trace::SpanId parent_span_id) noexcept override + { + m_parentSpan = parent_span_id; + m_spanId = span_context.span_id(); + }; + + /** + * Set an attribute of a span. + * @param key the name of the attribute + * @param value the attribute value + */ + void SetAttribute( + opentelemetry::nostd::string_view key, + const opentelemetry::common::AttributeValue& value) noexcept override + { + m_attributes.SetAttribute(key, value); + }; + + /** + * Add an event to a span. + * @param name the name of the event + * @param timestamp the timestamp of the event + * @param attributes the attributes associated with the event + */ + void AddEvent( + opentelemetry::nostd::string_view name, + opentelemetry::common::SystemTimestamp timestamp, + const opentelemetry::common::KeyValueIterable& attributes) noexcept override + { + Event event; + event.Name = std::string(name); + event.Timestamp = timestamp; + + attributes.ForEachKeyValue( + [&event]( + opentelemetry::nostd::string_view name, opentelemetry::common::AttributeValue value) { + event.Attributes.SetAttribute(name, value); + return true; + }); + m_events.push_back(event); + }; + + /** + * Add a link to a span. + */ + void AddLink( + const opentelemetry::trace::SpanContext&, + const opentelemetry::common::KeyValueIterable&) noexcept override{ + // TODO, when we use this, we need to test this. + // NO-OP since this exporter silences link data. + }; + + /** + * Set the status of the span. + * @param code the status code + * @param description a description of the status + */ + void SetStatus( + opentelemetry::trace::StatusCode code, + opentelemetry::nostd::string_view description) noexcept override + { + m_statusCode = code; + m_statusDescription = std::string(description); + }; + + /** + * Set the name of the span. + * @param name the name to set + */ + void SetName(opentelemetry::nostd::string_view name) noexcept override + { + m_name = std::string(name); + }; + + /** + * Set the spankind of the span. + * @param span_kind the spankind to set + */ + void SetSpanKind(opentelemetry::trace::SpanKind span_kind) noexcept override + { + m_spanKind = span_kind; + }; + + /** + * Set Resource of the span + * @param resource the resource to set + */ + void SetResource(const opentelemetry::sdk::resource::Resource& resource) noexcept override + { + m_resource = std::make_unique(resource); + }; + + /** + * Set the start time of the span. + * @param start_time the start time to set + */ + void SetStartTime(opentelemetry::common::SystemTimestamp start_time) noexcept override + { + m_startTime = start_time; + }; + + /** + * Set the duration of the span. + * @param duration the duration to set + */ + void SetDuration(std::chrono::nanoseconds duration) noexcept override { m_duration = duration; } + + /** + * Set the instrumentation scope of the span. + * @param instrumentation_scope the instrumentation scope to set + */ + void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope& + instrumentation_scope) noexcept override + { + m_scope = std::make_unique( + instrumentation_scope); + }; + + std::string GetName() { return m_name; } + opentelemetry::trace::StatusCode GetStatus() { return m_statusCode; } + opentelemetry::trace::SpanId GetParentSpanId() { return m_parentSpan; } + opentelemetry::trace::SpanKind GetSpanKind() { return m_spanKind; } + opentelemetry::trace::SpanId GetSpanId() { return m_spanId; } + opentelemetry::sdk::common::AttributeMap const& GetAttributes() { return m_attributes; } + opentelemetry::sdk::instrumentationscope::InstrumentationScope& GetInstrumentationScope() + { + return *m_scope; + } +}; + +class TestExporter final : public opentelemetry::sdk::trace::SpanExporter { + +public: + class TestData { + std::vector> m_spans; + + public: + // Returns a copy of the recorded spans and clears the set of recorded spans. + std::vector> const ExtractSpans() { return std::move(m_spans); } + void AddSpan(std::unique_ptr&& span) { m_spans.push_back(std::move(span)); } + }; + std::shared_ptr const& GetTestData() { return m_testData; } + + TestExporter() : m_testData{std::make_shared()} {} + virtual ~TestExporter() = default; + + /** + * Create a span recordable. This object will be used to record span data and + * will subsequently be passed to SpanExporter::Export. Vendors can implement + * custom recordables or use the default SpanData recordable provided by the + * SDK. + * @return a newly initialized Recordable object + * + * Note: This method must be callable from multiple threads. + */ + std::unique_ptr MakeRecordable() noexcept override + { + return std::unique_ptr(new (std::nothrow) RecordedSpan); + } + + /** + * Exports a batch of span recordables. This method must not be called + * concurrently for the same exporter instance. + * @param spans a span of unique pointers to span recordables + */ + opentelemetry::sdk::common::ExportResult Export( + const opentelemetry::nostd::span>& + spans) noexcept override + { + for (auto& recordable : spans) + { + auto span = std::unique_ptr(static_cast(recordable.release())); + m_testData->AddSpan(std::move(span)); + } + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + + /** + * Shut down the exporter. + * @return return the status of the operation. + */ + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::shared_ptr m_testData; +};