From 1026ec3ffcf794d91af0459ce1c30faef154eba2 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Fri, 18 Feb 2022 18:56:06 -0800 Subject: [PATCH 1/4] Run apt update before install thrift dependencies (#1225) --- ci/setup_thrift.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/setup_thrift.sh b/ci/setup_thrift.sh index 8550fb52d1..af8cc3ffc8 100755 --- a/ci/setup_thrift.sh +++ b/ci/setup_thrift.sh @@ -4,6 +4,8 @@ set -e export DEBIAN_FRONTEND=noninteractive export THRIFT_VERSION=0.14.1 +apt update + if ! type cmake > /dev/null; then #cmake not installed, exiting exit 1 From 3508d7c11c8bce76e7de1d252b97a239516be2a7 Mon Sep 17 00:00:00 2001 From: Ehsan Saei <71217171+esigo@users.noreply.github.com> Date: Sat, 19 Feb 2022 08:44:56 +0100 Subject: [PATCH 2/4] allow extension of the lifetime of ContextStorage. (#1214) --- api/include/opentelemetry/context/runtime_context.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/include/opentelemetry/context/runtime_context.h b/api/include/opentelemetry/context/runtime_context.h index 5cb793b23a..fbcf425c2c 100644 --- a/api/include/opentelemetry/context/runtime_context.h +++ b/api/include/opentelemetry/context/runtime_context.h @@ -150,6 +150,18 @@ class RuntimeContext GetStorage() = storage; } + /** + * Provide a pointer to const runtime context storage. + * + * The returned pointer can only be used for extending the lifetime of the runtime context + * storage. + * + */ + static nostd::shared_ptr GetConstRuntimeContextStorage() noexcept + { + return GetRuntimeContextStorage(); + } + private: static nostd::shared_ptr GetRuntimeContextStorage() noexcept { From 9502339a588a11642b14d795e316bb7095c63adc Mon Sep 17 00:00:00 2001 From: Ehsan Saei <71217171+esigo@users.noreply.github.com> Date: Sat, 19 Feb 2022 09:23:57 +0100 Subject: [PATCH 3/4] Ostream metric exporter (#1196) --- exporters/ostream/BUILD | 33 ++- .../exporters/ostream/metric_exporter.h | 67 ++++++ exporters/ostream/src/metric_exporter.cc | 150 +++++++++++++ exporters/ostream/test/ostream_metric_test.cc | 210 ++++++++++++++++++ .../sdk/metrics/metric_exporter.h | 13 +- .../opentelemetry/sdk/metrics/recordable.h | 5 +- sdk/test/metrics/meter_provider_sdk_test.cc | 7 +- 7 files changed, 476 insertions(+), 9 deletions(-) create mode 100644 exporters/ostream/include/opentelemetry/exporters/ostream/metric_exporter.h create mode 100644 exporters/ostream/src/metric_exporter.cc create mode 100644 exporters/ostream/test/ostream_metric_test.cc diff --git a/exporters/ostream/BUILD b/exporters/ostream/BUILD index 4c2a9d0c02..cca74d6693 100644 --- a/exporters/ostream/BUILD +++ b/exporters/ostream/BUILD @@ -43,8 +43,39 @@ cc_library( ], ) +cc_library( + name = "ostream_metric_exporter", + srcs = [ + "src/metric_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/ostream/metric_exporter.h", + ], + strip_include_prefix = "include", + tags = [ + "metrics", + "ostream", + ], + deps = [ + "//sdk/src/metrics", + ], +) + +cc_test( + name = "ostream_metric_test", + srcs = ["test/ostream_metric_test.cc"], + tags = [ + "ostream", + "test", + ], + deps = [ + ":ostream_metric_exporter", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( - name = "ostream_metrics_test", + name = "ostream_metrics_test_deprecated", srcs = ["test/ostream_metrics_test.cc"], tags = [ "ostream", diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/metric_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/metric_exporter.h new file mode 100644 index 0000000000..5658e3bff7 --- /dev/null +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/metric_exporter.h @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifndef ENABLE_METRICS_PREVIEW + +# include +# include +# include "opentelemetry/common/spin_lock_mutex.h" +# include "opentelemetry/nostd/span.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" +# include "opentelemetry/sdk/metrics/recordable.h" +# include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ + +/** + * The OStreamMetricExporter exports record data through an ostream + */ +class OStreamMetricExporter final : public opentelemetry::sdk::metrics::MetricExporter +{ +public: + /** + * Create an OStreamMetricExporter. This constructor takes in a reference to an ostream that the + * export() function will send span data into. + * The default ostream is set to stdout + */ + explicit OStreamMetricExporter(std::ostream &sout = std::cout) noexcept; + + /** + * Export + * @param records a span of unique pointers to metrics data + */ + sdk::common::ExportResult Export( + const nostd::span> &records) noexcept + override; + + /** + * Force flush the exporter. + */ + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + /** + * Shut down the exporter. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. + * @return return the status of this operation + */ + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + +private: + std::ostream &sout_; + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; + void printPointData(opentelemetry::sdk::metrics::PointType &point_data); +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/exporters/ostream/src/metric_exporter.cc b/exporters/ostream/src/metric_exporter.cc new file mode 100644 index 0000000000..f8d26a7fa5 --- /dev/null +++ b/exporters/ostream/src/metric_exporter.cc @@ -0,0 +1,150 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/exporters/ostream/metric_exporter.h" +# include +# include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" +# include "opentelemetry/sdk_config.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ + +OStreamMetricExporter::OStreamMetricExporter(std::ostream &sout) noexcept : sout_(sout) {} + +sdk::common::ExportResult OStreamMetricExporter::Export( + const nostd::span> &records) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[OStream Metric] Exporting " + << records.size() << " records(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + + for (auto &record : records) + { + sout_ << "{" + << "\n name : " << record->instrumentation_library_->GetName() + << "\n version : " << record->instrumentation_library_->GetVersion(); + printPointData(record->point_data_); + sout_ << "\n}\n"; + } + return sdk::common::ExportResult::kSuccess; +} + +template +inline void printVec(std::ostream &os, std::vector &vec) +{ + os << '['; + if (vec.size() > 1) + { + std::copy(vec.begin(), vec.end() - 1, std::ostream_iterator(os, ", ")); + } + if (!vec.empty()) + { + os << vec.back(); + } + os << ']'; +} + +void OStreamMetricExporter::printPointData(opentelemetry::sdk::metrics::PointType &point_data) +{ + if (nostd::holds_alternative(point_data)) + { + auto sum_point_data = nostd::get(point_data); + sout_ << "\n type : SumPointData"; + sout_ << "\n start timestamp : " + << std::to_string(sum_point_data.start_epoch_nanos_.time_since_epoch().count()); + sout_ << "\n end timestamp : " + << std::to_string(sum_point_data.end_epoch_nanos_.time_since_epoch().count()); + sout_ << "\n value : "; + if (nostd::holds_alternative(sum_point_data.value_)) + { + sout_ << nostd::get(sum_point_data.value_); + } + else if (nostd::holds_alternative(sum_point_data.value_)) + { + sout_ << nostd::get(sum_point_data.value_); + } + } + else if (nostd::holds_alternative(point_data)) + { + auto histogram_point_data = nostd::get(point_data); + sout_ << "\n type : HistogramPointData"; + sout_ << "\n timestamp : " + << std::to_string(histogram_point_data.epoch_nanos_.time_since_epoch().count()); + sout_ << "\n count : " << histogram_point_data.count_; + sout_ << "\n sum : "; + if (nostd::holds_alternative(histogram_point_data.sum_)) + { + sout_ << nostd::get(histogram_point_data.sum_); + } + else if (nostd::holds_alternative(histogram_point_data.sum_)) + { + sout_ << nostd::get(histogram_point_data.sum_); + } + + sout_ << "\n buckets : "; + if (nostd::holds_alternative>(histogram_point_data.boundaries_)) + { + auto &double_boundaries = nostd::get>(histogram_point_data.boundaries_); + printVec(sout_, double_boundaries); + } + else if (nostd::holds_alternative>(histogram_point_data.boundaries_)) + { + auto &long_boundaries = nostd::get>(histogram_point_data.boundaries_); + printVec(sout_, long_boundaries); + } + + sout_ << "\n counts : "; + printVec(sout_, histogram_point_data.counts_); + } + else if (nostd::holds_alternative(point_data)) + { + auto last_point_data = nostd::get(point_data); + sout_ << "\n type : LastValuePointData"; + sout_ << "\n timestamp : " + << std::to_string(last_point_data.epoch_nanos_.time_since_epoch().count()) + << std::boolalpha << "\n valid : " << last_point_data.is_lastvalue_valid_; + sout_ << "\n value : "; + if (nostd::holds_alternative(last_point_data.value_)) + { + sout_ << nostd::get(last_point_data.value_); + } + else if (nostd::holds_alternative(last_point_data.value_)) + { + sout_ << nostd::get(last_point_data.value_); + } + } + else if (nostd::holds_alternative(point_data)) + {} +} + +bool OStreamMetricExporter::ForceFlush(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard locked(lock_); + return true; +} + +bool OStreamMetricExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard locked(lock_); + is_shutdown_ = true; + return true; +} + +bool OStreamMetricExporter::isShutdown() const noexcept +{ + const std::lock_guard locked(lock_); + return is_shutdown_; +} + +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/exporters/ostream/test/ostream_metric_test.cc b/exporters/ostream/test/ostream_metric_test.cc new file mode 100644 index 0000000000..51b9882c47 --- /dev/null +++ b/exporters/ostream/test/ostream_metric_test.cc @@ -0,0 +1,210 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/exporters/ostream/metric_exporter.h" +# include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" + +# include + +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace nostd = opentelemetry::nostd; +namespace exportermetrics = opentelemetry::exporter::metrics; + +TEST(OStreamMetricsExporter, Shutdown) +{ + auto exporter = + std::unique_ptr(new exportermetrics::OStreamMetricExporter); + ASSERT_TRUE(exporter->Shutdown()); + auto result = exporter->Export(nostd::span>{}); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); +} + +TEST(OStreamMetricsExporter, ExportSumPointData) +{ + auto exporter = + std::unique_ptr(new exportermetrics::OStreamMetricExporter); + + std::unique_ptr record(new metric_sdk::MetricData); + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + record->instrumentation_library_ = instrumentation_library.get(); + record->point_data_ = metric_sdk::SumPointData{ + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, 10.0, + metric_sdk::AggregationTemporarily::kUnspecified, false}; + auto record2 = std::unique_ptr(new metric_sdk::MetricData(*record)); + record2->point_data_ = metric_sdk::SumPointData{ + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, 20l, + metric_sdk::AggregationTemporarily::kUnspecified, false}; + std::vector> records; + records.push_back(std::move(record)); + records.push_back(std::move(record2)); + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(records); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : library_name\n" + " version : 1.2.0\n" + " type : SumPointData\n" + " start timestamp : 0\n" + " end timestamp : 0\n" + " value : 10\n" + "}\n" + "{\n" + " name : library_name\n" + " version : 1.2.0\n" + " type : SumPointData\n" + " start timestamp : 0\n" + " end timestamp : 0\n" + " value : 20\n" + "}\n"; + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, ExportHistogramPointData) +{ + auto exporter = + std::unique_ptr(new exportermetrics::OStreamMetricExporter); + + std::unique_ptr record(new metric_sdk::MetricData); + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + record->instrumentation_library_ = instrumentation_library.get(); + record->point_data_ = metric_sdk::HistogramPointData{opentelemetry::common::SystemTimestamp{}, + std::vector{10.1, 20.2, 30.2}, + 900.5, + {200, 300, 400, 500}, + 3}; + auto record2 = std::unique_ptr(new metric_sdk::MetricData(*record)); + record2->point_data_ = metric_sdk::HistogramPointData{opentelemetry::common::SystemTimestamp{}, + std::vector{10, 20, 30}, + 900l, + {200, 300, 400, 500}, + 3}; + std::vector> records; + records.push_back(std::move(record)); + records.push_back(std::move(record2)); + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(records); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : library_name\n" + " version : 1.2.0\n" + " type : HistogramPointData\n" + " timestamp : 0\n" + " count : 3\n" + " sum : 900.5\n" + " buckets : [10.1, 20.2, 30.2]\n" + " counts : [200, 300, 400, 500]\n" + "}\n" + "{\n" + " name : library_name\n" + " version : 1.2.0\n" + " type : HistogramPointData\n" + " timestamp : 0\n" + " count : 3\n" + " sum : 900\n" + " buckets : [10, 20, 30]\n" + " counts : [200, 300, 400, 500]\n" + "}\n"; + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, ExportLastValuePointData) +{ + auto exporter = + std::unique_ptr(new exportermetrics::OStreamMetricExporter); + + std::unique_ptr record(new metric_sdk::MetricData); + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + record->instrumentation_library_ = instrumentation_library.get(); + record->point_data_ = + metric_sdk::LastValuePointData{opentelemetry::common::SystemTimestamp{}, true, 10.0}; + auto record2 = std::unique_ptr(new metric_sdk::MetricData(*record)); + record2->point_data_ = + metric_sdk::LastValuePointData{opentelemetry::common::SystemTimestamp{}, true, 20l}; + std::vector> records; + records.push_back(std::move(record)); + records.push_back(std::move(record2)); + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(records); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : library_name\n" + " version : 1.2.0\n" + " type : LastValuePointData\n" + " timestamp : 0\n" + " valid : true\n" + " value : 10\n" + "}\n" + "{\n" + " name : library_name\n" + " version : 1.2.0\n" + " type : LastValuePointData\n" + " timestamp : 0\n" + " valid : true\n" + " value : 20\n" + "}\n"; + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, ExportDropPointData) +{ + auto exporter = + std::unique_ptr(new exportermetrics::OStreamMetricExporter); + + std::unique_ptr record(new metric_sdk::MetricData); + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + record->instrumentation_library_ = instrumentation_library.get(); + record->point_data_ = metric_sdk::DropPointData{}; + std::vector> records; + records.push_back(std::move(record)); + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(records); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : library_name\n" + " version : 1.2.0\n" + "}\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +#endif diff --git a/sdk/include/opentelemetry/sdk/metrics/metric_exporter.h b/sdk/include/opentelemetry/sdk/metrics/metric_exporter.h index a31879c63c..039d3819ec 100644 --- a/sdk/include/opentelemetry/sdk/metrics/metric_exporter.h +++ b/sdk/include/opentelemetry/sdk/metrics/metric_exporter.h @@ -4,8 +4,10 @@ #pragma once #ifndef ENABLE_METRICS_PREVIEW # include +# include +# include "opentelemetry/nostd/span.h" # include "opentelemetry/sdk/common/exporter_utils.h" -# include "opentelemetry/sdk/metrics/recordable.h" +# include "opentelemetry/sdk/metrics/data/metric_data.h" # include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -26,11 +28,11 @@ class MetricExporter /** * Exports a batch of metrics recordables. This method must not be called * concurrently for the same exporter instance. - * @param spans a span of unique pointers to metrics recordables + * @param spans a span of unique pointers to metrics data */ virtual opentelemetry::sdk::common::ExportResult Export( - const nostd::span> - &spans) noexcept = 0; + const nostd::span> + &records) noexcept = 0; /** * Force flush the exporter. @@ -43,7 +45,8 @@ class MetricExporter * @param timeout an optional timeout. * @return return the status of the operation. */ - virtual bool Shutdown() noexcept = 0; + virtual bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept = 0; private: AggregationTemporarily aggregation_temporarily; diff --git a/sdk/include/opentelemetry/sdk/metrics/recordable.h b/sdk/include/opentelemetry/sdk/metrics/recordable.h index 7daee592b1..d7e7f5756e 100644 --- a/sdk/include/opentelemetry/sdk/metrics/recordable.h +++ b/sdk/include/opentelemetry/sdk/metrics/recordable.h @@ -3,6 +3,7 @@ #pragma once #ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/nostd/string_view.h" # include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -14,8 +15,10 @@ class Recordable { public: virtual ~Recordable() = default; - + nostd::string_view GetName() { return name_; } // TBD +private: + nostd::string_view name_; }; } // namespace metrics } // namespace sdk diff --git a/sdk/test/metrics/meter_provider_sdk_test.cc b/sdk/test/metrics/meter_provider_sdk_test.cc index b8d4db5670..e321a29a96 100644 --- a/sdk/test/metrics/meter_provider_sdk_test.cc +++ b/sdk/test/metrics/meter_provider_sdk_test.cc @@ -16,7 +16,7 @@ class MockMetricExporter : public MetricExporter public: MockMetricExporter() = default; opentelemetry::sdk::common::ExportResult Export( - const opentelemetry::nostd::span> &spans) noexcept override + const opentelemetry::nostd::span> &records) noexcept override { return opentelemetry::sdk::common::ExportResult::kSuccess; } @@ -27,7 +27,10 @@ class MockMetricExporter : public MetricExporter return true; } - bool Shutdown() noexcept override { return true; } + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override + { + return true; + } }; class MockMetricReader : public MetricReader From 9157fd474883b4d90dab2e4f5aa887a705c2ab94 Mon Sep 17 00:00:00 2001 From: Ehsan Saei <71217171+esigo@users.noreply.github.com> Date: Sat, 19 Feb 2022 11:12:02 +0100 Subject: [PATCH 4/4] Mock for http exporters (#1185) --- CMakeLists.txt | 2 + ci/do_ci.ps1 | 2 +- ci/do_ci.sh | 2 +- exporters/otlp/BUILD | 2 + exporters/otlp/CMakeLists.txt | 12 +- .../exporters/otlp/otlp_http_client.h | 11 + .../exporters/otlp/otlp_http_exporter.h | 13 +- .../exporters/otlp/otlp_http_log_exporter.h | 13 +- exporters/otlp/src/otlp_http_client.cc | 10 +- exporters/otlp/src/otlp_http_exporter.cc | 21 +- exporters/otlp/src/otlp_http_log_exporter.cc | 21 +- .../otlp/test/otlp_http_exporter_test.cc | 307 +++++-------- .../otlp/test/otlp_http_log_exporter_test.cc | 420 +++++++----------- exporters/zipkin/test/zipkin_exporter_test.cc | 195 +++----- .../ext/http/client/curl/http_client_curl.h | 3 + .../ext/http/client/http_client_factory.h | 4 +- .../http/client/nosend/http_client_nosend.h | 184 ++++++++ ext/src/CMakeLists.txt | 4 + .../client/curl/http_client_factory_curl.cc | 2 +- ext/src/http/client/nosend/BUILD | 19 + ext/src/http/client/nosend/CMakeLists.txt | 36 ++ .../nosend/http_client_factory_nosend.cc | 13 + .../http/client/nosend/http_client_nosend.cc | 71 +++ ext/test/http/curl_http_test.cc | 2 +- 24 files changed, 732 insertions(+), 637 deletions(-) mode change 100755 => 100644 exporters/otlp/test/otlp_http_exporter_test.cc mode change 100755 => 100644 exporters/otlp/test/otlp_http_log_exporter_test.cc create mode 100644 ext/include/opentelemetry/ext/http/client/nosend/http_client_nosend.h create mode 100644 ext/src/http/client/nosend/BUILD create mode 100644 ext/src/http/client/nosend/CMakeLists.txt create mode 100644 ext/src/http/client/nosend/http_client_factory_nosend.cc create mode 100644 ext/src/http/client/nosend/http_client_nosend.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index e49dcf6302..d631e09236 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -390,6 +390,7 @@ list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}") include(CTest) if(BUILD_TESTING) + add_definitions(-DENABLE_TEST) if(EXISTS ${CMAKE_BINARY_DIR}/lib/libgtest.a) # Prefer GTest from build tree. GTest is not always working with # CMAKE_PREFIX_PATH @@ -426,6 +427,7 @@ include_directories(api/include) add_subdirectory(api) if(NOT WITH_API_ONLY) + set(BUILD_TESTING ${BUILD_TESTING}) include_directories(sdk/include) include_directories(sdk) include_directories(ext/include) diff --git a/ci/do_ci.ps1 b/ci/do_ci.ps1 index da340c9624..c97ca13381 100644 --- a/ci/do_ci.ps1 +++ b/ci/do_ci.ps1 @@ -22,7 +22,7 @@ $VCPKG_DIR="$SRC_DIR\vcpkg" switch ($action) { "bazel.build" { - bazel build $BAZEL_OPTIONS -- //... + bazel build --copt=-DENABLE_TEST $BAZEL_OPTIONS -- //... $exit = $LASTEXITCODE if ($exit -ne 0) { exit $exit diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 993a57a5cc..db00524585 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -59,7 +59,7 @@ mkdir -p "${BUILD_DIR}" [ -z "${PLUGIN_DIR}" ] && export PLUGIN_DIR=$HOME/plugin mkdir -p "${PLUGIN_DIR}" -BAZEL_OPTIONS="--copt=-DENABLE_METRICS_PREVIEW --copt=-DENABLE_LOGS_PREVIEW" +BAZEL_OPTIONS="--copt=-DENABLE_METRICS_PREVIEW --copt=-DENABLE_LOGS_PREVIEW --copt=-DENABLE_TEST" BAZEL_TEST_OPTIONS="$BAZEL_OPTIONS --test_output=errors" # https://github.com/bazelbuild/bazel/issues/4341 diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 1a39a82baf..4968191385 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -234,6 +234,7 @@ cc_test( deps = [ ":otlp_http_exporter", "//api", + "//ext/src/http/client/nosend:http_client_nosend", "@com_google_googletest//:gtest_main", ], ) @@ -249,6 +250,7 @@ cc_test( deps = [ ":otlp_http_log_exporter", "//api", + "//ext/src/http/client/nosend:http_client_nosend", "@com_google_googletest//:gtest_main", ], ) diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 705fc97042..0332267922 100755 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -170,7 +170,7 @@ if(BUILD_TESTING) add_executable(otlp_http_exporter_test test/otlp_http_exporter_test.cc) target_link_libraries( otlp_http_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - ${GMOCK_LIB} opentelemetry_exporter_otlp_http) + ${GMOCK_LIB} opentelemetry_exporter_otlp_http http_client_nosend) gtest_add_tests( TARGET otlp_http_exporter_test TEST_PREFIX exporter.otlp. @@ -180,9 +180,13 @@ if(BUILD_TESTING) add_executable(otlp_http_log_exporter_test test/otlp_http_log_exporter_test.cc) target_link_libraries( - otlp_http_log_exporter_test ${GTEST_BOTH_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} ${GMOCK_LIB} - opentelemetry_exporter_otlp_http_log opentelemetry_logs) + otlp_http_log_exporter_test + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${GMOCK_LIB} + opentelemetry_exporter_otlp_http_log + opentelemetry_logs + http_client_nosend) gtest_add_tests( TARGET otlp_http_log_exporter_test TEST_PREFIX exporter.otlp. diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h index 35939204b3..1a199bed48 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h @@ -127,6 +127,17 @@ class OtlpHttpClient std::string http_uri_; mutable opentelemetry::common::SpinLockMutex lock_; bool isShutdown() const noexcept; + // For testing + friend class OtlpHttpExporterTestPeer; + friend class OtlpHttpLogExporterTestPeer; + /** + * Create an OtlpHttpClient using the specified http client. + * Only tests can call this constructor directly. + * @param options the Otlp http client options to be used for exporting + * @param http_client the http client to be used for exporting + */ + OtlpHttpClient(OtlpHttpClientOptions &&options, + std::shared_ptr http_client); }; } // namespace otlp } // namespace exporter diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h index 852745ac39..3e6a521194 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h @@ -91,14 +91,19 @@ class OtlpHttpExporter final : public opentelemetry::sdk::trace::SpanExporter bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; private: - // For testing - friend class OtlpHttpExporterTestPeer; - // The configuration options associated with this exporter. const OtlpHttpExporterOptions options_; // Object that stores the HTTP sessions that have been created - OtlpHttpClient http_client_; + std::unique_ptr http_client_; + // For testing + friend class OtlpHttpExporterTestPeer; + /** + * Create an OtlpHttpExporter using the specified http client. + * Only tests can call this constructor directly. + * @param http_client the http client to be used for exporting + */ + OtlpHttpExporter(std::unique_ptr http_client); }; } // namespace otlp } // namespace exporter diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h index 738a60d8f6..d330e62be4 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h @@ -90,14 +90,19 @@ class OtlpHttpLogExporter final : public opentelemetry::sdk::logs::LogExporter bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; private: - // For testing - friend class OtlpHttpLogExporterTestPeer; - // Configuration options for the exporter const OtlpHttpLogExporterOptions options_; // Object that stores the HTTP sessions that have been created - OtlpHttpClient http_client_; + std::unique_ptr http_client_; + // For testing + friend class OtlpHttpLogExporterTestPeer; + /** + * Create an OtlpHttpLogExporter using the specified http client. + * Only tests can call this constructor directly. + * @param http_client the http client to be used for exporting + */ + OtlpHttpLogExporter(std::unique_ptr http_client); }; } // namespace otlp } // namespace exporter diff --git a/exporters/otlp/src/otlp_http_client.cc b/exporters/otlp/src/otlp_http_client.cc index fd86b1e136..544f74ca7c 100644 --- a/exporters/otlp/src/otlp_http_client.cc +++ b/exporters/otlp/src/otlp_http_client.cc @@ -12,14 +12,13 @@ #include "opentelemetry/ext/http/client/http_client_factory.h" #include "opentelemetry/ext/http/common/url_parser.h" -#include "nlohmann/json.hpp" - #include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" #include #include "google/protobuf/message.h" #include "google/protobuf/reflection.h" #include "google/protobuf/stubs/common.h" +#include "nlohmann/json.hpp" #if defined(GOOGLE_PROTOBUF_VERSION) && GOOGLE_PROTOBUF_VERSION >= 3007000 # include "google/protobuf/stubs/strutil.h" @@ -362,7 +361,7 @@ static void ConvertGenericMessageToJson(nlohmann::json &value, } } -static bool SerializeToHttpBody(http_client::Body &output, const google::protobuf::Message &message) +bool SerializeToHttpBody(http_client::Body &output, const google::protobuf::Message &message) { auto body_size = message.ByteSizeLong(); if (body_size > 0) @@ -566,6 +565,11 @@ OtlpHttpClient::OtlpHttpClient(OtlpHttpClientOptions &&options) : options_(options), http_client_(http_client::HttpClientFactory::Create()) {} +OtlpHttpClient::OtlpHttpClient(OtlpHttpClientOptions &&options, + std::shared_ptr http_client) + : options_(options), http_client_(http_client) +{} + // ----------------------------- HTTP Client methods ------------------------------ opentelemetry::sdk::common::ExportResult OtlpHttpClient::Export( const google::protobuf::Message &message) noexcept diff --git a/exporters/otlp/src/otlp_http_exporter.cc b/exporters/otlp/src/otlp_http_exporter.cc index 8b017f25f1..ca705d2e30 100644 --- a/exporters/otlp/src/otlp_http_exporter.cc +++ b/exporters/otlp/src/otlp_http_exporter.cc @@ -23,15 +23,18 @@ OtlpHttpExporter::OtlpHttpExporter() : OtlpHttpExporter(OtlpHttpExporterOptions( OtlpHttpExporter::OtlpHttpExporter(const OtlpHttpExporterOptions &options) : options_(options), - http_client_(OtlpHttpClientOptions(options.url, - options.content_type, - options.json_bytes_mapping, - options.use_json_name, - options.console_debug, - options.timeout, - options.http_headers)) + http_client_(new OtlpHttpClient(OtlpHttpClientOptions(options.url, + options.content_type, + options.json_bytes_mapping, + options.use_json_name, + options.console_debug, + options.timeout, + options.http_headers))) {} +OtlpHttpExporter::OtlpHttpExporter(std::unique_ptr http_client) + : options_(OtlpHttpExporterOptions()), http_client_(std::move(http_client)) +{} // ----------------------------- Exporter methods ------------------------------ std::unique_ptr OtlpHttpExporter::MakeRecordable() noexcept @@ -45,12 +48,12 @@ opentelemetry::sdk::common::ExportResult OtlpHttpExporter::Export( { proto::collector::trace::v1::ExportTraceServiceRequest service_request; OtlpRecordableUtils::PopulateRequest(spans, &service_request); - return http_client_.Export(service_request); + return http_client_->Export(service_request); } bool OtlpHttpExporter::Shutdown(std::chrono::microseconds timeout) noexcept { - return http_client_.Shutdown(timeout); + return http_client_->Shutdown(timeout); } } // namespace otlp diff --git a/exporters/otlp/src/otlp_http_log_exporter.cc b/exporters/otlp/src/otlp_http_log_exporter.cc index f4cc6a9f9c..3b64ee4eda 100644 --- a/exporters/otlp/src/otlp_http_log_exporter.cc +++ b/exporters/otlp/src/otlp_http_log_exporter.cc @@ -25,15 +25,18 @@ OtlpHttpLogExporter::OtlpHttpLogExporter() : OtlpHttpLogExporter(OtlpHttpLogExpo OtlpHttpLogExporter::OtlpHttpLogExporter(const OtlpHttpLogExporterOptions &options) : options_(options), - http_client_(OtlpHttpClientOptions(options.url, - options.content_type, - options.json_bytes_mapping, - options.use_json_name, - options.console_debug, - options.timeout, - options.http_headers)) + http_client_(new OtlpHttpClient(OtlpHttpClientOptions(options.url, + options.content_type, + options.json_bytes_mapping, + options.use_json_name, + options.console_debug, + options.timeout, + options.http_headers))) {} +OtlpHttpLogExporter::OtlpHttpLogExporter(std::unique_ptr http_client) + : options_(OtlpHttpLogExporterOptions()), http_client_(std::move(http_client)) +{} // ----------------------------- Exporter methods ------------------------------ std::unique_ptr OtlpHttpLogExporter::MakeRecordable() noexcept @@ -47,12 +50,12 @@ opentelemetry::sdk::common::ExportResult OtlpHttpLogExporter::Export( { proto::collector::logs::v1::ExportLogsServiceRequest service_request; OtlpRecordableUtils::PopulateRequest(logs, &service_request); - return http_client_.Export(service_request); + return http_client_->Export(service_request); } bool OtlpHttpLogExporter::Shutdown(std::chrono::microseconds timeout) noexcept { - return http_client_.Shutdown(timeout); + return http_client_->Shutdown(timeout); } } // namespace otlp diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc old mode 100755 new mode 100644 index 446b9aec29..ef0b5a509e --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -11,12 +11,15 @@ # include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" +# include "opentelemetry/ext/http/client/http_client_factory.h" +# include "opentelemetry/ext/http/client/nosend/http_client_nosend.h" # include "opentelemetry/ext/http/server/http_server.h" # include "opentelemetry/sdk/trace/batch_span_processor.h" # include "opentelemetry/sdk/trace/tracer_provider.h" # include "opentelemetry/trace/provider.h" # include +# include "gmock/gmock.h" # include "nlohmann/json.hpp" @@ -42,144 +45,28 @@ static nostd::span MakeSpan(T (&array)[N]) return nostd::span(array); } -class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS::HttpRequestCallback +OtlpHttpClientOptions MakeOtlpHttpClientOptions(HttpRequestContentType content_type) { -protected: - HTTP_SERVER_NS::HttpServer server_; - std::string server_address_; - std::atomic is_setup_; - std::atomic is_running_; - std::mutex mtx_requests; - std::condition_variable cv_got_events; - std::vector received_requests_json_; - std::vector - received_requests_binary_; - std::map received_requests_headers_; - -public: - OtlpHttpExporterTestPeer() : is_setup_(false), is_running_(false){}; - - virtual void SetUp() override - { - if (is_setup_.exchange(true)) - { - return; - } - int port = server_.addListeningPort(14371); - std::ostringstream os; - os << "localhost:" << port; - server_address_ = "http://" + os.str() + "/v1/traces"; - server_.setServerName(os.str()); - server_.setKeepalive(false); - server_.addHandler("/v1/traces", *this); - server_.start(); - is_running_ = true; - } - - virtual void TearDown() override - { - if (!is_setup_.exchange(false)) - return; - server_.stop(); - is_running_ = false; - } - - virtual int onHttpRequest(HTTP_SERVER_NS::HttpRequest const &request, - HTTP_SERVER_NS::HttpResponse &response) override - { - const std::string *request_content_type = nullptr; - { - auto it = request.headers.find("Content-Type"); - if (it != request.headers.end()) - { - request_content_type = &it->second; - } - } - received_requests_headers_ = request.headers; - - int response_status = 0; - - if (request.uri == "/v1/traces") - { - response.headers["Content-Type"] = "application/json"; - std::unique_lock lk(mtx_requests); - if (nullptr != request_content_type && *request_content_type == kHttpBinaryContentType) - { - opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request_body; - if (request_body.ParseFromArray(&request.content[0], - static_cast(request.content.size()))) - { - received_requests_binary_.push_back(request_body); - response.body = "{\"code\": 0, \"message\": \"success\"}"; - } - else - { - response.body = "{\"code\": 400, \"message\": \"Parse binary failed\"}"; - response_status = 400; - } - } - else if (nullptr != request_content_type && *request_content_type == kHttpJsonContentType) - { - auto json = nlohmann::json::parse(request.content, nullptr, false); - response.headers["Content-Type"] = "application/json"; - if (json.is_discarded()) - { - response.body = "{\"code\": 400, \"message\": \"Parse json failed\"}"; - response_status = 400; - } - else - { - received_requests_json_.push_back(json); - response.body = "{\"code\": 0, \"message\": \"success\"}"; - } - } - else - { - response.body = "{\"code\": 400, \"message\": \"Unsupported content type\"}"; - response_status = 400; - } - - response_status = 200; - } - else - { - std::unique_lock lk(mtx_requests); - response.headers["Content-Type"] = "text/plain"; - response.body = "404 Not Found"; - response_status = 200; - } - - cv_got_events.notify_one(); - - return response_status; - } + OtlpHttpExporterOptions options; + options.content_type = content_type; + options.console_debug = true; + options.timeout = std::chrono::system_clock::duration::zero(); + options.http_headers.insert( + std::make_pair("Custom-Header-Key", "Custom-Header-Value")); + OtlpHttpClientOptions otlp_http_client_options( + options.url, options.content_type, options.json_bytes_mapping, options.use_json_name, + options.console_debug, options.timeout, options.http_headers); + return otlp_http_client_options; +} - bool waitForRequests(unsigned timeOutSec, size_t expected_count = 1) - { - std::unique_lock lk(mtx_requests); - if (cv_got_events.wait_for(lk, std::chrono::milliseconds(1000 * timeOutSec), - [&] { return getCurrentRequestCount() >= expected_count; })) - { - return true; - } - return false; - } - - size_t getCurrentRequestCount() const - { - return received_requests_json_.size() + received_requests_binary_.size(); - } +namespace http_client = opentelemetry::ext::http::client; +class OtlpHttpExporterTestPeer : public ::testing::Test +{ public: - std::unique_ptr GetExporter(HttpRequestContentType content_type) + std::unique_ptr GetExporter(std::unique_ptr http_client) { - OtlpHttpExporterOptions opts; - opts.url = server_address_; - opts.content_type = content_type; - opts.console_debug = true; - opts.http_headers.insert( - std::make_pair("Custom-Header-Key", "Custom-Header-Value")); - return std::unique_ptr(new OtlpHttpExporter(opts)); + return std::unique_ptr(new OtlpHttpExporter(std::move(http_client))); } // Get the options associated with the given exporter. @@ -187,13 +74,23 @@ class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS:: { return exporter->options_; } + + static std::pair> + GetMockOtlpHttpClient(HttpRequestContentType content_type) + { + auto http_client = http_client::HttpClientFactory::CreateNoSend(); + return {new OtlpHttpClient(MakeOtlpHttpClientOptions(content_type), http_client), http_client}; + } }; // Create spans, let processor call Export() TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTest) { - size_t old_count = getCurrentRequestCount(); - auto exporter = GetExporter(HttpRequestContentType::kJson); + auto mock_otlp_client = + OtlpHttpExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kJson); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); resource::ResourceAttributes resource_attributes = {{"service.name", "unit_test_service"}, {"tenant.id", "test_user"}}; @@ -222,47 +119,57 @@ TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTest) new sdk::trace::TracerProvider(std::move(processor), resource)); std::string report_trace_id; - { - char trace_id_hex[2 * trace_api::TraceId::kSize] = {0}; - auto tracer = provider->GetTracer("test"); - auto parent_span = tracer->StartSpan("Test parent span"); - - trace_api::StartSpanOptions child_span_opts = {}; - child_span_opts.parent = parent_span->GetContext(); - - auto child_span = tracer->StartSpan("Test child span", child_span_opts); - child_span->End(); - parent_span->End(); - nostd::get(child_span_opts.parent) - .trace_id() - .ToLowerBase16(MakeSpan(trace_id_hex)); - report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); - } + char trace_id_hex[2 * trace_api::TraceId::kSize] = {0}; + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); + + trace_api::StartSpanOptions child_span_opts = {}; + child_span_opts.parent = parent_span->GetContext(); + + auto child_span = tracer->StartSpan("Test child span", child_span_opts); + + nostd::get(child_span_opts.parent) + .trace_id() + .ToLowerBase16(MakeSpan(trace_id_hex)); + report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, + report_trace_id](opentelemetry::ext::http::client::EventHandler &callback) { + auto check_json = nlohmann::json::parse(mock_session->GetRequest()->body_, nullptr, false); + auto resource_span = *check_json["resource_spans"].begin(); + auto instrumentation_library_span = *resource_span["instrumentation_library_spans"].begin(); + auto span = *instrumentation_library_span["spans"].begin(); + auto received_trace_id = span["trace_id"].get(); + EXPECT_EQ(received_trace_id, report_trace_id); + + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + // let the otlp_http_client to continue + http_client::nosend::Response response; + callback.OnResponse(response); + }); - ASSERT_TRUE(waitForRequests(30, old_count + 1)); - auto check_json = received_requests_json_.back(); - auto resource_span = *check_json["resource_spans"].begin(); - auto instrumentation_library_span = *resource_span["instrumentation_library_spans"].begin(); - auto span = *instrumentation_library_span["spans"].begin(); - auto received_trace_id = span["trace_id"].get(); - EXPECT_EQ(received_trace_id, report_trace_id); - { - auto custom_header = received_requests_headers_.find("Custom-Header-Key"); - ASSERT_TRUE(custom_header != received_requests_headers_.end()); - if (custom_header != received_requests_headers_.end()) - { - EXPECT_EQ("Custom-Header-Value", custom_header->second); - } - } + child_span->End(); + parent_span->End(); } // Create spans, let processor call Export() TEST_F(OtlpHttpExporterTestPeer, ExportBinaryIntegrationTest) { - size_t old_count = getCurrentRequestCount(); - - auto exporter = GetExporter(HttpRequestContentType::kBinary); + auto mock_otlp_client = + OtlpHttpExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kBinary); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); resource::ResourceAttributes resource_attributes = {{"service.name", "unit_test_service"}, {"tenant.id", "test_user"}}; @@ -292,32 +199,46 @@ TEST_F(OtlpHttpExporterTestPeer, ExportBinaryIntegrationTest) new sdk::trace::TracerProvider(std::move(processor), resource)); std::string report_trace_id; - { - uint8_t trace_id_binary[trace_api::TraceId::kSize] = {0}; - auto tracer = provider->GetTracer("test"); - auto parent_span = tracer->StartSpan("Test parent span"); - - trace_api::StartSpanOptions child_span_opts = {}; - child_span_opts.parent = parent_span->GetContext(); - - auto child_span = tracer->StartSpan("Test child span", child_span_opts); - child_span->End(); - parent_span->End(); - nostd::get(child_span_opts.parent) - .trace_id() - .CopyBytesTo(MakeSpan(trace_id_binary)); - report_trace_id.assign(reinterpret_cast(trace_id_binary), sizeof(trace_id_binary)); - } - - ASSERT_TRUE(waitForRequests(30, old_count + 1)); + uint8_t trace_id_binary[trace_api::TraceId::kSize] = {0}; + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); + + trace_api::StartSpanOptions child_span_opts = {}; + child_span_opts.parent = parent_span->GetContext(); + + auto child_span = tracer->StartSpan("Test child span", child_span_opts); + nostd::get(child_span_opts.parent) + .trace_id() + .CopyBytesTo(MakeSpan(trace_id_binary)); + report_trace_id.assign(reinterpret_cast(trace_id_binary), sizeof(trace_id_binary)); + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, + report_trace_id](opentelemetry::ext::http::client::EventHandler &callback) { + opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request_body; + request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + auto received_trace_id = + request_body.resource_spans(0).instrumentation_library_spans(0).spans(0).trace_id(); + EXPECT_EQ(received_trace_id, report_trace_id); + + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + // let the otlp_http_client to continue + http_client::nosend::Response response; + callback.OnResponse(response); + }); - auto received_trace_id = received_requests_binary_.back() - .resource_spans(0) - .instrumentation_library_spans(0) - .spans(0) - .trace_id(); - EXPECT_EQ(received_trace_id, report_trace_id); + child_span->End(); + parent_span->End(); } // Test exporter configuration options diff --git a/exporters/otlp/test/otlp_http_log_exporter_test.cc b/exporters/otlp/test/otlp_http_log_exporter_test.cc old mode 100755 new mode 100644 index c3c9095ec2..9be4af0983 --- a/exporters/otlp/test/otlp_http_log_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_log_exporter_test.cc @@ -13,6 +13,8 @@ # include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" # include "opentelemetry/common/key_value_iterable_view.h" +# include "opentelemetry/ext/http/client/http_client_factory.h" +# include "opentelemetry/ext/http/client/nosend/http_client_nosend.h" # include "opentelemetry/ext/http/server/http_server.h" # include "opentelemetry/logs/provider.h" # include "opentelemetry/sdk/logs/batch_log_processor.h" @@ -22,6 +24,7 @@ # include "opentelemetry/sdk/resource/resource.h" # include +# include "gmock/gmock.h" # include "nlohmann/json.hpp" @@ -45,145 +48,27 @@ static nostd::span MakeSpan(T (&array)[N]) return nostd::span(array); } -class OtlpHttpLogExporterTestPeer : public ::testing::Test, - public HTTP_SERVER_NS::HttpRequestCallback +OtlpHttpClientOptions MakeOtlpHttpClientOptions(HttpRequestContentType content_type) { -protected: - HTTP_SERVER_NS::HttpServer server_; - std::string server_address_; - std::atomic is_setup_; - std::atomic is_running_; - std::mutex mtx_requests; - std::condition_variable cv_got_events; - std::vector received_requests_json_; - std::vector - received_requests_binary_; - std::map received_requests_headers_; - -public: - OtlpHttpLogExporterTestPeer() : is_setup_(false), is_running_(false){}; - - virtual void SetUp() override - { - if (is_setup_.exchange(true)) - { - return; - } - int port = server_.addListeningPort(14372); - std::ostringstream os; - os << "localhost:" << port; - server_address_ = "http://" + os.str() + "/v1/logs"; - server_.setServerName(os.str()); - server_.setKeepalive(false); - server_.addHandler("/v1/logs", *this); - server_.start(); - is_running_ = true; - } - - virtual void TearDown() override - { - if (!is_setup_.exchange(false)) - return; - server_.stop(); - is_running_ = false; - } - - virtual int onHttpRequest(HTTP_SERVER_NS::HttpRequest const &request, - HTTP_SERVER_NS::HttpResponse &response) override - { - const std::string *request_content_type = nullptr; - { - auto it = request.headers.find("Content-Type"); - if (it != request.headers.end()) - { - request_content_type = &it->second; - } - } - received_requests_headers_ = request.headers; - - int response_status = 0; - - if (request.uri == "/v1/logs") - { - response.headers["Content-Type"] = "application/json"; - std::unique_lock lk(mtx_requests); - if (nullptr != request_content_type && *request_content_type == kHttpBinaryContentType) - { - opentelemetry::proto::collector::logs::v1::ExportLogsServiceRequest request_body; - if (request_body.ParseFromArray(&request.content[0], - static_cast(request.content.size()))) - { - received_requests_binary_.push_back(request_body); - response.body = "{\"code\": 0, \"message\": \"success\"}"; - } - else - { - response.body = "{\"code\": 400, \"message\": \"Parse binary failed\"}"; - response_status = 400; - } - } - else if (nullptr != request_content_type && *request_content_type == kHttpJsonContentType) - { - auto json = nlohmann::json::parse(request.content, nullptr, false); - response.headers["Content-Type"] = "application/json"; - if (json.is_discarded()) - { - response.body = "{\"code\": 400, \"message\": \"Parse json failed\"}"; - response_status = 400; - } - else - { - received_requests_json_.push_back(json); - response.body = "{\"code\": 0, \"message\": \"success\"}"; - } - } - else - { - response.body = "{\"code\": 400, \"message\": \"Unsupported content type\"}"; - response_status = 400; - } - - response_status = 200; - } - else - { - std::unique_lock lk(mtx_requests); - response.headers["Content-Type"] = "text/plain"; - response.body = "404 Not Found"; - response_status = 200; - } - - cv_got_events.notify_one(); - - return response_status; - } - - bool waitForRequests(unsigned timeOutSec, size_t expected_count = 1) - { - std::unique_lock lk(mtx_requests); - if (cv_got_events.wait_for(lk, std::chrono::milliseconds(1000 * timeOutSec), - [&] { return getCurrentRequestCount() >= expected_count; })) - { - return true; - } - return false; - } + OtlpHttpLogExporterOptions options; + options.content_type = content_type; + options.console_debug = true; + options.http_headers.insert( + std::make_pair("Custom-Header-Key", "Custom-Header-Value")); + OtlpHttpClientOptions otlp_http_client_options( + options.url, options.content_type, options.json_bytes_mapping, options.use_json_name, + options.console_debug, options.timeout, options.http_headers); + return otlp_http_client_options; +} - size_t getCurrentRequestCount() const - { - return received_requests_json_.size() + received_requests_binary_.size(); - } +namespace http_client = opentelemetry::ext::http::client; +class OtlpHttpLogExporterTestPeer : public ::testing::Test +{ public: - std::unique_ptr GetExporter(HttpRequestContentType content_type) + std::unique_ptr GetExporter(std::unique_ptr http_client) { - OtlpHttpLogExporterOptions opts; - opts.url = server_address_; - opts.content_type = content_type; - opts.console_debug = true; - opts.http_headers.insert( - std::make_pair("Custom-Header-Key", "Custom-Header-Value")); - return std::unique_ptr(new OtlpHttpLogExporter(opts)); + return std::unique_ptr(new OtlpHttpLogExporter(std::move(http_client))); } // Get the options associated with the given exporter. @@ -191,13 +76,22 @@ class OtlpHttpLogExporterTestPeer : public ::testing::Test, { return exporter->options_; } + static std::pair> + GetMockOtlpHttpClient(HttpRequestContentType content_type) + { + auto http_client = http_client::HttpClientFactory::CreateNoSend(); + return {new OtlpHttpClient(MakeOtlpHttpClientOptions(content_type), http_client), http_client}; + } }; // Create log records, let processor call Export() TEST_F(OtlpHttpLogExporterTestPeer, ExportJsonIntegrationTest) { - size_t old_count = getCurrentRequestCount(); - auto exporter = GetExporter(HttpRequestContentType::kJson); + auto mock_otlp_client = + OtlpHttpLogExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kJson); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); bool attribute_storage_bool_value[] = {true, false, true}; int32_t attribute_storage_int32_value[] = {1, 2}; @@ -213,84 +107,80 @@ TEST_F(OtlpHttpLogExporterTestPeer, ExportJsonIntegrationTest) std::string report_trace_id; std::string report_span_id; - { - uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - char trace_id_hex[2 * opentelemetry::trace::TraceId::kSize] = {0}; - opentelemetry::trace::TraceId trace_id{trace_id_bin}; - uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', - '3', '2', '1', '0'}; - char span_id_hex[2 * opentelemetry::trace::SpanId::kSize] = {0}; - opentelemetry::trace::SpanId span_id{span_id_bin}; - - const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; - auto logger = provider->GetLogger("test", "", "opentelelemtry_library", "", schema_url); - logger->Log(opentelemetry::logs::Severity::kInfo, "Log name", "Log message", - {{"service.name", "unit_test_service"}, - {"tenant.id", "test_user"}, - {"bool_value", true}, - {"int32_value", static_cast(1)}, - {"uint32_value", static_cast(2)}, - {"int64_value", static_cast(0x1100000000LL)}, - {"uint64_value", static_cast(0x1200000000ULL)}, - {"double_value", static_cast(3.1)}, - {"vec_bool_value", attribute_storage_bool_value}, - {"vec_int32_value", attribute_storage_int32_value}, - {"vec_uint32_value", attribute_storage_uint32_value}, - {"vec_int64_value", attribute_storage_int64_value}, - {"vec_uint64_value", attribute_storage_uint64_value}, - {"vec_double_value", attribute_storage_double_value}, - {"vec_string_value", attribute_storage_string_value}}, - trace_id, span_id, - opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, - std::chrono::system_clock::now()); - - trace_id.ToLowerBase16(MakeSpan(trace_id_hex)); - report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); - - span_id.ToLowerBase16(MakeSpan(span_id_hex)); - report_span_id.assign(span_id_hex, sizeof(span_id_hex)); - } - - ASSERT_TRUE(waitForRequests(30, old_count + 1)); - auto check_json = received_requests_json_.back(); - auto resource_logs = *check_json["resource_logs"].begin(); - auto instrumentation_library_span = *resource_logs["instrumentation_library_logs"].begin(); - auto log = *instrumentation_library_span["logs"].begin(); - auto received_trace_id = log["trace_id"].get(); - auto received_span_id = log["span_id"].get(); - EXPECT_EQ(received_trace_id, report_trace_id); - EXPECT_EQ(received_span_id, report_span_id); - EXPECT_EQ("Log name", log["name"].get()); - EXPECT_EQ("Log message", log["body"]["string_value"].get()); - EXPECT_LE(15, log["attributes"].size()); - bool check_service_name = false; - for (auto attribute : log["attributes"]) - { - if ("service.name" == attribute["key"].get()) - { - check_service_name = true; - EXPECT_EQ("unit_test_service", attribute["value"]["string_value"].get()); - } - } - ASSERT_TRUE(check_service_name); - - { - auto custom_header = received_requests_headers_.find("Custom-Header-Key"); - ASSERT_TRUE(custom_header != received_requests_headers_.end()); - if (custom_header != received_requests_headers_.end()) - { - EXPECT_EQ("Custom-Header-Value", custom_header->second); - } - } + uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + char trace_id_hex[2 * opentelemetry::trace::TraceId::kSize] = {0}; + opentelemetry::trace::TraceId trace_id{trace_id_bin}; + uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', + '3', '2', '1', '0'}; + char span_id_hex[2 * opentelemetry::trace::SpanId::kSize] = {0}; + opentelemetry::trace::SpanId span_id{span_id_bin}; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = provider->GetLogger("test", "", "opentelelemtry_library", "", schema_url); + logger->Log(opentelemetry::logs::Severity::kInfo, "Log name", "Log message", + {{"service.name", "unit_test_service"}, + {"tenant.id", "test_user"}, + {"bool_value", true}, + {"int32_value", static_cast(1)}, + {"uint32_value", static_cast(2)}, + {"int64_value", static_cast(0x1100000000LL)}, + {"uint64_value", static_cast(0x1200000000ULL)}, + {"double_value", static_cast(3.1)}, + {"vec_bool_value", attribute_storage_bool_value}, + {"vec_int32_value", attribute_storage_int32_value}, + {"vec_uint32_value", attribute_storage_uint32_value}, + {"vec_int64_value", attribute_storage_int64_value}, + {"vec_uint64_value", attribute_storage_uint64_value}, + {"vec_double_value", attribute_storage_double_value}, + {"vec_string_value", attribute_storage_string_value}}, + trace_id, span_id, + opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, + std::chrono::system_clock::now()); + + trace_id.ToLowerBase16(MakeSpan(trace_id_hex)); + report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); + + span_id.ToLowerBase16(MakeSpan(span_id_hex)); + report_span_id.assign(span_id_hex, sizeof(span_id_hex)); + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, report_trace_id, + report_span_id](opentelemetry::ext::http::client::EventHandler &callback) { + auto check_json = nlohmann::json::parse(mock_session->GetRequest()->body_, nullptr, false); + auto resource_logs = *check_json["resource_logs"].begin(); + auto instrumentation_library_span = *resource_logs["instrumentation_library_logs"].begin(); + auto log = *instrumentation_library_span["logs"].begin(); + auto received_trace_id = log["trace_id"].get(); + auto received_span_id = log["span_id"].get(); + EXPECT_EQ(received_trace_id, report_trace_id); + EXPECT_EQ(received_span_id, report_span_id); + EXPECT_EQ("Log name", log["name"].get()); + EXPECT_EQ("Log message", log["body"]["string_value"].get()); + EXPECT_LE(15, log["attributes"].size()); + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + // let the otlp_http_client to continue + http_client::nosend::Response response; + callback.OnResponse(response); + }); } // Create log records, let processor call Export() TEST_F(OtlpHttpLogExporterTestPeer, ExportBinaryIntegrationTest) { - size_t old_count = getCurrentRequestCount(); - - auto exporter = GetExporter(HttpRequestContentType::kBinary); + auto mock_otlp_client = + OtlpHttpLogExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kBinary); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr{mock_otlp_http_client}); bool attribute_storage_bool_value[] = {true, false, true}; int32_t attribute_storage_int32_value[] = {1, 2}; @@ -306,58 +196,66 @@ TEST_F(OtlpHttpLogExporterTestPeer, ExportBinaryIntegrationTest) std::string report_trace_id; std::string report_span_id; - { - uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - opentelemetry::trace::TraceId trace_id{trace_id_bin}; - uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', - '3', '2', '1', '0'}; - opentelemetry::trace::SpanId span_id{span_id_bin}; - - const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; - auto logger = provider->GetLogger("test", "", "opentelelemtry_library", "", schema_url); - logger->Log(opentelemetry::logs::Severity::kInfo, "Log name", "Log message", - {{"service.name", "unit_test_service"}, - {"tenant.id", "test_user"}, - {"bool_value", true}, - {"int32_value", static_cast(1)}, - {"uint32_value", static_cast(2)}, - {"int64_value", static_cast(0x1100000000LL)}, - {"uint64_value", static_cast(0x1200000000ULL)}, - {"double_value", static_cast(3.1)}, - {"vec_bool_value", attribute_storage_bool_value}, - {"vec_int32_value", attribute_storage_int32_value}, - {"vec_uint32_value", attribute_storage_uint32_value}, - {"vec_int64_value", attribute_storage_int64_value}, - {"vec_uint64_value", attribute_storage_uint64_value}, - {"vec_double_value", attribute_storage_double_value}, - {"vec_string_value", attribute_storage_string_value}}, - trace_id, span_id, - opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, - std::chrono::system_clock::now()); - - report_trace_id.assign(reinterpret_cast(trace_id_bin), sizeof(trace_id_bin)); - report_span_id.assign(reinterpret_cast(span_id_bin), sizeof(span_id_bin)); - } - - ASSERT_TRUE(waitForRequests(30, old_count + 1)); - auto received_log = - received_requests_binary_.back().resource_logs(0).instrumentation_library_logs(0).logs(0); - EXPECT_EQ(received_log.trace_id(), report_trace_id); - EXPECT_EQ(received_log.span_id(), report_span_id); - EXPECT_EQ("Log name", received_log.name()); - EXPECT_EQ("Log message", received_log.body().string_value()); - EXPECT_LE(15, received_log.attributes_size()); - bool check_service_name = false; - for (auto &attribute : received_log.attributes()) - { - if ("service.name" == attribute.key()) - { - check_service_name = true; - EXPECT_EQ("unit_test_service", attribute.value().string_value()); - } - } - ASSERT_TRUE(check_service_name); + uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + opentelemetry::trace::TraceId trace_id{trace_id_bin}; + uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', + '3', '2', '1', '0'}; + opentelemetry::trace::SpanId span_id{span_id_bin}; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = provider->GetLogger("test", "", "opentelelemtry_library", "", schema_url); + logger->Log(opentelemetry::logs::Severity::kInfo, "Log name", "Log message", + {{"service.name", "unit_test_service"}, + {"tenant.id", "test_user"}, + {"bool_value", true}, + {"int32_value", static_cast(1)}, + {"uint32_value", static_cast(2)}, + {"int64_value", static_cast(0x1100000000LL)}, + {"uint64_value", static_cast(0x1200000000ULL)}, + {"double_value", static_cast(3.1)}, + {"vec_bool_value", attribute_storage_bool_value}, + {"vec_int32_value", attribute_storage_int32_value}, + {"vec_uint32_value", attribute_storage_uint32_value}, + {"vec_int64_value", attribute_storage_int64_value}, + {"vec_uint64_value", attribute_storage_uint64_value}, + {"vec_double_value", attribute_storage_double_value}, + {"vec_string_value", attribute_storage_string_value}}, + trace_id, span_id, + opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, + std::chrono::system_clock::now()); + + report_trace_id.assign(reinterpret_cast(trace_id_bin), sizeof(trace_id_bin)); + report_span_id.assign(reinterpret_cast(span_id_bin), sizeof(span_id_bin)); + + auto no_send_client = std::static_pointer_cast(client); + auto mock_session = + std::static_pointer_cast(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, report_trace_id, + report_span_id](opentelemetry::ext::http::client::EventHandler &callback) { + opentelemetry::proto::collector::logs::v1::ExportLogsServiceRequest request_body; + request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + auto received_log = request_body.resource_logs(0).instrumentation_library_logs(0).logs(0); + EXPECT_EQ(received_log.trace_id(), report_trace_id); + EXPECT_EQ(received_log.span_id(), report_span_id); + EXPECT_EQ("Log name", received_log.name()); + EXPECT_EQ("Log message", received_log.body().string_value()); + EXPECT_LE(15, received_log.attributes_size()); + bool check_service_name = false; + for (auto &attribute : received_log.attributes()) + { + if ("service.name" == attribute.key()) + { + check_service_name = true; + EXPECT_EQ("unit_test_service", attribute.value().string_value()); + } + } + ASSERT_TRUE(check_service_name); + http_client::nosend::Response response; + callback.OnResponse(response); + }); } // Test exporter configuration options diff --git a/exporters/zipkin/test/zipkin_exporter_test.cc b/exporters/zipkin/test/zipkin_exporter_test.cc index 67d5c1c8ce..eec71f43d6 100644 --- a/exporters/zipkin/test/zipkin_exporter_test.cc +++ b/exporters/zipkin/test/zipkin_exporter_test.cc @@ -39,124 +39,9 @@ static nostd::span MakeSpan(T (&array)[N]) return nostd::span(array); } -class ZipkinExporterTestPeer : public ::testing::Test, HTTP_SERVER_NS::HttpRequestCallback +class ZipkinExporterTestPeer : public ::testing::Test { -protected: - HTTP_SERVER_NS::HttpServer server_; - std::string server_address_; - std::atomic is_setup_; - std::atomic is_running_; - std::mutex mtx_requests; - std::condition_variable cv_got_events; - std::vector received_requests_json_; - std::map received_requests_headers_; - -public: - ZipkinExporterTestPeer() : is_setup_(false), is_running_(false){}; - - virtual void SetUp() override - { - if (is_setup_.exchange(true)) - { - return; - } - int port = server_.addListeningPort(14371); - std::ostringstream os; - os << "localhost:" << port; - server_address_ = "http://" + os.str() + "/v1/traces"; - server_.setServerName(os.str()); - server_.setKeepalive(false); - server_.addHandler("/v1/traces", *this); - server_.start(); - is_running_ = true; - } - - virtual void TearDown() override - { - if (!is_setup_.exchange(false)) - return; - server_.stop(); - is_running_ = false; - } - - virtual int onHttpRequest(HTTP_SERVER_NS::HttpRequest const &request, - HTTP_SERVER_NS::HttpResponse &response) override - { - const std::string *request_content_type = nullptr; - { - auto it = request.headers.find("Content-Type"); - if (it != request.headers.end()) - { - request_content_type = &it->second; - } - } - received_requests_headers_ = request.headers; - - int response_status = 0; - std::string kHttpJsonContentType{"application/json"}; - if (request.uri == "/v1/traces") - { - response.headers["Content-Type"] = kHttpJsonContentType; - std::unique_lock lk(mtx_requests); - if (nullptr != request_content_type && *request_content_type == kHttpJsonContentType) - { - auto json = nlohmann::json::parse(request.content, nullptr, false); - response.headers["Content-Type"] = kHttpJsonContentType; - if (json.is_discarded()) - { - response.body = "{\"code\": 400, \"message\": \"Parse json failed\"}"; - response_status = 400; - } - else - { - received_requests_json_.push_back(json); - response.body = "{\"code\": 0, \"message\": \"success\"}"; - } - } - else - { - response.body = "{\"code\": 400, \"message\": \"Unsupported content type\"}"; - response_status = 400; - } - - response_status = 200; - } - else - { - std::unique_lock lk(mtx_requests); - response.headers["Content-Type"] = "text/plain"; - response.body = "404 Not Found"; - response_status = 200; - } - - cv_got_events.notify_one(); - - return response_status; - } - - bool waitForRequests(unsigned timeOutSec, size_t expected_count = 1) - { - std::unique_lock lk(mtx_requests); - if (cv_got_events.wait_for(lk, std::chrono::milliseconds(1000 * timeOutSec), - [&] { return getCurrentRequestCount() >= expected_count; })) - { - return true; - } - return false; - } - - size_t getCurrentRequestCount() const { return received_requests_json_.size(); } - public: - std::unique_ptr GetExporter() - { - ZipkinExporterOptions opts; - opts.endpoint = server_address_; - opts.headers.insert( - std::make_pair("Custom-Header-Key", "Custom-Header-Value")); - return std::unique_ptr(new ZipkinExporter(opts)); - } - std::unique_ptr GetExporter( std::shared_ptr http_client) { @@ -185,11 +70,40 @@ class MockHttpClient : public opentelemetry::ext::http::client::HttpClientSync (noexcept, override)); }; +class IsValidMessageMatcher +{ +public: + IsValidMessageMatcher(const std::string &trace_id) : trace_id_(trace_id) {} + template + bool MatchAndExplain(const T &p, MatchResultListener * /* listener */) const + { + auto body = std::string(p.begin(), p.end()); + nlohmann::json check_json = nlohmann::json::parse(body); + auto trace_id_kv = check_json.at(0).find("traceId"); + auto received_trace_id = trace_id_kv.value().get(); + return trace_id_ == received_trace_id; + } + + void DescribeTo(std::ostream *os) const { *os << "received trace_id matches"; } + + void DescribeNegationTo(std::ostream *os) const { *os << "received trace_id does not matche"; } + +private: + std::string trace_id_; +}; + +PolymorphicMatcher IsValidMessage(const std::string &trace_id) +{ + return MakePolymorphicMatcher(IsValidMessageMatcher(trace_id)); +} + // Create spans, let processor call Export() TEST_F(ZipkinExporterTestPeer, ExportJsonIntegrationTest) { - size_t old_count = getCurrentRequestCount(); - auto exporter = GetExporter(); + auto mock_http_client = new MockHttpClient; + // Leave a comment line here or different version of clang-format has a different result here + auto exporter = GetExporter( + std::shared_ptr{mock_http_client}); resource::ResourceAttributes resource_attributes = {{"service.name", "unit_test_service"}, {"tenant.id", "test_user"}}; @@ -218,37 +132,28 @@ TEST_F(ZipkinExporterTestPeer, ExportJsonIntegrationTest) new sdk::trace::TracerProvider(std::move(processor), resource)); std::string report_trace_id; - { - char trace_id_hex[2 * trace_api::TraceId::kSize] = {0}; - auto tracer = provider->GetTracer("test"); - auto parent_span = tracer->StartSpan("Test parent span"); + char trace_id_hex[2 * trace_api::TraceId::kSize] = {0}; + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); - trace_api::StartSpanOptions child_span_opts = {}; - child_span_opts.parent = parent_span->GetContext(); + trace_api::StartSpanOptions child_span_opts = {}; + child_span_opts.parent = parent_span->GetContext(); + auto child_span = tracer->StartSpan("Test child span", child_span_opts); - auto child_span = tracer->StartSpan("Test child span", child_span_opts); - child_span->End(); - parent_span->End(); + nostd::get(child_span_opts.parent) + .trace_id() + .ToLowerBase16(MakeSpan(trace_id_hex)); + report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); - nostd::get(child_span_opts.parent) - .trace_id() - .ToLowerBase16(MakeSpan(trace_id_hex)); - report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); - } + auto expected_url = nostd::string_view{"http://localhost:9411/api/v2/spans"}; + EXPECT_CALL(*mock_http_client, Post(expected_url, IsValidMessage(report_trace_id), _)) + .Times(Exactly(1)) + .WillOnce(Return(ByMove(std::move(ext::http::client::Result{ + std::unique_ptr{new ext::http::client::curl::Response()}, + ext::http::client::SessionState::Response})))); - ASSERT_TRUE(waitForRequests(30, old_count + 1)); - auto check_json = received_requests_json_.back(); - auto trace_id_kv = check_json.at(0).find("traceId"); - auto received_trace_id = trace_id_kv.value().get(); - EXPECT_EQ(received_trace_id, report_trace_id); - { - auto custom_header = received_requests_headers_.find("Custom-Header-Key"); - ASSERT_TRUE(custom_header != received_requests_headers_.end()); - if (custom_header != received_requests_headers_.end()) - { - EXPECT_EQ("Custom-Header-Value", custom_header->second); - } - } + child_span->End(); + parent_span->End(); } // Create spans, let processor call Export() diff --git a/ext/include/opentelemetry/ext/http/client/curl/http_client_curl.h b/ext/include/opentelemetry/ext/http/client/curl/http_client_curl.h index 32d0a96783..9f2f05f3f0 100644 --- a/ext/include/opentelemetry/ext/http/client/curl/http_client_curl.h +++ b/ext/include/opentelemetry/ext/http/client/curl/http_client_curl.h @@ -185,6 +185,9 @@ class Session : public opentelemetry::ext::http::client::Session */ const std::string &GetBaseUri() const { return host_; } +#ifdef ENABLE_TEST + std::shared_ptr GetRequest() { return http_request_; } +#endif private: std::shared_ptr http_request_; std::string host_; diff --git a/ext/include/opentelemetry/ext/http/client/http_client_factory.h b/ext/include/opentelemetry/ext/http/client/http_client_factory.h index 49504d4aac..f03c1a0b64 100644 --- a/ext/include/opentelemetry/ext/http/client/http_client_factory.h +++ b/ext/include/opentelemetry/ext/http/client/http_client_factory.h @@ -17,8 +17,10 @@ class HttpClientFactory static std::shared_ptr CreateSync(); static std::shared_ptr Create(); + + static std::shared_ptr CreateNoSend(); }; } // namespace client } // namespace http } // namespace ext -OPENTELEMETRY_END_NAMESPACE \ No newline at end of file +OPENTELEMETRY_END_NAMESPACE diff --git a/ext/include/opentelemetry/ext/http/client/nosend/http_client_nosend.h b/ext/include/opentelemetry/ext/http/client/nosend/http_client_nosend.h new file mode 100644 index 0000000000..02433d75ce --- /dev/null +++ b/ext/include/opentelemetry/ext/http/client/nosend/http_client_nosend.h @@ -0,0 +1,184 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef ENABLE_TEST +# include "opentelemetry/ext/http/client/http_client.h" +# include "opentelemetry/ext/http/common/url_parser.h" +# include "opentelemetry/version.h" + +# include +# include +# include + +# include +# include "gmock/gmock.h" + +using namespace testing; +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace http +{ +namespace client +{ +namespace nosend +{ + +const opentelemetry::ext::http::client::StatusCode Http_Ok = 200; + +class Request : public opentelemetry::ext::http::client::Request +{ +public: + Request() : method_(opentelemetry::ext::http::client::Method::Get), uri_("/") {} + + void SetMethod(opentelemetry::ext::http::client::Method method) noexcept override + { + method_ = method; + } + + void SetBody(opentelemetry::ext::http::client::Body &body) noexcept override + { + body_ = std::move(body); + } + + void AddHeader(nostd::string_view name, nostd::string_view value) noexcept override + { + headers_.insert(std::pair(static_cast(name), + static_cast(value))); + } + + void ReplaceHeader(nostd::string_view name, nostd::string_view value) noexcept override; + + virtual void SetUri(nostd::string_view uri) noexcept override + { + uri_ = static_cast(uri); + } + + void SetTimeoutMs(std::chrono::milliseconds timeout_ms) noexcept override + { + timeout_ms_ = timeout_ms; + } + +public: + opentelemetry::ext::http::client::Method method_; + opentelemetry::ext::http::client::Body body_; + opentelemetry::ext::http::client::Headers headers_; + std::string uri_; + std::chrono::milliseconds timeout_ms_{5000}; // ms +}; + +class Response : public opentelemetry::ext::http::client::Response +{ +public: + Response() : status_code_(Http_Ok) {} + + virtual const opentelemetry::ext::http::client::Body &GetBody() const noexcept override + { + return body_; + } + + virtual bool ForEachHeader( + nostd::function_ref callable) + const noexcept override; + + virtual bool ForEachHeader( + const nostd::string_view &name, + nostd::function_ref callable) + const noexcept override; + + virtual opentelemetry::ext::http::client::StatusCode GetStatusCode() const noexcept override + { + return status_code_; + } + +public: + Headers headers_; + opentelemetry::ext::http::client::Body body_; + opentelemetry::ext::http::client::StatusCode status_code_; +}; + +class HttpClient; + +class Session : public opentelemetry::ext::http::client::Session +{ +public: + Session(HttpClient &http_client, + std::string scheme = "http", + const std::string &host = "", + uint16_t port = 80) + : http_client_(http_client), is_session_active_(false) + { + host_ = scheme + "://" + host + ":" + std::to_string(port) + "/"; + } + + std::shared_ptr CreateRequest() noexcept override + { + http_request_.reset(new Request()); + return http_request_; + } + + MOCK_METHOD(void, + SendRequest, + (opentelemetry::ext::http::client::EventHandler &), + (noexcept, override)); + + virtual bool CancelSession() noexcept override; + + virtual bool FinishSession() noexcept override; + + virtual bool IsSessionActive() noexcept override { return is_session_active_; } + + void SetId(uint64_t session_id) { session_id_ = session_id; } + + /** + * Returns the base URI. + * @return the base URI as a string consisting of scheme, host and port. + */ + const std::string &GetBaseUri() const { return host_; } + + std::shared_ptr GetRequest() { return http_request_; } + +private: + std::shared_ptr http_request_; + std::string host_; + uint64_t session_id_; + HttpClient &http_client_; + bool is_session_active_; +}; + +class HttpClient : public opentelemetry::ext::http::client::HttpClient +{ +public: + HttpClient() { session_ = std::shared_ptr{new Session(*this)}; } + + std::shared_ptr CreateSession( + nostd::string_view) noexcept override + { + return session_; + } + + bool CancelAllSessions() noexcept override + { + session_->CancelSession(); + return true; + } + + bool FinishAllSessions() noexcept override + { + session_->FinishSession(); + return true; + } + + void CleanupSession(uint64_t session_id) {} + + std::shared_ptr session_; +}; + +} // namespace nosend +} // namespace client +} // namespace http +} // namespace ext +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/ext/src/CMakeLists.txt b/ext/src/CMakeLists.txt index 191a9e7bdf..a976882ff0 100644 --- a/ext/src/CMakeLists.txt +++ b/ext/src/CMakeLists.txt @@ -1,4 +1,8 @@ if(WITH_ZPAGES) add_subdirectory(zpages) endif() + add_subdirectory(http/client/curl) +if(BUILD_TESTING) + add_subdirectory(http/client/nosend) +endif() diff --git a/ext/src/http/client/curl/http_client_factory_curl.cc b/ext/src/http/client/curl/http_client_factory_curl.cc index 262dfde63c..f6266c2931 100644 --- a/ext/src/http/client/curl/http_client_factory_curl.cc +++ b/ext/src/http/client/curl/http_client_factory_curl.cc @@ -15,4 +15,4 @@ std::shared_ptr http_client::HttpClientFactory::Create( std::shared_ptr http_client::HttpClientFactory::CreateSync() { return std::make_shared(); -} \ No newline at end of file +} diff --git a/ext/src/http/client/nosend/BUILD b/ext/src/http/client/nosend/BUILD new file mode 100644 index 0000000000..b27106a164 --- /dev/null +++ b/ext/src/http/client/nosend/BUILD @@ -0,0 +1,19 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "http_client_nosend", + srcs = [ + "http_client_factory_nosend.cc", + "http_client_nosend.cc", + ], + include_prefix = "src/http/client/nosend", + tags = [ + "test", + ], + deps = [ + "//api", + "//ext:headers", + "//sdk:headers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/ext/src/http/client/nosend/CMakeLists.txt b/ext/src/http/client/nosend/CMakeLists.txt new file mode 100644 index 0000000000..9118abbfb5 --- /dev/null +++ b/ext/src/http/client/nosend/CMakeLists.txt @@ -0,0 +1,36 @@ +if(${BUILD_TESTING}) + add_library(http_client_nosend http_client_factory_nosend.cc + http_client_nosend.cc) + + set_target_properties(http_client_nosend PROPERTIES EXPORT_NAME + http_client_nosend) + + if(MSVC) + # Explicitly specify that we consume GTest from shared library. The rest of + # code logic below determines whether we link Release or Debug flavor of the + # library. These flavors have different prefix on Windows, gmock and gmockd + # respectively. + add_definitions(-DGTEST_LINKED_AS_SHARED_LIBRARY=1) + if(GMOCK_LIB) + # unset GMOCK_LIB to force find_library to redo the lookup, as the cached + # entry could cause linking to incorrect flavor of gmock and leading to + # runtime error. + unset(GMOCK_LIB CACHE) + endif() + endif() + if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug") + find_library(GMOCK_LIB gmockd PATH_SUFFIXES lib) + else() + find_library(GMOCK_LIB gmock PATH_SUFFIXES lib) + endif() + + target_link_libraries(http_client_nosend ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIB} + opentelemetry_api opentelemetry_ext) + + install( + TARGETS http_client_nosend + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() diff --git a/ext/src/http/client/nosend/http_client_factory_nosend.cc b/ext/src/http/client/nosend/http_client_factory_nosend.cc new file mode 100644 index 0000000000..841dd2d8eb --- /dev/null +++ b/ext/src/http/client/nosend/http_client_factory_nosend.cc @@ -0,0 +1,13 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/ext/http/client/http_client.h" +#include "opentelemetry/ext/http/client/http_client_factory.h" +#include "opentelemetry/ext/http/client/nosend/http_client_nosend.h" + +namespace http_client = opentelemetry::ext::http::client; + +std::shared_ptr http_client::HttpClientFactory::CreateNoSend() +{ + return std::make_shared(); +} diff --git a/ext/src/http/client/nosend/http_client_nosend.cc b/ext/src/http/client/nosend/http_client_nosend.cc new file mode 100644 index 0000000000..c2b1c6acf9 --- /dev/null +++ b/ext/src/http/client/nosend/http_client_nosend.cc @@ -0,0 +1,71 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_TEST +# include "opentelemetry/ext/http/client/nosend/http_client_nosend.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace http +{ +namespace client +{ +namespace nosend +{ +void Request::ReplaceHeader(nostd::string_view name, nostd::string_view value) noexcept +{ + // erase matching headers + auto range = headers_.equal_range(static_cast(name)); + headers_.erase(range.first, range.second); + AddHeader(name, value); +} + +bool Response::ForEachHeader( + nostd::function_ref callable) + const noexcept +{ + for (const auto &header : headers_) + { + if (!callable(header.first, header.second)) + { + return false; + } + } + return true; +} + +bool Response::ForEachHeader( + const nostd::string_view &name, + nostd::function_ref callable) + const noexcept +{ + auto range = headers_.equal_range(static_cast(name)); + for (auto it = range.first; it != range.second; ++it) + { + if (!callable(it->first, it->second)) + { + return false; + } + } + return true; +} + +bool Session::CancelSession() noexcept +{ + http_client_.CleanupSession(session_id_); + return true; +} + +bool Session::FinishSession() noexcept +{ + http_client_.CleanupSession(session_id_); + return true; +} + +} // namespace nosend +} // namespace client +} // namespace http +} // namespace ext +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/ext/test/http/curl_http_test.cc b/ext/test/http/curl_http_test.cc index bc89b3277e..c9a6312888 100644 --- a/ext/test/http/curl_http_test.cc +++ b/ext/test/http/curl_http_test.cc @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include "opentelemetry/ext//http/client/curl//http_client_curl.h" +#include "opentelemetry/ext//http/client/curl/http_client_curl.h" #include "opentelemetry/ext/http/client/http_client_factory.h" #include "opentelemetry/ext/http/server/http_server.h"