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"