From 5e91f3eb0f8c9e5e0c58c9717137b69c07b10064 Mon Sep 17 00:00:00 2001 From: owent Date: Sun, 30 May 2021 23:15:50 +0800 Subject: [PATCH 1/7] Add OTLP/HTTP+JSON Protocol exporter Signed-off-by: owent --- CMakeLists.txt | 21 +- examples/CMakeLists.txt | 2 +- examples/otlp/BUILD | 17 +- examples/otlp/CMakeLists.txt | 27 +- examples/otlp/README.md | 8 +- examples/otlp/{main.cc => grpc_main.cc} | 0 examples/otlp/http_main.cc | 46 ++ exporters/otlp/BUILD | 30 + exporters/otlp/CMakeLists.txt | 57 +- exporters/otlp/README.md | 14 +- .../exporters/otlp/otlp_http_exporter.h | 113 ++++ exporters/otlp/src/otlp_http_exporter.cc | 553 ++++++++++++++++++ 12 files changed, 854 insertions(+), 34 deletions(-) rename examples/otlp/{main.cc => grpc_main.cc} (100%) create mode 100644 examples/otlp/http_main.cc create mode 100644 exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h create mode 100644 exporters/otlp/src/otlp_http_exporter.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b78ca7ecb..2e075e63dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,7 +203,7 @@ if(WITH_OTLP) find_package(Protobuf REQUIRED) endif() if(NOT gRPC_FOUND) - find_package(gRPC REQUIRED) + find_package(gRPC) endif() if(WIN32) # Always use x64 protoc.exe @@ -221,6 +221,23 @@ if(WITH_OTLP) message("PROTOBUF_PROTOC_EXECUTABLE=${PROTOBUF_PROTOC_EXECUTABLE}") include(cmake/opentelemetry-proto.cmake) + + if(gRPC_FOUND) + option(WITH_OTLP_GRPC + "Whether to include the OTLP gRPC exporter in the SDK" ON) + else() + option(WITH_OTLP_GRPC + "Whether to include the OTLP gRPC exporter in the SDK" OFF) + endif() + find_package(CURL) + find_package(nlohmann_json) + if(CURL_FOUND AND nlohmann_json_FOUND) + option(WITH_OTLP_HTTP + "Whether to include the OTLP http exporter in the SDK" ON) + else() + option(WITH_OTLP_HTTP + "Whether to include the OTLP http exporter in the SDK" OFF) + endif() endif() list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}") @@ -268,11 +285,11 @@ if(NOT WITH_API_ONLY) include_directories(ext/include) add_subdirectory(sdk) + add_subdirectory(ext) add_subdirectory(exporters) if(WITH_EXAMPLES) add_subdirectory(examples) endif() - add_subdirectory(ext) endif() # Add nlohmann/json submodule to include directories diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f7eb276016..9ea0a544fb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -if(WITH_OTLP) +if(WITH_OTLP_GRPC OR WITH_OTLP_HTTP) add_subdirectory(otlp) add_subdirectory(grpc) endif() diff --git a/examples/otlp/BUILD b/examples/otlp/BUILD index 29a4035697..a9b5130421 100644 --- a/examples/otlp/BUILD +++ b/examples/otlp/BUILD @@ -12,9 +12,9 @@ cc_library( ) cc_binary( - name = "example_otlp", + name = "example_otlp_grpc", srcs = [ - "main.cc", + "grpc_main.cc", ], deps = [ ":foo_library", @@ -23,3 +23,16 @@ cc_binary( "//sdk/src/trace", ], ) + +cc_binary( + name = "example_otlp_http", + srcs = [ + "http_main.cc", + ], + deps = [ + ":foo_library", + "//api", + "//exporters/otlp:otlp_http_exporter", + "//sdk/src/trace", + ], +) diff --git a/examples/otlp/CMakeLists.txt b/examples/otlp/CMakeLists.txt index 8b77baad38..f661108573 100644 --- a/examples/otlp/CMakeLists.txt +++ b/examples/otlp/CMakeLists.txt @@ -6,12 +6,21 @@ add_library(otlp_foo_library foo_library/foo_library.cc) target_link_libraries(otlp_foo_library ${CMAKE_THREAD_LIBS_INIT} ${CORE_RUNTIME_LIBS} opentelemetry_api) -add_executable(example_otlp main.cc) -target_link_libraries( - example_otlp - ${CMAKE_THREAD_LIBS_INIT} - otlp_foo_library - opentelemetry_trace - ${CORE_RUNTIME_LIBS} - opentelemetry_exporter_otprotocol - gRPC::grpc++) +if(WITH_OTLP_GRPC) + add_executable(example_otlp_grpc grpc_main.cc) + target_link_libraries( + example_otlp_grpc + ${CMAKE_THREAD_LIBS_INIT} + otlp_foo_library + opentelemetry_trace + ${CORE_RUNTIME_LIBS} + opentelemetry_exporter_otlp_grpc + gRPC::grpc++) +endif() + +if(WITH_OTLP_HTTP) + add_executable(example_otlp_http http_main.cc) + target_link_libraries( + example_otlp_http ${CMAKE_THREAD_LIBS_INIT} otlp_foo_library + opentelemetry_trace ${CORE_RUNTIME_LIBS} opentelemetry_exporter_otlp_http) +endif() diff --git a/examples/otlp/README.md b/examples/otlp/README.md index e4750ef875..7ed565f0ea 100644 --- a/examples/otlp/README.md +++ b/examples/otlp/README.md @@ -4,8 +4,9 @@ This is an example of how to use the [OpenTelemetry Protocol](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/README.md) (OTLP) exporter. -The application in `main.cc` initializes an `OtlpExporter` instance and uses it -to register a tracer provider from the [OpenTelemetry +The application in `grpc_main.cc` initializes an `OtlpExporter` instance and +the application in `http_main.cc` initializes an `OtlpHttpExporter` instance +and they register a tracer provider from the [OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-cpp). The application then calls a `foo_library` which has been instrumented using the [OpenTelemetry API](https://github.com/open-telemetry/opentelemetry-cpp/tree/main/api). @@ -42,7 +43,8 @@ docker run --rm -it -p 4317:4317 -v "%cd%/examples/otlp":/cfg otel/opentelemetry Note that the OTLP exporter connects to the Collector at `localhost:4317` by default. This can be changed with first argument from command-line, for example: -`./example_otlp gateway.docker.internal:4317`. +`./example_otlp_grpc gateway.docker.internal:4317` and +`./example_otlp_http gateway.docker.internal:4317`.. Once you have the Collector running, see [CONTRIBUTING.md](../../CONTRIBUTING.md) for instructions on building and diff --git a/examples/otlp/main.cc b/examples/otlp/grpc_main.cc similarity index 100% rename from examples/otlp/main.cc rename to examples/otlp/grpc_main.cc diff --git a/examples/otlp/http_main.cc b/examples/otlp/http_main.cc new file mode 100644 index 0000000000..772b56742e --- /dev/null +++ b/examples/otlp/http_main.cc @@ -0,0 +1,46 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_http_exporter.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +#include "foo_library/foo_library.h" + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; +namespace otlp = opentelemetry::exporter::otlp; + +namespace +{ +opentelemetry::exporter::otlp::OtlpHttpExporterOptions opts; +void InitTracer() +{ + // Create OTLP exporter instance + auto exporter = std::unique_ptr(new otlp::OtlpHttpExporter(opts)); + auto processor = std::unique_ptr( + new sdktrace::SimpleSpanProcessor(std::move(exporter))); + auto provider = + nostd::shared_ptr(new sdktrace::TracerProvider(std::move(processor))); + // Set the global trace provider + trace::Provider::SetTracerProvider(provider); +} +} // namespace + +int main(int argc, char *argv[]) +{ + if (argc > 1) + { + opts.url = argv[1]; + if (argc > 2) + { + opts.console_debug = false; + } + } + // Removing this line will leave the default noop TracerProvider in place. + InitTracer(); + + foo_library(); +} diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 731cb2335c..81ef0e0adb 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -55,6 +55,36 @@ cc_library( ], ) +cc_library( + name = "otlp_http_exporter", + srcs = [ + "src/otlp_http_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_http_exporter.h", + "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", + "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", + ], + copts = [ + "-DCURL_STATICLIB", + ], + linkopts = select({ + "//bazel:windows": [ + "-DEFAULTLIB:advapi32.lib", + "-DEFAULTLIB:crypt32.lib", + ], + "//conditions:default": [], + }), + strip_include_prefix = "include", + deps = [ + ":otlp_recordable", + "//ext/src/http/client/curl:http_client_curl", + "//sdk/src/trace", + "@com_github_opentelemetry_proto//:trace_service_proto_cc", + "@github_nlohmann_json//:json", + ], +) + cc_test( name = "otlp_recordable_test", srcs = ["test/otlp_recordable_test.cc"], diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 6c5bed5607..600b13bb49 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -1,16 +1,46 @@ -add_library(opentelemetry_exporter_otprotocol src/otlp_recordable.cc - src/otlp_exporter.cc) +add_library(opentelemetry_otlp_recordable src/otlp_recordable.cc) +set_target_properties(opentelemetry_otlp_recordable PROPERTIES EXPORT_NAME + otlp_recordable) -set_target_properties(opentelemetry_exporter_otprotocol - PROPERTIES EXPORT_NAME otlp_exporter) +target_include_directories( + opentelemetry_otlp_recordable + PUBLIC "$" + "$") +set(OPENTELEMETRY_OTLP_TARGETS opentelemetry_otlp_recordable) target_link_libraries( - opentelemetry_exporter_otprotocol - PUBLIC opentelemetry_trace opentelemetry_resources opentelemetry_proto - protobuf::libprotobuf gRPC::grpc++) + opentelemetry_otlp_recordable + PUBLIC opentelemetry_trace opentelemetry_resources opentelemetry_proto) + +if(WITH_OTLP_GRPC) + add_library(opentelemetry_exporter_otlp_grpc src/otlp_exporter.cc) + + set_target_properties(opentelemetry_exporter_otlp_grpc + PROPERTIES EXPORT_NAME otlp_exporter) + + target_link_libraries( + opentelemetry_exporter_otlp_grpc PUBLIC opentelemetry_otlp_recordable + protobuf::libprotobuf gRPC::grpc++) + + list(APPEND OPENTELEMETRY_OTLP_TARGETS opentelemetry_exporter_otlp_grpc) +endif() + +if(WITH_OTLP_HTTP) + add_library(opentelemetry_exporter_otlp_http src/otlp_http_exporter.cc) + + set_target_properties(opentelemetry_exporter_otlp_http + PROPERTIES EXPORT_NAME otlp_http_exporter) + + target_link_libraries( + opentelemetry_exporter_otlp_http + PUBLIC opentelemetry_otlp_recordable http_client_curl + nlohmann_json::nlohmann_json) + + list(APPEND OPENTELEMETRY_OTLP_TARGETS opentelemetry_exporter_otlp_http) +endif() install( - TARGETS opentelemetry_exporter_otprotocol + TARGETS ${OPENTELEMETRY_OTLP_TARGETS} EXPORT "${PROJECT_NAME}-target" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -22,15 +52,10 @@ install( FILES_MATCHING PATTERN "*.h") -target_include_directories( - opentelemetry_exporter_otprotocol - PUBLIC "$") - if(BUILD_TESTING) add_executable(otlp_recordable_test test/otlp_recordable_test.cc) - target_link_libraries( - otlp_recordable_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - opentelemetry_exporter_otprotocol) + target_link_libraries(otlp_recordable_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_otlp_recordable) gtest_add_tests( TARGET otlp_recordable_test TEST_PREFIX exporter.otlp. @@ -57,7 +82,7 @@ if(BUILD_TESTING) add_executable(otlp_exporter_test test/otlp_exporter_test.cc) target_link_libraries( otlp_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - ${GMOCK_LIB} opentelemetry_exporter_otprotocol) + ${GMOCK_LIB} opentelemetry_exporter_otlp_grpc) gtest_add_tests( TARGET otlp_exporter_test TEST_PREFIX exporter.otlp. diff --git a/exporters/otlp/README.md b/exporters/otlp/README.md index 7e9763fa81..7555a61775 100644 --- a/exporters/otlp/README.md +++ b/exporters/otlp/README.md @@ -20,7 +20,7 @@ contributions](https://github.com/open-telemetry/opentelemetry-collector-contrib The OTLP exporter offers some configuration options. To configure the exporter, create an `OtlpExporterOptions` struct (defined in -[exporter.h](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_exporter.h)), +[otlp_exporter.h](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_exporter.h)), set the options inside, and pass the struct to the `OtlpExporter` constructor, like so: @@ -30,6 +30,18 @@ options.endpoint = "localhost:12345"; auto exporter = std::unique_ptr(new otlp::OtlpExporter(options)); ``` +The OTLP HTTP exporter offers some configuration options. To configure the exporter, +create an `OtlpHttpExporterOptions` struct (defined in +[otlp_http_exporter.h](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h)), +set the options inside, and pass the struct to the `OtlpHttpExporter` constructor, +like so: + +```cpp +OtlpHttpExporterOptions options; +options.url = "localhost:12345"; +auto exporter = std::unique_ptr(new otlp::OtlpHttpExporter(options)); +``` + ### Configuration options | Option | Default | diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h new file mode 100644 index 0000000000..b0b6b5a211 --- /dev/null +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// We need include exporter.h first, which will include Windows.h with NOMINMAX on Windows +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/ext/http/client/curl/http_client_curl.h" + +#include +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +// The default URL path to post trace data. +constexpr char kDefaultTracePath[] = "/v1/traces"; +// The default URL path to post metric data. +constexpr char kDefaultMetricsPath[] = "/v1/metrics"; +// The default URL path to post metric data. +constexpr char kDefaultLogPath[] = "/v1/logs"; +// The HTTP header "Content-Type" +constexpr char kHttpContentType[] = "application/json"; + +enum class BytesMappingKind +{ + kHexId, + kBase64, + kHex, +}; + +/** + * Struct to hold OTLP exporter options. + */ +struct OtlpHttpExporterOptions +{ + // The endpoint to export to. By default + // @see + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md + // @see https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver/otlpreceiver + std::string url = std::string("http://localhost:4317") + kDefaultTracePath; + + // TODO: By default when false, set CURLOPT_SSL_VERIFYPEER to false + // bool use_ssl_credentials = false; + // If convert bytes into hex. By default, we will convert bytes into base64 + BytesMappingKind json_bytes_mapping = BytesMappingKind::kHexId; + + // If using the json name of protobuf field to set the key of json. By default, we will use the + // field name just like proto files. + bool use_json_name = false; + + // Whether to print the status of the exporter in the console + bool console_debug = false; + + // Maximum time to wait for response after sending http request(milliseconds) + int response_timeout = 30000; +}; + +/** + * The OTLP exporter exports span data in OpenTelemetry Protocol (OTLP) format. + */ +class OtlpHttpExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * Create an OtlpHttpExporter using all default options. + */ + OtlpHttpExporter(); + + /** + * Create an OtlpHttpExporter using the given options. + */ + OtlpHttpExporter(const OtlpHttpExporterOptions &options); + + /** + * Create a span recordable. + * @return a newly initialized Recordable object + */ + std::unique_ptr MakeRecordable() noexcept override; + + /** + * Export + * @param spans a span of unique pointers to span recordables + */ + sdk::common::ExportResult Export( + const nostd::span> &spans) 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: + // Stores if this exporter had its Shutdown() method called + bool is_shutdown_ = false; + + // The configuration options associated with this exporter. + const OtlpHttpExporterOptions options_; + + // Object that stores the HTTP sessions that have been created + std::unique_ptr curl_operation_; + std::mutex mutex_; +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/src/otlp_http_exporter.cc b/exporters/otlp/src/otlp_http_exporter.cc new file mode 100644 index 0000000000..69d0216551 --- /dev/null +++ b/exporters/otlp/src/otlp_http_exporter.cc @@ -0,0 +1,553 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_http_exporter.h" +#include "opentelemetry/exporters/otlp/otlp_recordable.h" + +#include "nlohmann/json.hpp" + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include "google/protobuf/message.h" +#include "google/protobuf/reflection.h" +#include "google/protobuf/stubs/stl_util.h" +#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +#include +#include +#include +#include + +#ifdef GetMessage +# undef GetMessage +#endif + +namespace nostd = opentelemetry::nostd; +namespace http_client = opentelemetry::ext::http::client; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +namespace +{ + +static inline char HexEncode(unsigned char byte) +{ + if (byte >= 10) + { + return byte - 10 + 'a'; + } + else + { + return byte + '0'; + } +} + +static std::string HexEncode(const std::string &bytes) +{ + std::string ret; + ret.reserve(bytes.size() * 2); + for (std::string::size_type i = 0; i < bytes.size(); ++i) + { + unsigned char byte = static_cast(bytes[i]); + ret.push_back(HexEncode(byte >> 4)); + ret.push_back(HexEncode(byte & 0x0f)); + } + return ret; +} + +static std::string BytesMapping(const std::string &bytes, + const google::protobuf::FieldDescriptor *field_descriptor, + BytesMappingKind kind) +{ + switch (kind) + { + case BytesMappingKind::kHexId: { + if (field_descriptor->lowercase_name() == "trace_id" || + field_descriptor->lowercase_name() == "span_id" || + field_descriptor->lowercase_name() == "parent_span_id") + { + return HexEncode(bytes); + } + else + { + std::string base64_value; + google::protobuf::Base64Escape(bytes, &base64_value); + return base64_value; + } + } + case BytesMappingKind::kBase64: { + // Base64 is the default bytes mapping of protobuf + std::string base64_value; + google::protobuf::Base64Escape(bytes, &base64_value); + return base64_value; + } + case BytesMappingKind::kHex: + return HexEncode(bytes); + default: + return bytes; + } +} + +static void ConvertGenericFieldToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const google::protobuf::FieldDescriptor *field_descriptor, + const OtlpHttpExporterOptions &options); + +static void ConvertListFieldToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const google::protobuf::FieldDescriptor *field_descriptor, + const OtlpHttpExporterOptions &options); + +static void ConvertGenericMessageToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const OtlpHttpExporterOptions &options) +{ + std::vector fields_with_data; + message.GetReflection()->ListFields(message, &fields_with_data); + for (std::size_t i = 0; i < fields_with_data.size(); ++i) + { + const google::protobuf::FieldDescriptor *field_descriptor = fields_with_data[i]; + nlohmann::json &child_value = options.use_json_name ? value[field_descriptor->json_name()] + : value[field_descriptor->name()]; + if (field_descriptor->is_repeated()) + { + ConvertListFieldToJson(child_value, message, field_descriptor, options); + } + else + { + ConvertGenericFieldToJson(child_value, message, field_descriptor, options); + } + } +} + +void ConvertGenericFieldToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const google::protobuf::FieldDescriptor *field_descriptor, + const OtlpHttpExporterOptions &options) +{ + switch (field_descriptor->cpp_type()) + { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: { + value = message.GetReflection()->GetInt32(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: { + value = message.GetReflection()->GetInt64(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: { + value = message.GetReflection()->GetUInt32(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: { + value = message.GetReflection()->GetUInt64(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: { + std::string empty; + if (field_descriptor->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) + { + value = BytesMapping( + message.GetReflection()->GetStringReference(message, field_descriptor, &empty), + field_descriptor, options.json_bytes_mapping); + } + else + { + value = message.GetReflection()->GetStringReference(message, field_descriptor, &empty); + } + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + ConvertGenericMessageToJson( + value, message.GetReflection()->GetMessage(message, field_descriptor, nullptr), options); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: { + value = message.GetReflection()->GetDouble(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: { + value = message.GetReflection()->GetFloat(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: { + value = message.GetReflection()->GetBool(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: { + value = message.GetReflection()->GetEnumValue(message, field_descriptor); + break; + } + default: { + break; + } + } +} + +void ConvertListFieldToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const google::protobuf::FieldDescriptor *field_descriptor, + const OtlpHttpExporterOptions &options) +{ + auto field_size = message.GetReflection()->FieldSize(message, field_descriptor); + + switch (field_descriptor->cpp_type()) + { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedInt32(message, field_descriptor, i)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedInt64(message, field_descriptor, i)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedUInt32(message, field_descriptor, i)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedUInt64(message, field_descriptor, i)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: { + std::string empty; + if (field_descriptor->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) + { + for (int i = 0; i < field_size; ++i) + { + value.push_back(BytesMapping(message.GetReflection()->GetRepeatedStringReference( + message, field_descriptor, i, &empty), + field_descriptor, options.json_bytes_mapping)); + } + } + else + { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedStringReference( + message, field_descriptor, i, &empty)); + } + } + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + for (int i = 0; i < field_size; ++i) + { + nlohmann::json sub_value; + ConvertGenericMessageToJson( + sub_value, message.GetReflection()->GetRepeatedMessage(message, field_descriptor, i), + options); + value.push_back(std::move(sub_value)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedDouble(message, field_descriptor, i)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedFloat(message, field_descriptor, i)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: { + for (int i = 0; i < field_size; ++i) + { + value.push_back(message.GetReflection()->GetRepeatedBool(message, field_descriptor, i)); + } + + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: { + for (int i = 0; i < field_size; ++i) + { + value.push_back( + message.GetReflection()->GetRepeatedEnumValue(message, field_descriptor, i)); + } + break; + } + default: { + break; + } + } +} + +/** + * This class handles the response message from the Elasticsearch request + */ +class ResponseHandler : public http_client::EventHandler +{ +public: + /** + * Creates a response handler, that by default doesn't display to console + */ + ResponseHandler(bool console_debug = false) : console_debug_{console_debug} {} + + /** + * Automatically called when the response is received, store the body into a string and notify any + * threads blocked on this result + */ + void OnResponse(http_client::Response &response) noexcept override + { + // Lock the private members so they can't be read while being modified + { + std::unique_lock lk(mutex_); + + if (console_debug_) + { + std::cout << "[HTTP Exporter] Status:" << response.GetStatusCode() << std::endl + << std::string(response.GetBody().begin(), response.GetBody().end()) << std::endl; + response.ForEachHeader([](opentelemetry::nostd::string_view header_name, + opentelemetry::nostd::string_view header_value) { + std::cout << "\tHeader: " << header_name.data() << " : " << header_value.data() + << std::endl; + return true; + }); + } + + // Set the response_received_ flag to true and notify any threads waiting on this result + response_received_ = true; + } + cv_.notify_all(); + } + + /** + * A method the user calls to block their thread until the response is received. The longest + * duration is the timeout of the request, set by SetTimeoutMs() + */ + bool waitForResponse() + { + std::unique_lock lk(mutex_); + cv_.wait(lk); + return response_received_; + } + + // Callback method when an http event occurs + void OnEvent(http_client::SessionState state, + opentelemetry::nostd::string_view reason) noexcept override + { + // If any failure event occurs, release the condition variable to unblock main thread + switch (state) + { + case http_client::SessionState::ConnectFailed: + if (console_debug_) + { + std::cerr << "[HTTP Exporter] Connection to http server failed." << reason << std::endl; + } + cv_.notify_all(); + break; + case http_client::SessionState::SendFailed: + if (console_debug_) + { + std::cerr << "[HTTP Exporter] Request failed to be sent to http server." << reason + << std::endl; + } + cv_.notify_all(); + break; + case http_client::SessionState::TimedOut: + if (console_debug_) + { + std::cerr << "[HTTP Exporter] Request to http server timed out." << reason << std::endl; + } + cv_.notify_all(); + break; + case http_client::SessionState::NetworkError: + if (console_debug_) + { + std::cerr << "[HTTP Exporter] Network error to http server." << reason << std::endl; + } + cv_.notify_all(); + break; + case http_client::SessionState::Cancelled: + if (console_debug_) + { + std::cerr << "[HTTP Exporter] Request cancelled." << reason << std::endl; + } + cv_.notify_all(); + break; + default: + break; + } + } + +private: + // Define a condition variable and mutex + std::condition_variable cv_; + std::mutex mutex_; + + // Whether the response from Elasticsearch has been received + bool response_received_ = false; + + // Whether to print the results from the callback + bool console_debug_ = false; +}; + +/** + * Add span protobufs contained in recordables to request. + * @param spans the spans to export + * @param request the current request + */ +static void PopulateRequest( + const nostd::span> &spans, + opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest *request) +{ + auto resource_span = request->add_resource_spans(); + auto instrumentation_lib = resource_span->add_instrumentation_library_spans(); + bool has_resource = false; + + for (auto &recordable : spans) + { + auto rec = std::unique_ptr( + static_cast(recordable.release())); + *instrumentation_lib->add_spans() = std::move(rec->span()); + + if (!has_resource) + { + *resource_span->mutable_resource() = rec->ProtoResource(); + has_resource = true; + } + } +} + +} // namespace + +OtlpHttpExporter::OtlpHttpExporter() : OtlpHttpExporter(OtlpHttpExporterOptions()) {} + +OtlpHttpExporter::OtlpHttpExporter(const OtlpHttpExporterOptions &options) : options_(options) {} + +// ----------------------------- Exporter methods ------------------------------ + +std::unique_ptr OtlpHttpExporter::MakeRecordable() noexcept +{ + return std::unique_ptr(new exporter::otlp::OtlpRecordable()); +} + +sdk::common::ExportResult OtlpHttpExporter::Export( + const nostd::span> &spans) noexcept +{ + // Return failure if this exporter has been shutdown + if (is_shutdown_) + { + if (options_.console_debug) + { + std::cerr << "[HTTP Exporter] Export failed, exporter is shutdown" << std::endl; + } + + return sdk::common::ExportResult::kFailure; + } + + nlohmann::json json_request; + proto::collector::trace::v1::ExportTraceServiceRequest service_request; + PopulateRequest(spans, &service_request); + + // Convert from proto into json object + ConvertGenericMessageToJson(json_request, service_request, options_); + + std::string post_body_json = + json_request.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); + if (options_.console_debug) + { + std::cerr << "[HTTP Exporter] Request body:\n" << post_body_json << std::endl; + } + http_client::Body body_vec(post_body_json.begin(), post_body_json.end()); + + // Send the request + std::unique_ptr handler(new ResponseHandler(options_.console_debug)); + std::unique_ptr curl_operation; + curl_operation.reset(new http_client::curl::HttpOperation( + http_client::Method::Post, options_.url, handler.get(), http_client::curl::RequestMode::Async, + {{"content-type", kHttpContentType}}, body_vec, false, + std::chrono::milliseconds(options_.response_timeout))); + + auto callback_ptr = handler.get(); + curl_operation->SendAsync([callback_ptr](http_client::curl::HttpOperation &operation) { + if (operation.WasAborted()) + { + // Manually cancelled + callback_ptr->OnEvent(http_client::SessionState::Cancelled, ""); + } + + if (operation.GetResponseCode() >= CURL_LAST) + { + // we have a http response + auto response = + std::unique_ptr(new http_client::curl::Response()); + response->headers_ = operation.GetResponseHeaders(); + response->body_ = operation.GetResponseBody(); + response->status_code_ = operation.GetResponseCode(); + callback_ptr->OnResponse(*response); + } + }); + + { + std::lock_guard lock_guard(mutex_); + curl_operation_.swap(curl_operation); + } + + bool write_successful = handler->waitForResponse(); + + { + std::lock_guard lock_guard(mutex_); + curl_operation_->Finish(); + curl_operation_.reset(); + } + + // If an error occurred with the HTTP request + if (!write_successful) + { + // TODO: retry logic + return sdk::common::ExportResult::kFailure; + } + + return sdk::common::ExportResult::kSuccess; +} + +bool OtlpHttpExporter::Shutdown(std::chrono::microseconds) noexcept +{ + is_shutdown_ = true; + + // Shutdown the curl operation + std::lock_guard lock_guard(mutex_); + if (curl_operation_) + { + curl_operation_->Abort(); + curl_operation_->Finish(); + curl_operation_.reset(); + } + + return true; +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE From ad3a6cf762a37a0961893265c79becb6ac83d6aa Mon Sep 17 00:00:00 2001 From: owent Date: Tue, 1 Jun 2021 19:36:42 +0800 Subject: [PATCH 2/7] + Rename `OtlpExporter`, `OtlpExporterOptions`, `otlp_exporter.h`, `otlp_exporter.cpp` to `OtlpGrpcExporter`, `OtlpGrpcExporterOptions`, `otlp_grpc_exporter.h`, `otlp_grpc_exporter.cpp` + Update gRPC to 1.33.2(which is the first version support bazel 4+ and the last version support gcc 4.8) Signed-off-by: owent --- CHANGELOG.md | 3 + CMakeLists.txt | 22 +-- WORKSPACE | 4 +- bazel/repository.bzl | 6 +- examples/otlp/BUILD | 2 +- examples/otlp/README.md | 8 +- examples/otlp/grpc_main.cc | 6 +- exporters/otlp/BUILD | 10 +- exporters/otlp/CMakeLists.txt | 7 +- exporters/otlp/README.md | 10 +- .../{otlp_exporter.h => otlp_grpc_exporter.h} | 18 +-- .../exporters/otlp/otlp_http_exporter.h | 6 - ...otlp_exporter.cc => otlp_grpc_exporter.cc} | 16 +- exporters/otlp/src/otlp_http_exporter.cc | 148 +++++++++++------- .../otlp/test/otlp_exporter_benchmark.cc | 6 +- exporters/otlp/test/otlp_exporter_test.cc | 15 +- opentelemetry-cpp-config.cmake.in | 6 +- 17 files changed, 165 insertions(+), 128 deletions(-) rename exporters/otlp/include/opentelemetry/exporters/otlp/{otlp_exporter.h => otlp_grpc_exporter.h} (82%) rename exporters/otlp/src/{otlp_exporter.cc => otlp_grpc_exporter.cc} (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16156529c6..1c4f68df9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [EXPORTER] Add OTLP/HTTP+JSON Protocol exporter ([#810](https://github.com/open-telemetry/opentelemetry-cpp/pull/810)) +* [EXPORTER] Rename `OtlpExporter` to `OtlpGrpcExporter`, rename `otlp_exporter.h` to `otlp_grpc_exporter.h` ([#810](https://github.com/open-telemetry/opentelemetry-cpp/pull/810)) + ## [1.0.0-rc1] 2021-06-04 * [BUILD] Enable Jaeger exporter build in Windows ([#815](https://github.com/open-telemetry/opentelemetry-cpp/pull/815)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e075e63dc..c8bce5be1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,23 +221,15 @@ if(WITH_OTLP) message("PROTOBUF_PROTOC_EXECUTABLE=${PROTOBUF_PROTOC_EXECUTABLE}") include(cmake/opentelemetry-proto.cmake) - - if(gRPC_FOUND) - option(WITH_OTLP_GRPC - "Whether to include the OTLP gRPC exporter in the SDK" ON) - else() - option(WITH_OTLP_GRPC - "Whether to include the OTLP gRPC exporter in the SDK" OFF) - endif() + include(CMakeDependentOption) find_package(CURL) find_package(nlohmann_json) - if(CURL_FOUND AND nlohmann_json_FOUND) - option(WITH_OTLP_HTTP - "Whether to include the OTLP http exporter in the SDK" ON) - else() - option(WITH_OTLP_HTTP - "Whether to include the OTLP http exporter in the SDK" OFF) - endif() + cmake_dependent_option( + WITH_OTLP_GRPC "Whether to include the OTLP gRPC exporter in the SDK" ON + "gRPC_FOUND" OFF) + cmake_dependent_option( + WITH_OTLP_HTTP "Whether to include the OTLP http exporter in the SDK" ON + "CURL_FOUND;nlohmann_json_FOUND" OFF) endif() list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}") diff --git a/WORKSPACE b/WORKSPACE index e1041d37dd..1bd7b57c39 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -29,9 +29,9 @@ load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") grpc_extra_deps() -load("@upb//bazel:repository_defs.bzl", "bazel_version_repository") +load("@upb//bazel:workspace_deps.bzl", "upb_deps") -bazel_version_repository(name = "upb_bazel_version") +upb_deps() # Load prometheus C++ dependencies. load("@com_github_jupp0r_prometheus_cpp//bazel:repositories.bzl", "prometheus_cpp_repositories") diff --git a/bazel/repository.bzl b/bazel/repository.bzl index 59ed8b1ada..9f8954ca27 100644 --- a/bazel/repository.bzl +++ b/bazel/repository.bzl @@ -32,10 +32,10 @@ def opentelemetry_cpp_deps(): maybe( http_archive, name = "com_github_grpc_grpc", - sha256 = "d6277f77e0bb922d3f6f56c0f93292bb4cfabfc3c92b31ee5ccea0e100303612", - strip_prefix = "grpc-1.28.0", + sha256 = "2060769f2d4b0d3535ba594b2ab614d7f68a492f786ab94b4318788d45e3278a", + strip_prefix = "grpc-1.33.2", urls = [ - "https://github.com/grpc/grpc/archive/v1.28.0.tar.gz", + "https://github.com/grpc/grpc/archive/v1.33.2.tar.gz", ], ) diff --git a/examples/otlp/BUILD b/examples/otlp/BUILD index a9b5130421..2892cfa86c 100644 --- a/examples/otlp/BUILD +++ b/examples/otlp/BUILD @@ -19,7 +19,7 @@ cc_binary( deps = [ ":foo_library", "//api", - "//exporters/otlp:otlp_exporter", + "//exporters/otlp:otlp_grpc_exporter", "//sdk/src/trace", ], ) diff --git a/examples/otlp/README.md b/examples/otlp/README.md index 7ed565f0ea..9aa9dfa55f 100644 --- a/examples/otlp/README.md +++ b/examples/otlp/README.md @@ -4,8 +4,8 @@ This is an example of how to use the [OpenTelemetry Protocol](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/README.md) (OTLP) exporter. -The application in `grpc_main.cc` initializes an `OtlpExporter` instance and -the application in `http_main.cc` initializes an `OtlpHttpExporter` instance +The application in `grpc_main.cc` initializes an `OtlpGrpcExporter` instance and +the application in `http_main.cc` initializes an `OtlpHttpExporter` instance and they register a tracer provider from the [OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-cpp). The application then calls a `foo_library` which has been instrumented using the [OpenTelemetry @@ -13,7 +13,7 @@ API](https://github.com/open-telemetry/opentelemetry-cpp/tree/main/api). To enable TLS authentication for OTLP grpc exporter, SslCredentials can be used by specifying the path to client certificate pem file, or the string containing -this certificate via OtlpExporterOptions. The path to such a .pem file can be +this certificate via OtlpGrpcExporterOptions. The path to such a .pem file can be provided as a command-line argument alongwith the collector endpoint to the main binary invocation above. @@ -43,7 +43,7 @@ docker run --rm -it -p 4317:4317 -v "%cd%/examples/otlp":/cfg otel/opentelemetry Note that the OTLP exporter connects to the Collector at `localhost:4317` by default. This can be changed with first argument from command-line, for example: -`./example_otlp_grpc gateway.docker.internal:4317` and +`./example_otlp_grpc gateway.docker.internal:4317` and `./example_otlp_http gateway.docker.internal:4317`.. Once you have the Collector running, see diff --git a/examples/otlp/grpc_main.cc b/examples/otlp/grpc_main.cc index 2acf2ccb8e..7df891ad0a 100644 --- a/examples/otlp/grpc_main.cc +++ b/examples/otlp/grpc_main.cc @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include "opentelemetry/exporters/otlp/otlp_exporter.h" +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/trace/provider.h" @@ -15,11 +15,11 @@ namespace otlp = opentelemetry::exporter::otlp; namespace { -opentelemetry::exporter::otlp::OtlpExporterOptions opts; +opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts; void InitTracer() { // Create OTLP exporter instance - auto exporter = std::unique_ptr(new otlp::OtlpExporter(opts)); + auto exporter = std::unique_ptr(new otlp::OtlpGrpcExporter(opts)); auto processor = std::unique_ptr( new sdktrace::SimpleSpanProcessor(std::move(exporter))); auto provider = diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 81ef0e0adb..11e6613863 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -35,12 +35,12 @@ cc_library( ) cc_library( - name = "otlp_exporter", + name = "otlp_grpc_exporter", srcs = [ - "src/otlp_exporter.cc", + "src/otlp_grpc_exporter.cc", ], hdrs = [ - "include/opentelemetry/exporters/otlp/otlp_exporter.h", + "include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h", "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", ], @@ -98,7 +98,7 @@ cc_test( name = "otlp_exporter_test", srcs = ["test/otlp_exporter_test.cc"], deps = [ - ":otlp_exporter", + ":otlp_grpc_exporter", "//api", "@com_google_googletest//:gtest_main", ], @@ -108,6 +108,6 @@ otel_cc_benchmark( name = "otlp_exporter_benchmark", srcs = ["test/otlp_exporter_benchmark.cc"], deps = [ - ":otlp_exporter", + ":otlp_grpc_exporter", ], ) diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 600b13bb49..b79c1a1ad2 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -13,10 +13,11 @@ target_link_libraries( PUBLIC opentelemetry_trace opentelemetry_resources opentelemetry_proto) if(WITH_OTLP_GRPC) - add_library(opentelemetry_exporter_otlp_grpc src/otlp_exporter.cc) + find_package(gRPC REQUIRED) + add_library(opentelemetry_exporter_otlp_grpc src/otlp_grpc_exporter.cc) set_target_properties(opentelemetry_exporter_otlp_grpc - PROPERTIES EXPORT_NAME otlp_exporter) + PROPERTIES EXPORT_NAME otlp_grpc_exporter) target_link_libraries( opentelemetry_exporter_otlp_grpc PUBLIC opentelemetry_otlp_recordable @@ -26,6 +27,8 @@ if(WITH_OTLP_GRPC) endif() if(WITH_OTLP_HTTP) + find_package(CURL REQUIRED) + find_package(nlohmann_json REQUIRED) add_library(opentelemetry_exporter_otlp_http src/otlp_http_exporter.cc) set_target_properties(opentelemetry_exporter_otlp_http diff --git a/exporters/otlp/README.md b/exporters/otlp/README.md index 7555a61775..8c6efe90d3 100644 --- a/exporters/otlp/README.md +++ b/exporters/otlp/README.md @@ -19,15 +19,15 @@ contributions](https://github.com/open-telemetry/opentelemetry-collector-contrib ## Configuration The OTLP exporter offers some configuration options. To configure the exporter, -create an `OtlpExporterOptions` struct (defined in -[otlp_exporter.h](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_exporter.h)), -set the options inside, and pass the struct to the `OtlpExporter` constructor, +create an `OtlpGrpcExporterOptions` struct (defined in +[otlp_grpc_exporter.h](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h)), +set the options inside, and pass the struct to the `OtlpGrpcExporter` constructor, like so: ```cpp -OtlpExporterOptions options; +OtlpGrpcExporterOptions options; options.endpoint = "localhost:12345"; -auto exporter = std::unique_ptr(new otlp::OtlpExporter(options)); +auto exporter = std::unique_ptr(new otlp::OtlpGrpcExporter(options)); ``` The OTLP HTTP exporter offers some configuration options. To configure the exporter, diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h similarity index 82% rename from exporters/otlp/include/opentelemetry/exporters/otlp/otlp_exporter.h rename to exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h index edf3b59003..a1b6edbe69 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h @@ -19,7 +19,7 @@ namespace otlp /** * Struct to hold OTLP exporter options. */ -struct OtlpExporterOptions +struct OtlpGrpcExporterOptions { // The endpoint to export to. By default the OpenTelemetry Collector's default endpoint. std::string endpoint = "localhost:4317"; @@ -36,18 +36,18 @@ struct OtlpExporterOptions /** * The OTLP exporter exports span data in OpenTelemetry Protocol (OTLP) format. */ -class OtlpExporter final : public opentelemetry::sdk::trace::SpanExporter +class OtlpGrpcExporter final : public opentelemetry::sdk::trace::SpanExporter { public: /** - * Create an OtlpExporter using all default options. + * Create an OtlpGrpcExporter using all default options. */ - OtlpExporter(); + OtlpGrpcExporter(); /** - * Create an OtlpExporter using the given options. + * Create an OtlpGrpcExporter using the given options. */ - OtlpExporter(const OtlpExporterOptions &options); + OtlpGrpcExporter(const OtlpGrpcExporterOptions &options); /** * Create a span recordable. @@ -76,7 +76,7 @@ class OtlpExporter final : public opentelemetry::sdk::trace::SpanExporter private: // The configuration options associated with this exporter. - const OtlpExporterOptions options_; + const OtlpGrpcExporterOptions options_; // For testing friend class OtlpExporterTestPeer; @@ -85,11 +85,11 @@ class OtlpExporter final : public opentelemetry::sdk::trace::SpanExporter std::unique_ptr trace_service_stub_; /** - * Create an OtlpExporter using the specified service stub. + * Create an OtlpGrpcExporter using the specified service stub. * Only tests can call this constructor directly. * @param stub the service stub to be used for exporting */ - OtlpExporter(std::unique_ptr stub); + OtlpGrpcExporter(std::unique_ptr stub); }; } // 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 b0b6b5a211..113b86cef5 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h @@ -6,8 +6,6 @@ // We need include exporter.h first, which will include Windows.h with NOMINMAX on Windows #include "opentelemetry/sdk/trace/exporter.h" -#include "opentelemetry/ext/http/client/curl/http_client_curl.h" - #include #include #include @@ -103,10 +101,6 @@ class OtlpHttpExporter final : public opentelemetry::sdk::trace::SpanExporter // The configuration options associated with this exporter. const OtlpHttpExporterOptions options_; - - // Object that stores the HTTP sessions that have been created - std::unique_ptr curl_operation_; - std::mutex mutex_; }; } // namespace otlp } // namespace exporter diff --git a/exporters/otlp/src/otlp_exporter.cc b/exporters/otlp/src/otlp_grpc_exporter.cc similarity index 86% rename from exporters/otlp/src/otlp_exporter.cc rename to exporters/otlp/src/otlp_grpc_exporter.cc index 995c2ba146..e0aaa0a0ab 100644 --- a/exporters/otlp/src/otlp_exporter.cc +++ b/exporters/otlp/src/otlp_grpc_exporter.cc @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include "opentelemetry/exporters/otlp/otlp_exporter.h" +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" #include "opentelemetry/exporters/otlp/otlp_recordable.h" #include @@ -54,7 +54,7 @@ static std::string get_file_contents(const char *fpath) * Create service stub to communicate with the OpenTelemetry Collector. */ std::unique_ptr MakeServiceStub( - const OtlpExporterOptions &options) + const OtlpGrpcExporterOptions &options) { std::shared_ptr channel; if (options.use_ssl_credentials) @@ -79,25 +79,25 @@ std::unique_ptr MakeServiceStub // -------------------------------- Constructors -------------------------------- -OtlpExporter::OtlpExporter() : OtlpExporter(OtlpExporterOptions()) {} +OtlpGrpcExporter::OtlpGrpcExporter() : OtlpGrpcExporter(OtlpGrpcExporterOptions()) {} -OtlpExporter::OtlpExporter(const OtlpExporterOptions &options) +OtlpGrpcExporter::OtlpGrpcExporter(const OtlpGrpcExporterOptions &options) : options_(options), trace_service_stub_(MakeServiceStub(options)) {} -OtlpExporter::OtlpExporter( +OtlpGrpcExporter::OtlpGrpcExporter( std::unique_ptr stub) - : options_(OtlpExporterOptions()), trace_service_stub_(std::move(stub)) + : options_(OtlpGrpcExporterOptions()), trace_service_stub_(std::move(stub)) {} // ----------------------------- Exporter methods ------------------------------ -std::unique_ptr OtlpExporter::MakeRecordable() noexcept +std::unique_ptr OtlpGrpcExporter::MakeRecordable() noexcept { return std::unique_ptr(new OtlpRecordable); } -sdk::common::ExportResult OtlpExporter::Export( +sdk::common::ExportResult OtlpGrpcExporter::Export( const nostd::span> &spans) noexcept { proto::collector::trace::v1::ExportTraceServiceRequest request; diff --git a/exporters/otlp/src/otlp_http_exporter.cc b/exporters/otlp/src/otlp_http_exporter.cc index 69d0216551..f80568244d 100644 --- a/exporters/otlp/src/otlp_http_exporter.cc +++ b/exporters/otlp/src/otlp_http_exporter.cc @@ -3,6 +3,7 @@ #include "opentelemetry/exporters/otlp/otlp_http_exporter.h" #include "opentelemetry/exporters/otlp/otlp_recordable.h" +#include "opentelemetry/ext/http/client/http_client_factory.h" #include "nlohmann/json.hpp" @@ -325,7 +326,7 @@ class ResponseHandler : public http_client::EventHandler if (console_debug_) { - std::cout << "[HTTP Exporter] Status:" << response.GetStatusCode() << std::endl + std::cout << "[OTLP HTTP Exporter] Status:" << response.GetStatusCode() << std::endl << std::string(response.GetBody().begin(), response.GetBody().end()) << std::endl; response.ForEachHeader([](opentelemetry::nostd::string_view header_name, opentelemetry::nostd::string_view header_value) { @@ -362,14 +363,15 @@ class ResponseHandler : public http_client::EventHandler case http_client::SessionState::ConnectFailed: if (console_debug_) { - std::cerr << "[HTTP Exporter] Connection to http server failed." << reason << std::endl; + std::cerr << "[OTLP HTTP Exporter] Connection to http server failed." << reason + << std::endl; } cv_.notify_all(); break; case http_client::SessionState::SendFailed: if (console_debug_) { - std::cerr << "[HTTP Exporter] Request failed to be sent to http server." << reason + std::cerr << "[OTLP HTTP Exporter] Request failed to be sent to http server." << reason << std::endl; } cv_.notify_all(); @@ -377,21 +379,22 @@ class ResponseHandler : public http_client::EventHandler case http_client::SessionState::TimedOut: if (console_debug_) { - std::cerr << "[HTTP Exporter] Request to http server timed out." << reason << std::endl; + std::cerr << "[OTLP HTTP Exporter] Request to http server timed out." << reason + << std::endl; } cv_.notify_all(); break; case http_client::SessionState::NetworkError: if (console_debug_) { - std::cerr << "[HTTP Exporter] Network error to http server." << reason << std::endl; + std::cerr << "[OTLP HTTP Exporter] Network error to http server." << reason << std::endl; } cv_.notify_all(); break; case http_client::SessionState::Cancelled: if (console_debug_) { - std::cerr << "[HTTP Exporter] Request cancelled." << reason << std::endl; + std::cerr << "[OTLP HTTP Exporter] Request cancelled." << reason << std::endl; } cv_.notify_all(); break; @@ -460,7 +463,7 @@ sdk::common::ExportResult OtlpHttpExporter::Export( { if (options_.console_debug) { - std::cerr << "[HTTP Exporter] Export failed, exporter is shutdown" << std::endl; + std::cerr << "[OTLP HTTP Exporter] Export failed, exporter is shutdown" << std::endl; } return sdk::common::ExportResult::kFailure; @@ -477,58 +480,104 @@ sdk::common::ExportResult OtlpHttpExporter::Export( json_request.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); if (options_.console_debug) { - std::cerr << "[HTTP Exporter] Request body:\n" << post_body_json << std::endl; + std::cout << "[OTLP HTTP Exporter] Request body:\n" << post_body_json << std::endl; } http_client::Body body_vec(post_body_json.begin(), post_body_json.end()); // Send the request - std::unique_ptr handler(new ResponseHandler(options_.console_debug)); - std::unique_ptr curl_operation; - curl_operation.reset(new http_client::curl::HttpOperation( - http_client::Method::Post, options_.url, handler.get(), http_client::curl::RequestMode::Async, - {{"content-type", kHttpContentType}}, body_vec, false, - std::chrono::milliseconds(options_.response_timeout))); - - auto callback_ptr = handler.get(); - curl_operation->SendAsync([callback_ptr](http_client::curl::HttpOperation &operation) { - if (operation.WasAborted()) - { - // Manually cancelled - callback_ptr->OnEvent(http_client::SessionState::Cancelled, ""); - } + auto client = http_client::HttpClientFactory::CreateSync(); + auto result = client->Post(options_.url, body_vec, {{"content-type", kHttpContentType}}); - if (operation.GetResponseCode() >= CURL_LAST) + // If an error occurred with the HTTP request + if (!result) + { + if (options_.console_debug) { - // we have a http response - auto response = - std::unique_ptr(new http_client::curl::Response()); - response->headers_ = operation.GetResponseHeaders(); - response->body_ = operation.GetResponseBody(); - response->status_code_ = operation.GetResponseCode(); - callback_ptr->OnResponse(*response); - } - }); + switch (result.GetSessionState()) + { + case http_client::SessionState::CreateFailed: + std::cerr << "[OTLP HTTP Exporter] session state: session create failed" << std::endl; + break; - { - std::lock_guard lock_guard(mutex_); - curl_operation_.swap(curl_operation); - } + case http_client::SessionState::Created: + std::cerr << "[OTLP HTTP Exporter] session state: session created" << std::endl; + break; - bool write_successful = handler->waitForResponse(); + case http_client::SessionState::Destroyed: + std::cerr << "[OTLP HTTP Exporter] session state: session destroyed" << std::endl; + break; - { - std::lock_guard lock_guard(mutex_); - curl_operation_->Finish(); - curl_operation_.reset(); - } + case http_client::SessionState::Connecting: + std::cerr << "[OTLP HTTP Exporter] session state: connecting to peer" << std::endl; + break; - // If an error occurred with the HTTP request - if (!write_successful) - { + case http_client::SessionState::ConnectFailed: + std::cerr << "[OTLP HTTP Exporter] session state: connection failed" << std::endl; + break; + + case http_client::SessionState::Connected: + std::cerr << "[OTLP HTTP Exporter] session state: connected" << std::endl; + break; + + case http_client::SessionState::Sending: + std::cerr << "[OTLP HTTP Exporter] session state: sending request" << std::endl; + break; + + case http_client::SessionState::SendFailed: + std::cerr << "[OTLP HTTP Exporter] session state: request send failed" << std::endl; + break; + + case http_client::SessionState::Response: + std::cerr << "[OTLP HTTP Exporter] session state: response received" << std::endl; + break; + + case http_client::SessionState::SSLHandshakeFailed: + std::cerr << "[OTLP HTTP Exporter] session state: SSL handshake failed" << std::endl; + break; + + case http_client::SessionState::TimedOut: + std::cerr << "[OTLP HTTP Exporter] session state: request time out" << std::endl; + break; + + case http_client::SessionState::NetworkError: + std::cerr << "[OTLP HTTP Exporter] session state: network error" << std::endl; + break; + + case http_client::SessionState::ReadError: + std::cerr << "[OTLP HTTP Exporter] session state: error reading response" << std::endl; + break; + + case http_client::SessionState::WriteError: + std::cerr << "[OTLP HTTP Exporter] session state: error writing request" << std::endl; + break; + + case http_client::SessionState::Cancelled: + std::cerr << "[OTLP HTTP Exporter] session state: (manually) cancelled" << std::endl; + break; + + default: + break; + } + } // TODO: retry logic return sdk::common::ExportResult::kFailure; } + if (options_.console_debug) + { + std::cout << "[OTLP HTTP Exporter] Status:" << result.GetResponse().GetStatusCode() << std::endl + << "Header:" << std::endl; + result.GetResponse().ForEachHeader([](opentelemetry::nostd::string_view header_name, + opentelemetry::nostd::string_view header_value) { + std::cout << "\t" << header_name.data() << " : " << header_value.data() << std::endl; + return true; + }); + std::cout << "Body:" << std::endl + << std::string(result.GetResponse().GetBody().begin(), + result.GetResponse().GetBody().end()) + << std::endl; + } + return sdk::common::ExportResult::kSuccess; } @@ -536,14 +585,7 @@ bool OtlpHttpExporter::Shutdown(std::chrono::microseconds) noexcept { is_shutdown_ = true; - // Shutdown the curl operation - std::lock_guard lock_guard(mutex_); - if (curl_operation_) - { - curl_operation_->Abort(); - curl_operation_->Finish(); - curl_operation_.reset(); - } + // TODO: Shutdown the curl operation return true; } diff --git a/exporters/otlp/test/otlp_exporter_benchmark.cc b/exporters/otlp/test/otlp_exporter_benchmark.cc index 6eee6dac83..41dc898f30 100644 --- a/exporters/otlp/test/otlp_exporter_benchmark.cc +++ b/exporters/otlp/test/otlp_exporter_benchmark.cc @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include "opentelemetry/exporters/otlp/otlp_exporter.h" +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" #include "opentelemetry/exporters/otlp/otlp_recordable.h" #include @@ -57,7 +57,7 @@ class FakeServiceStub : public proto::collector::trace::v1::TraceService::StubIn } }; -// OtlpExporterTestPeer is a friend class of OtlpExporter +// OtlpExporterTestPeer is a friend class of OtlpGrpcExporter class OtlpExporterTestPeer { public: @@ -67,7 +67,7 @@ class OtlpExporterTestPeer std::unique_ptr stub_interface( mock_stub); return std::unique_ptr( - new exporter::otlp::OtlpExporter(std::move(stub_interface))); + new exporter::otlp::OtlpGrpcExporter(std::move(stub_interface))); } }; diff --git a/exporters/otlp/test/otlp_exporter_test.cc b/exporters/otlp/test/otlp_exporter_test.cc index b9fe53b9ad..2283430a09 100644 --- a/exporters/otlp/test/otlp_exporter_test.cc +++ b/exporters/otlp/test/otlp_exporter_test.cc @@ -16,7 +16,7 @@ // // That is because `std::result_of` has been removed in C++20. -# include "opentelemetry/exporters/otlp/otlp_exporter.h" +# include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" # include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" @@ -45,11 +45,12 @@ class OtlpExporterTestPeer : public ::testing::Test std::unique_ptr GetExporter( std::unique_ptr &stub_interface) { - return std::unique_ptr(new OtlpExporter(std::move(stub_interface))); + return std::unique_ptr( + new OtlpGrpcExporter(std::move(stub_interface))); } // Get the options associated with the given exporter. - const OtlpExporterOptions &GetOptions(std::unique_ptr &exporter) + const OtlpGrpcExporterOptions &GetOptions(std::unique_ptr &exporter) { return exporter->options_; } @@ -111,9 +112,9 @@ TEST_F(OtlpExporterTestPeer, ExportIntegrationTest) // Test exporter configuration options TEST_F(OtlpExporterTestPeer, ConfigTest) { - OtlpExporterOptions opts; + OtlpGrpcExporterOptions opts; opts.endpoint = "localhost:45454"; - std::unique_ptr exporter(new OtlpExporter(opts)); + std::unique_ptr exporter(new OtlpGrpcExporter(opts)); EXPECT_EQ(GetOptions(exporter).endpoint, "localhost:45454"); } @@ -121,10 +122,10 @@ TEST_F(OtlpExporterTestPeer, ConfigTest) TEST_F(OtlpExporterTestPeer, ConfigSslCredentialsTest) { std::string cacert_str = "--begin and end fake cert--"; - OtlpExporterOptions opts; + OtlpGrpcExporterOptions opts; opts.use_ssl_credentials = true; opts.ssl_credentials_cacert_as_string = cacert_str; - std::unique_ptr exporter(new OtlpExporter(opts)); + std::unique_ptr exporter(new OtlpGrpcExporter(opts)); EXPECT_EQ(GetOptions(exporter).ssl_credentials_cacert_as_string, cacert_str); EXPECT_EQ(GetOptions(exporter).use_ssl_credentials, true); } diff --git a/opentelemetry-cpp-config.cmake.in b/opentelemetry-cpp-config.cmake.in index 3cd1e1c274..03951e6488 100644 --- a/opentelemetry-cpp-config.cmake.in +++ b/opentelemetry-cpp-config.cmake.in @@ -29,7 +29,8 @@ # opentelemetry-cpp::metrics - Imported target of opentelemetry-cpp::metrics # opentelemetry-cpp::logs - Imported target of opentelemetry-cpp::logs # opentelemetry-cpp::in_memory_span_exporter - Imported target of opentelemetry-cpp::in_memory_span_exporter -# opentelemetry-cpp::otlp_exporter - Imported target of opentelemetry-cpp::otlp_exporter +# opentelemetry-cpp::otlp_grpc_exporter - Imported target of opentelemetry-cpp::otlp_grpc_exporter +# opentelemetry-cpp::otlp_http_exporter - Imported target of opentelemetry-cpp::otlp_http_exporter # opentelemetry-cpp::ostream_log_exporter - Imported target of opentelemetry-cpp::ostream_log_exporter # opentelemetry-cpp::ostream_metrics_exporter - Imported target of opentelemetry-cpp::ostream_metrics_exporter # opentelemetry-cpp::ostream_span_exporter - Imported target of opentelemetry-cpp::ostream_span_exporter @@ -77,7 +78,8 @@ set(_OPENTELEMETRY_CPP_LIBRARIES_TEST_TARGETS metrics logs in_memory_span_exporter - otlp_exporter + otlp_grpc_exporter + otlp_http_exporter ostream_log_exporter ostream_metrics_exporter ostream_span_exporter From dcc66c65b500052f18098ebdc6e5f484f8c1367a Mon Sep 17 00:00:00 2001 From: owent Date: Tue, 1 Jun 2021 19:59:46 +0800 Subject: [PATCH 3/7] Remove old ResponseHandler for curl client Signed-off-by: owent --- exporters/otlp/src/otlp_http_exporter.cc | 115 +---------------------- 1 file changed, 2 insertions(+), 113 deletions(-) diff --git a/exporters/otlp/src/otlp_http_exporter.cc b/exporters/otlp/src/otlp_http_exporter.cc index f80568244d..e579dd24f5 100644 --- a/exporters/otlp/src/otlp_http_exporter.cc +++ b/exporters/otlp/src/otlp_http_exporter.cc @@ -303,118 +303,6 @@ void ConvertListFieldToJson(nlohmann::json &value, } } -/** - * This class handles the response message from the Elasticsearch request - */ -class ResponseHandler : public http_client::EventHandler -{ -public: - /** - * Creates a response handler, that by default doesn't display to console - */ - ResponseHandler(bool console_debug = false) : console_debug_{console_debug} {} - - /** - * Automatically called when the response is received, store the body into a string and notify any - * threads blocked on this result - */ - void OnResponse(http_client::Response &response) noexcept override - { - // Lock the private members so they can't be read while being modified - { - std::unique_lock lk(mutex_); - - if (console_debug_) - { - std::cout << "[OTLP HTTP Exporter] Status:" << response.GetStatusCode() << std::endl - << std::string(response.GetBody().begin(), response.GetBody().end()) << std::endl; - response.ForEachHeader([](opentelemetry::nostd::string_view header_name, - opentelemetry::nostd::string_view header_value) { - std::cout << "\tHeader: " << header_name.data() << " : " << header_value.data() - << std::endl; - return true; - }); - } - - // Set the response_received_ flag to true and notify any threads waiting on this result - response_received_ = true; - } - cv_.notify_all(); - } - - /** - * A method the user calls to block their thread until the response is received. The longest - * duration is the timeout of the request, set by SetTimeoutMs() - */ - bool waitForResponse() - { - std::unique_lock lk(mutex_); - cv_.wait(lk); - return response_received_; - } - - // Callback method when an http event occurs - void OnEvent(http_client::SessionState state, - opentelemetry::nostd::string_view reason) noexcept override - { - // If any failure event occurs, release the condition variable to unblock main thread - switch (state) - { - case http_client::SessionState::ConnectFailed: - if (console_debug_) - { - std::cerr << "[OTLP HTTP Exporter] Connection to http server failed." << reason - << std::endl; - } - cv_.notify_all(); - break; - case http_client::SessionState::SendFailed: - if (console_debug_) - { - std::cerr << "[OTLP HTTP Exporter] Request failed to be sent to http server." << reason - << std::endl; - } - cv_.notify_all(); - break; - case http_client::SessionState::TimedOut: - if (console_debug_) - { - std::cerr << "[OTLP HTTP Exporter] Request to http server timed out." << reason - << std::endl; - } - cv_.notify_all(); - break; - case http_client::SessionState::NetworkError: - if (console_debug_) - { - std::cerr << "[OTLP HTTP Exporter] Network error to http server." << reason << std::endl; - } - cv_.notify_all(); - break; - case http_client::SessionState::Cancelled: - if (console_debug_) - { - std::cerr << "[OTLP HTTP Exporter] Request cancelled." << reason << std::endl; - } - cv_.notify_all(); - break; - default: - break; - } - } - -private: - // Define a condition variable and mutex - std::condition_variable cv_; - std::mutex mutex_; - - // Whether the response from Elasticsearch has been received - bool response_received_ = false; - - // Whether to print the results from the callback - bool console_debug_ = false; -}; - /** * Add span protobufs contained in recordables to request. * @param spans the spans to export @@ -486,6 +374,7 @@ sdk::common::ExportResult OtlpHttpExporter::Export( // Send the request auto client = http_client::HttpClientFactory::CreateSync(); + // TODO: Set timeout auto result = client->Post(options_.url, body_vec, {{"content-type", kHttpContentType}}); // If an error occurred with the HTTP request @@ -585,7 +474,7 @@ bool OtlpHttpExporter::Shutdown(std::chrono::microseconds) noexcept { is_shutdown_ = true; - // TODO: Shutdown the curl operation + // TODO: Shutdown the http request return true; } From 5e502df7bddf06f0000646e0dd397d23396ca476 Mon Sep 17 00:00:00 2001 From: owent Date: Wed, 2 Jun 2021 14:12:08 +0800 Subject: [PATCH 4/7] Add test for otlp http exporter Signed-off-by: owent --- exporters/otlp/BUILD | 8 +- exporters/otlp/CMakeLists.txt | 8 +- .../exporters/otlp/otlp_grpc_exporter.h | 2 +- ...ark.cc => otlp_grpc_exporter_benchmark.cc} | 10 +- ...ter_test.cc => otlp_grpc_exporter_test.cc} | 10 +- .../otlp/test/otlp_http_exporter_test.cc | 123 ++++++++++++++++++ 6 files changed, 142 insertions(+), 19 deletions(-) rename exporters/otlp/test/{otlp_exporter_benchmark.cc => otlp_grpc_exporter_benchmark.cc} (94%) rename exporters/otlp/test/{otlp_exporter_test.cc => otlp_grpc_exporter_test.cc} (94%) create mode 100644 exporters/otlp/test/otlp_http_exporter_test.cc diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 11e6613863..66ca5e5cfc 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -95,8 +95,8 @@ cc_test( ) cc_test( - name = "otlp_exporter_test", - srcs = ["test/otlp_exporter_test.cc"], + name = "otlp_grpc_exporter_test", + srcs = ["test/otlp_grpc_exporter_test.cc"], deps = [ ":otlp_grpc_exporter", "//api", @@ -105,8 +105,8 @@ cc_test( ) otel_cc_benchmark( - name = "otlp_exporter_benchmark", - srcs = ["test/otlp_exporter_benchmark.cc"], + name = "otlp_grpc_exporter_benchmark", + srcs = ["test/otlp_grpc_exporter_benchmark.cc"], deps = [ ":otlp_grpc_exporter", ], diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index b79c1a1ad2..871880e7ee 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -82,12 +82,12 @@ if(BUILD_TESTING) else() find_library(GMOCK_LIB gmock PATH_SUFFIXES lib) endif() - add_executable(otlp_exporter_test test/otlp_exporter_test.cc) + add_executable(otlp_grpc_exporter_test test/otlp_grpc_exporter_test.cc) target_link_libraries( - otlp_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + otlp_grpc_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${GMOCK_LIB} opentelemetry_exporter_otlp_grpc) gtest_add_tests( - TARGET otlp_exporter_test + TARGET otlp_grpc_exporter_test TEST_PREFIX exporter.otlp. - TEST_LIST otlp_exporter_test) + TEST_LIST otlp_grpc_exporter_test) endif() # BUILD_TESTING diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h index a1b6edbe69..d4791bac81 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h @@ -79,7 +79,7 @@ class OtlpGrpcExporter final : public opentelemetry::sdk::trace::SpanExporter const OtlpGrpcExporterOptions options_; // For testing - friend class OtlpExporterTestPeer; + friend class OtlpGrpcExporterTestPeer; // Store service stub internally. Useful for testing. std::unique_ptr trace_service_stub_; diff --git a/exporters/otlp/test/otlp_exporter_benchmark.cc b/exporters/otlp/test/otlp_grpc_exporter_benchmark.cc similarity index 94% rename from exporters/otlp/test/otlp_exporter_benchmark.cc rename to exporters/otlp/test/otlp_grpc_exporter_benchmark.cc index 41dc898f30..e5dac38ba4 100644 --- a/exporters/otlp/test/otlp_exporter_benchmark.cc +++ b/exporters/otlp/test/otlp_grpc_exporter_benchmark.cc @@ -57,8 +57,8 @@ class FakeServiceStub : public proto::collector::trace::v1::TraceService::StubIn } }; -// OtlpExporterTestPeer is a friend class of OtlpGrpcExporter -class OtlpExporterTestPeer +// OtlpGrpcExporterTestPeer is a friend class of OtlpGrpcExporter +class OtlpGrpcExporterTestPeer { public: std::unique_ptr GetExporter() @@ -125,7 +125,7 @@ void CreateDenseSpans(std::array, kBatch // Benchmark Export() with empty spans void BM_OtlpExporterEmptySpans(benchmark::State &state) { - std::unique_ptr testpeer(new OtlpExporterTestPeer()); + std::unique_ptr testpeer(new OtlpGrpcExporterTestPeer()); auto exporter = testpeer->GetExporter(); while (state.KeepRunningBatch(kNumIterations)) @@ -140,7 +140,7 @@ BENCHMARK(BM_OtlpExporterEmptySpans); // Benchmark Export() with sparse spans void BM_OtlpExporterSparseSpans(benchmark::State &state) { - std::unique_ptr testpeer(new OtlpExporterTestPeer()); + std::unique_ptr testpeer(new OtlpGrpcExporterTestPeer()); auto exporter = testpeer->GetExporter(); while (state.KeepRunningBatch(kNumIterations)) @@ -155,7 +155,7 @@ BENCHMARK(BM_OtlpExporterSparseSpans); // Benchmark Export() with dense spans void BM_OtlpExporterDenseSpans(benchmark::State &state) { - std::unique_ptr testpeer(new OtlpExporterTestPeer()); + std::unique_ptr testpeer(new OtlpGrpcExporterTestPeer()); auto exporter = testpeer->GetExporter(); while (state.KeepRunningBatch(kNumIterations)) diff --git a/exporters/otlp/test/otlp_exporter_test.cc b/exporters/otlp/test/otlp_grpc_exporter_test.cc similarity index 94% rename from exporters/otlp/test/otlp_exporter_test.cc rename to exporters/otlp/test/otlp_grpc_exporter_test.cc index 2283430a09..f7e82e6a46 100644 --- a/exporters/otlp/test/otlp_exporter_test.cc +++ b/exporters/otlp/test/otlp_grpc_exporter_test.cc @@ -39,7 +39,7 @@ namespace exporter namespace otlp { -class OtlpExporterTestPeer : public ::testing::Test +class OtlpGrpcExporterTestPeer : public ::testing::Test { public: std::unique_ptr GetExporter( @@ -57,7 +57,7 @@ class OtlpExporterTestPeer : public ::testing::Test }; // Call Export() directly -TEST_F(OtlpExporterTestPeer, ExportUnitTest) +TEST_F(OtlpGrpcExporterTestPeer, ExportUnitTest) { auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); std::unique_ptr stub_interface( @@ -85,7 +85,7 @@ TEST_F(OtlpExporterTestPeer, ExportUnitTest) } // Create spans, let processor call Export() -TEST_F(OtlpExporterTestPeer, ExportIntegrationTest) +TEST_F(OtlpGrpcExporterTestPeer, ExportIntegrationTest) { auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); std::unique_ptr stub_interface( @@ -110,7 +110,7 @@ TEST_F(OtlpExporterTestPeer, ExportIntegrationTest) } // Test exporter configuration options -TEST_F(OtlpExporterTestPeer, ConfigTest) +TEST_F(OtlpGrpcExporterTestPeer, ConfigTest) { OtlpGrpcExporterOptions opts; opts.endpoint = "localhost:45454"; @@ -119,7 +119,7 @@ TEST_F(OtlpExporterTestPeer, ConfigTest) } // Test exporter configuration options with use_ssl_credentials -TEST_F(OtlpExporterTestPeer, ConfigSslCredentialsTest) +TEST_F(OtlpGrpcExporterTestPeer, ConfigSslCredentialsTest) { std::string cacert_str = "--begin and end fake cert--"; OtlpGrpcExporterOptions opts; diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc new file mode 100644 index 0000000000..4bcecadb2e --- /dev/null +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -0,0 +1,123 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef HAVE_CPP_STDLIB + +# include "opentelemetry/exporters/otlp/otlp_http_exporter.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +// Problematic code that pulls in Gmock and breaks with vs2019/c++latest : +# include "opentelemetry/proto/collector/trace/v1/trace_service_mock.grpc.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +# include "opentelemetry/ext/http/server/http_server.h" +# include "opentelemetry/sdk/trace/simple_processor.h" +# include "opentelemetry/sdk/trace/tracer_provider.h" +# include "opentelemetry/trace/provider.h" + +# include + +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +class OtlpHttpExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr GetExporter( + std::unique_ptr &stub_interface) + { + return std::unique_ptr( + new OtlpGrpcExporter(std::move(stub_interface))); + } + + // Get the options associated with the given exporter. + const OtlpGrpcExporterOptions &GetOptions(std::unique_ptr &exporter) + { + return exporter->options_; + } +}; + +// Call Export() directly +TEST_F(OtlpHttpExporterTestPeer, ExportUnitTest) +{ + auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); + std::unique_ptr stub_interface( + mock_stub); + auto exporter = GetExporter(stub_interface); + + auto recordable_1 = exporter->MakeRecordable(); + recordable_1->SetName("Test span 1"); + auto recordable_2 = exporter->MakeRecordable(); + recordable_2->SetName("Test span 2"); + + // Test successful RPC + nostd::span> batch_1(&recordable_1, 1); + EXPECT_CALL(*mock_stub, Export(_, _, _)).Times(Exactly(1)).WillOnce(Return(grpc::Status::OK)); + auto result = exporter->Export(batch_1); + EXPECT_EQ(sdk::common::ExportResult::kSuccess, result); + + // Test failed RPC + nostd::span> batch_2(&recordable_2, 1); + EXPECT_CALL(*mock_stub, Export(_, _, _)) + .Times(Exactly(1)) + .WillOnce(Return(grpc::Status::CANCELLED)); + result = exporter->Export(batch_2); + EXPECT_EQ(sdk::common::ExportResult::kFailure, result); +} + +// Create spans, let processor call Export() +TEST_F(OtlpHttpExporterTestPeer, ExportIntegrationTest) +{ + auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); + std::unique_ptr stub_interface( + mock_stub); + + auto exporter = GetExporter(stub_interface); + + auto processor = std::unique_ptr( + new sdk::trace::SimpleSpanProcessor(std::move(exporter))); + auto provider = nostd::shared_ptr( + new sdk::trace::TracerProvider(std::move(processor))); + auto tracer = provider->GetTracer("test"); + + EXPECT_CALL(*mock_stub, Export(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(grpc::Status::OK)); + + auto parent_span = tracer->StartSpan("Test parent span"); + auto child_span = tracer->StartSpan("Test child span"); + child_span->End(); + parent_span->End(); +} + +// Test exporter configuration options +TEST_F(OtlpHttpExporterTestPeer, ConfigTest) +{ + OtlpGrpcExporterOptions opts; + opts.endpoint = "localhost:45454"; + std::unique_ptr exporter(new OtlpGrpcExporter(opts)); + EXPECT_EQ(GetOptions(exporter).endpoint, "localhost:45454"); +} + +// Test exporter configuration options with use_ssl_credentials +TEST_F(OtlpHttpExporterTestPeer, ConfigSslCredentialsTest) +{ + std::string cacert_str = "--begin and end fake cert--"; + OtlpGrpcExporterOptions opts; + opts.use_ssl_credentials = true; + opts.ssl_credentials_cacert_as_string = cacert_str; + std::unique_ptr exporter(new OtlpGrpcExporter(opts)); + EXPECT_EQ(GetOptions(exporter).ssl_credentials_cacert_as_string, cacert_str); + EXPECT_EQ(GetOptions(exporter).use_ssl_credentials, true); +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif From 2cdf4380ad0b546363e2c3f56f9c1e815957e906 Mon Sep 17 00:00:00 2001 From: owent Date: Fri, 4 Jun 2021 23:28:10 +0800 Subject: [PATCH 5/7] Add support of `application/x-protobuf` for OtlpHttpExporter. Signed-off-by: owent --- bazel/repository.bzl | 10 + examples/otlp/README.md | 2 +- exporters/otlp/BUILD | 10 + exporters/otlp/CMakeLists.txt | 8 + .../exporters/otlp/otlp_grpc_exporter.h | 2 +- .../exporters/otlp/otlp_http_exporter.h | 27 ++- exporters/otlp/src/otlp_http_exporter.cc | 76 ++++-- .../otlp/test/otlp_http_exporter_test.cc | 226 +++++++++++++++--- 8 files changed, 303 insertions(+), 58 deletions(-) diff --git a/bazel/repository.bzl b/bazel/repository.bzl index 9f8954ca27..af35dece2d 100644 --- a/bazel/repository.bzl +++ b/bazel/repository.bzl @@ -29,6 +29,16 @@ def opentelemetry_cpp_deps(): ) # Load gRPC dependency + maybe( + http_archive, + name = "com_github_grpc_grpc_legacy", + sha256 = "2060769f2d4b0d3535ba594b2ab614d7f68a492f786ab94b4318788d45e3278a", + strip_prefix = "grpc-1.33.2", + urls = [ + "https://github.com/grpc/grpc/archive/v1.33.2.tar.gz", + ], + ) + maybe( http_archive, name = "com_github_grpc_grpc", diff --git a/examples/otlp/README.md b/examples/otlp/README.md index 9aa9dfa55f..86901411f3 100644 --- a/examples/otlp/README.md +++ b/examples/otlp/README.md @@ -44,7 +44,7 @@ docker run --rm -it -p 4317:4317 -v "%cd%/examples/otlp":/cfg otel/opentelemetry Note that the OTLP exporter connects to the Collector at `localhost:4317` by default. This can be changed with first argument from command-line, for example: `./example_otlp_grpc gateway.docker.internal:4317` and -`./example_otlp_http gateway.docker.internal:4317`.. +`./example_otlp_http gateway.docker.internal:4317`. Once you have the Collector running, see [CONTRIBUTING.md](../../CONTRIBUTING.md) for instructions on building and diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 66ca5e5cfc..134950b0f0 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -104,6 +104,16 @@ cc_test( ], ) +cc_test( + name = "otlp_http_exporter_test", + srcs = ["test/otlp_http_exporter_test.cc"], + deps = [ + ":otlp_http_exporter", + "//api", + "@com_google_googletest//:gtest_main", + ], +) + otel_cc_benchmark( name = "otlp_grpc_exporter_benchmark", srcs = ["test/otlp_grpc_exporter_benchmark.cc"], diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 871880e7ee..63b0e652db 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -90,4 +90,12 @@ if(BUILD_TESTING) TARGET otlp_grpc_exporter_test TEST_PREFIX exporter.otlp. TEST_LIST otlp_grpc_exporter_test) + 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) + gtest_add_tests( + TARGET otlp_http_exporter_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_http_exporter_test) endif() # BUILD_TESTING diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h index d4791bac81..b3df30d259 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h @@ -47,7 +47,7 @@ class OtlpGrpcExporter final : public opentelemetry::sdk::trace::SpanExporter /** * Create an OtlpGrpcExporter using the given options. */ - OtlpGrpcExporter(const OtlpGrpcExporterOptions &options); + explicit OtlpGrpcExporter(const OtlpGrpcExporterOptions &options); /** * Create a span recordable. 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 113b86cef5..55322589f9 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h @@ -22,13 +22,20 @@ constexpr char kDefaultMetricsPath[] = "/v1/metrics"; // The default URL path to post metric data. constexpr char kDefaultLogPath[] = "/v1/logs"; // The HTTP header "Content-Type" -constexpr char kHttpContentType[] = "application/json"; +constexpr char kHttpJsonContentType[] = "application/json"; +constexpr char kHttpBinaryContentType[] = "application/x-protobuf"; -enum class BytesMappingKind +enum class JsonBytesMappingKind { kHexId, - kBase64, kHex, + kBase64, +}; + +enum class HttpRequestContentType +{ + kJson, + kBinary, }; /** @@ -42,10 +49,13 @@ struct OtlpHttpExporterOptions // @see https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver/otlpreceiver std::string url = std::string("http://localhost:4317") + kDefaultTracePath; + // By default, post json data + HttpRequestContentType content_type = HttpRequestContentType::kJson; + // TODO: By default when false, set CURLOPT_SSL_VERIFYPEER to false - // bool use_ssl_credentials = false; - // If convert bytes into hex. By default, we will convert bytes into base64 - BytesMappingKind json_bytes_mapping = BytesMappingKind::kHexId; + // If convert bytes into hex. By default, we will convert all bytes but id into base64 + // This option is ignored if content_type is not kJson + JsonBytesMappingKind json_bytes_mapping = JsonBytesMappingKind::kHexId; // If using the json name of protobuf field to set the key of json. By default, we will use the // field name just like proto files. @@ -72,7 +82,7 @@ class OtlpHttpExporter final : public opentelemetry::sdk::trace::SpanExporter /** * Create an OtlpHttpExporter using the given options. */ - OtlpHttpExporter(const OtlpHttpExporterOptions &options); + explicit OtlpHttpExporter(const OtlpHttpExporterOptions &options); /** * Create a span recordable. @@ -99,6 +109,9 @@ class OtlpHttpExporter final : public opentelemetry::sdk::trace::SpanExporter // Stores if this exporter had its Shutdown() method called bool is_shutdown_ = false; + // For testing + friend class OtlpHttpExporterTestPeer; + // The configuration options associated with this exporter. const OtlpHttpExporterOptions options_; }; diff --git a/exporters/otlp/src/otlp_http_exporter.cc b/exporters/otlp/src/otlp_http_exporter.cc index e579dd24f5..7d13a3a7a7 100644 --- a/exporters/otlp/src/otlp_http_exporter.cc +++ b/exporters/otlp/src/otlp_http_exporter.cc @@ -64,11 +64,11 @@ static std::string HexEncode(const std::string &bytes) static std::string BytesMapping(const std::string &bytes, const google::protobuf::FieldDescriptor *field_descriptor, - BytesMappingKind kind) + JsonBytesMappingKind kind) { switch (kind) { - case BytesMappingKind::kHexId: { + case JsonBytesMappingKind::kHexId: { if (field_descriptor->lowercase_name() == "trace_id" || field_descriptor->lowercase_name() == "span_id" || field_descriptor->lowercase_name() == "parent_span_id") @@ -82,13 +82,13 @@ static std::string BytesMapping(const std::string &bytes, return base64_value; } } - case BytesMappingKind::kBase64: { + case JsonBytesMappingKind::kBase64: { // Base64 is the default bytes mapping of protobuf std::string base64_value; google::protobuf::Base64Escape(bytes, &base64_value); return base64_value; } - case BytesMappingKind::kHex: + case JsonBytesMappingKind::kHex: return HexEncode(bytes); default: return bytes; @@ -139,7 +139,9 @@ void ConvertGenericFieldToJson(nlohmann::json &value, break; } case google::protobuf::FieldDescriptor::CPPTYPE_INT64: { - value = message.GetReflection()->GetInt64(message, field_descriptor); + // According to Protobuf specs 64-bit integer numbers in JSON-encoded payloads are encoded as + // decimal strings, and either numbers or strings are accepted when decoding. + value = std::to_string(message.GetReflection()->GetInt64(message, field_descriptor)); break; } case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: { @@ -147,7 +149,9 @@ void ConvertGenericFieldToJson(nlohmann::json &value, break; } case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: { - value = message.GetReflection()->GetUInt64(message, field_descriptor); + // According to Protobuf specs 64-bit integer numbers in JSON-encoded payloads are encoded as + // decimal strings, and either numbers or strings are accepted when decoding. + value = std::to_string(message.GetReflection()->GetUInt64(message, field_descriptor)); break; } case google::protobuf::FieldDescriptor::CPPTYPE_STRING: { @@ -211,7 +215,10 @@ void ConvertListFieldToJson(nlohmann::json &value, case google::protobuf::FieldDescriptor::CPPTYPE_INT64: { for (int i = 0; i < field_size; ++i) { - value.push_back(message.GetReflection()->GetRepeatedInt64(message, field_descriptor, i)); + // According to Protobuf specs 64-bit integer numbers in JSON-encoded payloads are encoded + // as decimal strings, and either numbers or strings are accepted when decoding. + value.push_back(std::to_string( + message.GetReflection()->GetRepeatedInt64(message, field_descriptor, i))); } break; @@ -227,7 +234,10 @@ void ConvertListFieldToJson(nlohmann::json &value, case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: { for (int i = 0; i < field_size; ++i) { - value.push_back(message.GetReflection()->GetRepeatedUInt64(message, field_descriptor, i)); + // According to Protobuf specs 64-bit integer numbers in JSON-encoded payloads are encoded + // as decimal strings, and either numbers or strings are accepted when decoding. + value.push_back(std::to_string( + message.GetReflection()->GetRepeatedUInt64(message, field_descriptor, i))); } break; @@ -357,25 +367,55 @@ sdk::common::ExportResult OtlpHttpExporter::Export( return sdk::common::ExportResult::kFailure; } - nlohmann::json json_request; proto::collector::trace::v1::ExportTraceServiceRequest service_request; PopulateRequest(spans, &service_request); - // Convert from proto into json object - ConvertGenericMessageToJson(json_request, service_request, options_); - - std::string post_body_json = - json_request.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); - if (options_.console_debug) + http_client::Body body_vec; + std::string content_type; + if (options_.content_type == HttpRequestContentType::kBinary) { - std::cout << "[OTLP HTTP Exporter] Request body:\n" << post_body_json << std::endl; + body_vec.resize(service_request.ByteSizeLong()); + if (service_request.SerializeWithCachedSizesToArray( + reinterpret_cast(&body_vec[0]))) + { + if (options_.console_debug) + { + std::cout << "[OTLP HTTP Exporter] Request body(Binary):\n" + << service_request.Utf8DebugString() << std::endl; + } + } + else + { + if (options_.console_debug) + { + std::cout << "[OTLP HTTP Exporter] Serialize body failed(Binary):" + << service_request.InitializationErrorString() << std::endl; + } + return sdk::common::ExportResult::kFailure; + } + content_type = kHttpBinaryContentType; + } + else + { + nlohmann::json json_request; + + // Convert from proto into json object + ConvertGenericMessageToJson(json_request, service_request, options_); + + std::string post_body_json = + json_request.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); + if (options_.console_debug) + { + std::cout << "[OTLP HTTP Exporter] Request body(Json):\n" << post_body_json << std::endl; + } + body_vec.assign(post_body_json.begin(), post_body_json.end()); + content_type = kHttpJsonContentType; } - http_client::Body body_vec(post_body_json.begin(), post_body_json.end()); // Send the request auto client = http_client::HttpClientFactory::CreateSync(); // TODO: Set timeout - auto result = client->Post(options_.url, body_vec, {{"content-type", kHttpContentType}}); + auto result = client->Post(options_.url, body_vec, {{"Content-Type", content_type}}); // If an error occurred with the HTTP request if (!result) diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc index 4bcecadb2e..375abf7794 100644 --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -7,8 +7,7 @@ # include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" -// Problematic code that pulls in Gmock and breaks with vs2019/c++latest : -# include "opentelemetry/proto/collector/trace/v1/trace_service_mock.grpc.pb.h" +# include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" # include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" @@ -19,26 +18,136 @@ # include +# include "nlohmann/json.hpp" + using namespace testing; +namespace http_client = opentelemetry::ext::http::client; + OPENTELEMETRY_BEGIN_NAMESPACE namespace exporter { namespace otlp { -class OtlpHttpExporterTestPeer : public ::testing::Test +class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS::HttpRequestCallback { +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_; + public: - std::unique_ptr GetExporter( - std::unique_ptr &stub_interface) + OtlpHttpExporterTestPeer() : is_setup_(false), is_running_(false){}; + + virtual void SetUp() override { - return std::unique_ptr( - new OtlpGrpcExporter(std::move(stub_interface))); + if (is_setup_.exchange(true)) + { + return; + } + int port = server_.addListeningPort(14371); + std::ostringstream os; + os << "localhost:" << port; + server_address_ = "http://" + os.str() + kDefaultTracePath; + server_.setServerName(os.str()); + server_.setKeepalive(false); + server_.addHandler(kDefaultTracePath, *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 + { + if (request.uri == kDefaultTracePath) + { + response.headers["Content-Type"] = "application/json"; + std::unique_lock lk(mtx_requests); + if (request.headers["Content-Type"] == kHttpBinaryContentType) + { + opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request_body; + if (request_body.ParseFromArray(&request.content[0], 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\"}"; + return 400; + } + } + else if (request.headers["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\"}"; + return 400; + } + else + { + received_requests_json_.push_back(json); + response.body = "{\"code\": 0, \"message\": \"success\"}"; + } + } + else + { + response.body = "{\"code\": 400, \"message\": \"Unsupported content type\"}"; + return 400; + } + + return 200; + } + else + { + std::unique_lock lk(mtx_requests); + response.headers["Content-Type"] = "text/plain"; + response.body = "404 Not Found"; + return 200; + } + } + + bool waitForRequests(unsigned timeOutSec, unsigned expected_count = 1) + { + std::unique_lock lk(mtx_requests); + if (cv_got_events.wait_for(lk, std::chrono::milliseconds(1000 * timeOutSec), [&] { + return received_requests_json_.size() + received_requests_binary_.size() >= + expected_count; + })) + { + return true; + } + return false; + } + +public: + std::unique_ptr GetExporter(HttpRequestContentType content_type) + { + OtlpHttpExporterOptions opts; + opts.url = server_address_; + opts.content_type = content_type; + return std::unique_ptr(new OtlpHttpExporter(opts)); } // Get the options associated with the given exporter. - const OtlpGrpcExporterOptions &GetOptions(std::unique_ptr &exporter) + const OtlpHttpExporterOptions &GetOptions(std::unique_ptr &exporter) { return exporter->options_; } @@ -47,10 +156,7 @@ class OtlpHttpExporterTestPeer : public ::testing::Test // Call Export() directly TEST_F(OtlpHttpExporterTestPeer, ExportUnitTest) { - auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); - std::unique_ptr stub_interface( - mock_stub); - auto exporter = GetExporter(stub_interface); + auto exporter = GetExporter(HttpRequestContentType::kJson); auto recordable_1 = exporter->MakeRecordable(); recordable_1->SetName("Test span 1"); @@ -73,18 +179,69 @@ TEST_F(OtlpHttpExporterTestPeer, ExportUnitTest) } // Create spans, let processor call Export() -TEST_F(OtlpHttpExporterTestPeer, ExportIntegrationTest) +TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTest) { - auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); - std::unique_ptr stub_interface( - mock_stub); + auto exporter = GetExporter(HttpRequestContentType::kJson); - auto exporter = GetExporter(stub_interface); + opentelemetry::sdk::resource::ResourceAttributes resource_attributes = { + {"service.name", 'unit_test_service'}, {"tenant.id", 'test_user'}}; + resource_attributes["bool_value"] = true; + resource_attributes["int32_value"] = static_cast(1); + resource_attributes["uint32_value"] = static_cast(2); + resource_attributes["int64_value"] = static_cast(0x1100000000LL); + resource_attributes["uint64_value"] = static_cast(0x1200000000ULL); + resource_attributes["double_value"] = static_cast(3.1); + resource_attributes["vec_bool_value"] = std::vector{true, false, true}; + resource_attributes["vec_int32_value"] = std::vector{1, 2}; + resource_attributes["vec_uint32_value"] = std::vector{3, 4}; + resource_attributes["vec_int64_value"] = std::vector{5, 6}; + resource_attributes["vec_uint64_value"] = std::vector{7, 8}; + resource_attributes["vec_double_value"] = std::vector{3.2, 3.3}; + resource_attributes["vec_string_value"] = std::vector{"vector", "string"}; + auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); auto processor = std::unique_ptr( new sdk::trace::SimpleSpanProcessor(std::move(exporter))); auto provider = nostd::shared_ptr( - new sdk::trace::TracerProvider(std::move(processor))); + new sdk::trace::TracerProvider(std::move(processor), resource)); + auto tracer = provider->GetTracer("test"); + + EXPECT_CALL(*mock_stub, Export(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(grpc::Status::OK)); + + auto parent_span = tracer->StartSpan("Test parent span"); + auto child_span = tracer->StartSpan("Test child span"); + child_span->End(); + parent_span->End(); +} + +// Create spans, let processor call Export() +TEST_F(OtlpHttpExporterTestPeer, ExportBinaryIntegrationTest) +{ + auto exporter = GetExporter(HttpRequestContentType::kBinary); + + opentelemetry::sdk::resource::ResourceAttributes resource_attributes = { + {"service.name", 'unit_test_service'}, {"tenant.id", 'test_user'}}; + resource_attributes["bool_value"] = true; + resource_attributes["int32_value"] = static_cast(1); + resource_attributes["uint32_value"] = static_cast(2); + resource_attributes["int64_value"] = static_cast(0x1100000000LL); + resource_attributes["uint64_value"] = static_cast(0x1200000000ULL); + resource_attributes["double_value"] = static_cast(3.1); + resource_attributes["vec_bool_value"] = std::vector{true, false, true}; + resource_attributes["vec_int32_value"] = std::vector{1, 2}; + resource_attributes["vec_uint32_value"] = std::vector{3, 4}; + resource_attributes["vec_int64_value"] = std::vector{5, 6}; + resource_attributes["vec_uint64_value"] = std::vector{7, 8}; + resource_attributes["vec_double_value"] = std::vector{3.2, 3.3}; + resource_attributes["vec_string_value"] = std::vector{"vector", "string"}; + auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); + + auto processor = std::unique_ptr( + new sdk::trace::SimpleSpanProcessor(std::move(exporter))); + auto provider = nostd::shared_ptr( + new sdk::trace::TracerProvider(std::move(processor), resource)); auto tracer = provider->GetTracer("test"); EXPECT_CALL(*mock_stub, Export(_, _, _)) @@ -100,23 +257,30 @@ TEST_F(OtlpHttpExporterTestPeer, ExportIntegrationTest) // Test exporter configuration options TEST_F(OtlpHttpExporterTestPeer, ConfigTest) { - OtlpGrpcExporterOptions opts; - opts.endpoint = "localhost:45454"; - std::unique_ptr exporter(new OtlpGrpcExporter(opts)); - EXPECT_EQ(GetOptions(exporter).endpoint, "localhost:45454"); + OtlpHttpExporterOptions opts; + opts.url = "http://localhost:45455/v1/traces"; + std::unique_ptr exporter(new OtlpHttpExporter(opts)); + EXPECT_EQ(GetOptions(exporter).url, "http://localhost:45455/v1/traces"); } -// Test exporter configuration options with use_ssl_credentials -TEST_F(OtlpHttpExporterTestPeer, ConfigSslCredentialsTest) +// Test exporter configuration options with use_json_name +TEST_F(OtlpHttpExporterTestPeer, ConfigUseJsonNameTest) { - std::string cacert_str = "--begin and end fake cert--"; - OtlpGrpcExporterOptions opts; - opts.use_ssl_credentials = true; - opts.ssl_credentials_cacert_as_string = cacert_str; - std::unique_ptr exporter(new OtlpGrpcExporter(opts)); - EXPECT_EQ(GetOptions(exporter).ssl_credentials_cacert_as_string, cacert_str); - EXPECT_EQ(GetOptions(exporter).use_ssl_credentials, true); + OtlpHttpExporterOptions opts; + opts.use_json_name = true; + std::unique_ptr exporter(new OtlpHttpExporter(opts)); + EXPECT_EQ(GetOptions(exporter).use_json_name, true); } + +// Test exporter configuration options with json_bytes_mapping=JsonBytesMappingKind::kHex +TEST_F(OtlpHttpExporterTestPeer, ConfigUseJsonNameTest) +{ + OtlpHttpExporterOptions opts; + opts.json_bytes_mapping = JsonBytesMappingKind::kHex; + std::unique_ptr exporter(new OtlpHttpExporter(opts)); + EXPECT_EQ(GetOptions(exporter).json_bytes_mapping, JsonBytesMappingKind::kHex); +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE From d4d3459a7931f75a61f98bb944e9ac2e93f8ce99 Mon Sep 17 00:00:00 2001 From: owent Date: Sat, 5 Jun 2021 12:39:01 +0800 Subject: [PATCH 6/7] Finish tests for `OtlpHttpExporter` Signed-off-by: owent --- .../exporters/otlp/otlp_http_exporter.h | 5 +- .../otlp/test/otlp_http_exporter_test.cc | 157 +++++++++++------- 2 files changed, 95 insertions(+), 67 deletions(-) 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 55322589f9..7aa73f9290 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h @@ -52,7 +52,6 @@ struct OtlpHttpExporterOptions // By default, post json data HttpRequestContentType content_type = HttpRequestContentType::kJson; - // TODO: By default when false, set CURLOPT_SSL_VERIFYPEER to false // If convert bytes into hex. By default, we will convert all bytes but id into base64 // This option is ignored if content_type is not kJson JsonBytesMappingKind json_bytes_mapping = JsonBytesMappingKind::kHexId; @@ -64,8 +63,8 @@ struct OtlpHttpExporterOptions // Whether to print the status of the exporter in the console bool console_debug = false; - // Maximum time to wait for response after sending http request(milliseconds) - int response_timeout = 30000; + // TODO: Enable/disable to verify SSL certificate + // TODO: Reuqest timeout }; /** diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc index 375abf7794..fe74829a81 100644 --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -12,7 +12,7 @@ # include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" # include "opentelemetry/ext/http/server/http_server.h" -# include "opentelemetry/sdk/trace/simple_processor.h" +# include "opentelemetry/sdk/trace/batch_span_processor.h" # include "opentelemetry/sdk/trace/tracer_provider.h" # include "opentelemetry/trace/provider.h" @@ -22,14 +22,18 @@ using namespace testing; -namespace http_client = opentelemetry::ext::http::client; - OPENTELEMETRY_BEGIN_NAMESPACE namespace exporter { namespace otlp { +template +static nostd::span MakeSpan(T (&array)[N]) +{ + return nostd::span(array); +} + class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS::HttpRequestCallback { protected: @@ -74,14 +78,24 @@ class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS:: 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; + } + } + if (request.uri == kDefaultTracePath) { response.headers["Content-Type"] = "application/json"; std::unique_lock lk(mtx_requests); - if (request.headers["Content-Type"] == kHttpBinaryContentType) + if (nullptr != request_content_type && *request_content_type == kHttpBinaryContentType) { opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request_body; - if (request_body.ParseFromArray(&request.content[0], request.content.size())) + 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\"}"; @@ -92,7 +106,7 @@ class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS:: return 400; } } - else if (request.headers["Content-Type"] == kHttpJsonContentType) + 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"; @@ -124,25 +138,29 @@ class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS:: } } - bool waitForRequests(unsigned timeOutSec, unsigned expected_count = 1) + 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 received_requests_json_.size() + received_requests_binary_.size() >= - expected_count; - })) + 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(); + } + public: std::unique_ptr GetExporter(HttpRequestContentType content_type) { OtlpHttpExporterOptions opts; - opts.url = server_address_; - opts.content_type = content_type; + opts.url = server_address_; + opts.content_type = content_type; + opts.console_debug = true; return std::unique_ptr(new OtlpHttpExporter(opts)); } @@ -153,38 +171,14 @@ class OtlpHttpExporterTestPeer : public ::testing::Test, public HTTP_SERVER_NS:: } }; -// Call Export() directly -TEST_F(OtlpHttpExporterTestPeer, ExportUnitTest) -{ - auto exporter = GetExporter(HttpRequestContentType::kJson); - - auto recordable_1 = exporter->MakeRecordable(); - recordable_1->SetName("Test span 1"); - auto recordable_2 = exporter->MakeRecordable(); - recordable_2->SetName("Test span 2"); - - // Test successful RPC - nostd::span> batch_1(&recordable_1, 1); - EXPECT_CALL(*mock_stub, Export(_, _, _)).Times(Exactly(1)).WillOnce(Return(grpc::Status::OK)); - auto result = exporter->Export(batch_1); - EXPECT_EQ(sdk::common::ExportResult::kSuccess, result); - - // Test failed RPC - nostd::span> batch_2(&recordable_2, 1); - EXPECT_CALL(*mock_stub, Export(_, _, _)) - .Times(Exactly(1)) - .WillOnce(Return(grpc::Status::CANCELLED)); - result = exporter->Export(batch_2); - EXPECT_EQ(sdk::common::ExportResult::kFailure, result); -} - // Create spans, let processor call Export() TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTest) { - auto exporter = GetExporter(HttpRequestContentType::kJson); + size_t old_count = getCurrentRequestCount(); + auto exporter = GetExporter(HttpRequestContentType::kJson); opentelemetry::sdk::resource::ResourceAttributes resource_attributes = { - {"service.name", 'unit_test_service'}, {"tenant.id", 'test_user'}}; + {"service.name", "unit_test_service"}, {"tenant.id", "test_user"}}; resource_attributes["bool_value"] = true; resource_attributes["int32_value"] = static_cast(1); resource_attributes["uint32_value"] = static_cast(2); @@ -200,29 +194,47 @@ TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTest) resource_attributes["vec_string_value"] = std::vector{"vector", "string"}; auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); - auto processor = std::unique_ptr( - new sdk::trace::SimpleSpanProcessor(std::move(exporter))); - auto provider = nostd::shared_ptr( + auto processor = std::unique_ptr(new sdk::trace::BatchSpanProcessor( + std::move(exporter), + sdk::trace::BatchSpanProcessorOptions{5, std::chrono::milliseconds(256), 5})); + auto provider = nostd::shared_ptr( new sdk::trace::TracerProvider(std::move(processor), resource)); - auto tracer = provider->GetTracer("test"); - EXPECT_CALL(*mock_stub, Export(_, _, _)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(grpc::Status::OK)); + std::string report_trace_id; + { + char trace_id_hex[2 * opentelemetry::trace::TraceId::kSize] = {0}; + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); + + opentelemetry::trace::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(); - auto parent_span = tracer->StartSpan("Test parent span"); - auto child_span = tracer->StartSpan("Test child span"); - child_span->End(); - parent_span->End(); + child_span_opts.parent.trace_id().ToLowerBase16(MakeSpan(trace_id_hex)); + report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); + } + + ASSERT_TRUE(waitForRequests(2, 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); } // Create spans, let processor call Export() TEST_F(OtlpHttpExporterTestPeer, ExportBinaryIntegrationTest) { + size_t old_count = getCurrentRequestCount(); + auto exporter = GetExporter(HttpRequestContentType::kBinary); opentelemetry::sdk::resource::ResourceAttributes resource_attributes = { - {"service.name", 'unit_test_service'}, {"tenant.id", 'test_user'}}; + {"service.name", "unit_test_service"}, {"tenant.id", "test_user"}}; resource_attributes["bool_value"] = true; resource_attributes["int32_value"] = static_cast(1); resource_attributes["uint32_value"] = static_cast(2); @@ -238,20 +250,37 @@ TEST_F(OtlpHttpExporterTestPeer, ExportBinaryIntegrationTest) resource_attributes["vec_string_value"] = std::vector{"vector", "string"}; auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); - auto processor = std::unique_ptr( - new sdk::trace::SimpleSpanProcessor(std::move(exporter))); - auto provider = nostd::shared_ptr( + auto processor = std::unique_ptr(new sdk::trace::BatchSpanProcessor( + std::move(exporter), + sdk::trace::BatchSpanProcessorOptions{5, std::chrono::milliseconds(256), 5})); + auto provider = nostd::shared_ptr( new sdk::trace::TracerProvider(std::move(processor), resource)); - auto tracer = provider->GetTracer("test"); - EXPECT_CALL(*mock_stub, Export(_, _, _)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(grpc::Status::OK)); + std::string report_trace_id; + { + uint8_t trace_id_binary[opentelemetry::trace::TraceId::kSize] = {0}; + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); + + opentelemetry::trace::StartSpanOptions child_span_opts = {}; + child_span_opts.parent = parent_span->GetContext(); - auto parent_span = tracer->StartSpan("Test parent span"); - auto child_span = tracer->StartSpan("Test child span"); - child_span->End(); - parent_span->End(); + auto child_span = tracer->StartSpan("Test child span", child_span_opts); + child_span->End(); + parent_span->End(); + + 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(2, old_count + 1)); + + 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); } // Test exporter configuration options @@ -273,7 +302,7 @@ TEST_F(OtlpHttpExporterTestPeer, ConfigUseJsonNameTest) } // Test exporter configuration options with json_bytes_mapping=JsonBytesMappingKind::kHex -TEST_F(OtlpHttpExporterTestPeer, ConfigUseJsonNameTest) +TEST_F(OtlpHttpExporterTestPeer, ConfigJsonBytesMappingTest) { OtlpHttpExporterOptions opts; opts.json_bytes_mapping = JsonBytesMappingKind::kHex; From eaca36e4cbb4d182bedf785789b8d745ac2eae18 Mon Sep 17 00:00:00 2001 From: owent Date: Sat, 5 Jun 2021 13:49:01 +0800 Subject: [PATCH 7/7] Add binary mode for otlp_http_exporter Signed-off-by: owent --- examples/otlp/README.md | 6 ++--- examples/otlp/http_main.cc | 14 ++++++++++- .../config.dev.yaml | 4 ++++ .../otlp/test/otlp_http_exporter_test.cc | 23 ++++++++++++------- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/examples/otlp/README.md b/examples/otlp/README.md index 86901411f3..03325cd681 100644 --- a/examples/otlp/README.md +++ b/examples/otlp/README.md @@ -32,19 +32,19 @@ OpenTelemetry Collector with an OTLP receiver by running: - On Unix based systems use: ```console -docker run --rm -it -p 4317:4317 -v $(pwd)/examples/otlp:/cfg otel/opentelemetry-collector:0.19.0 --config=/cfg/opentelemetry-collector-config/config.dev.yaml +docker run --rm -it -p 4317:4317 -p 55681:55681 -v $(pwd)/examples/otlp:/cfg otel/opentelemetry-collector:0.19.0 --config=/cfg/opentelemetry-collector-config/config.dev.yaml ``` - On Windows use: ```console -docker run --rm -it -p 4317:4317 -v "%cd%/examples/otlp":/cfg otel/opentelemetry-collector:0.19.0 --config=/cfg/opentelemetry-collector-config/config.dev.yaml +docker run --rm -it -p 4317:4317 -p 55681:55681 -v "%cd%/examples/otlp":/cfg otel/opentelemetry-collector:0.19.0 --config=/cfg/opentelemetry-collector-config/config.dev.yaml ``` Note that the OTLP exporter connects to the Collector at `localhost:4317` by default. This can be changed with first argument from command-line, for example: `./example_otlp_grpc gateway.docker.internal:4317` and -`./example_otlp_http gateway.docker.internal:4317`. +`./example_otlp_http gateway.docker.internal:55681/v1/traces`. Once you have the Collector running, see [CONTRIBUTING.md](../../CONTRIBUTING.md) for instructions on building and diff --git a/examples/otlp/http_main.cc b/examples/otlp/http_main.cc index 772b56742e..a35fb4b35b 100644 --- a/examples/otlp/http_main.cc +++ b/examples/otlp/http_main.cc @@ -6,6 +6,8 @@ #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/trace/provider.h" +#include + #include "foo_library/foo_library.h" namespace trace = opentelemetry::trace; @@ -36,7 +38,17 @@ int main(int argc, char *argv[]) opts.url = argv[1]; if (argc > 2) { - opts.console_debug = false; + std::string debug = argv[2]; + opts.console_debug = debug != "" && debug != "0" && debug != "no"; + } + + if (argc > 3) + { + std::string binary_mode = argv[3]; + if (binary_mode.size() >= 3 && binary_mode.substr(0, 3) == "bin") + { + opts.content_type = opentelemetry::exporter::otlp::HttpRequestContentType::kBinary; + } } } // Removing this line will leave the default noop TracerProvider in place. diff --git a/examples/otlp/opentelemetry-collector-config/config.dev.yaml b/examples/otlp/opentelemetry-collector-config/config.dev.yaml index 6807000f01..d3fbfd8834 100644 --- a/examples/otlp/opentelemetry-collector-config/config.dev.yaml +++ b/examples/otlp/opentelemetry-collector-config/config.dev.yaml @@ -6,6 +6,10 @@ receivers: protocols: grpc: endpoint: 0.0.0.0:4317 + http: + endpoint: "0.0.0.0:55681" + cors_allowed_origins: + - '*' service: pipelines: traces: diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc index fe74829a81..ef4ab2094e 100644 --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -194,10 +194,13 @@ TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTest) resource_attributes["vec_string_value"] = std::vector{"vector", "string"}; auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); - auto processor = std::unique_ptr(new sdk::trace::BatchSpanProcessor( - std::move(exporter), - sdk::trace::BatchSpanProcessorOptions{5, std::chrono::milliseconds(256), 5})); - auto provider = nostd::shared_ptr( + auto processor_opts = sdk::trace::BatchSpanProcessorOptions(); + processor_opts.max_export_batch_size = 5; + processor_opts.max_queue_size = 5; + processor_opts.schedule_delay_millis = std::chrono::milliseconds(256); + auto processor = std::unique_ptr( + new sdk::trace::BatchSpanProcessor(std::move(exporter), processor_opts)); + auto provider = nostd::shared_ptr( new sdk::trace::TracerProvider(std::move(processor), resource)); std::string report_trace_id; @@ -250,10 +253,14 @@ TEST_F(OtlpHttpExporterTestPeer, ExportBinaryIntegrationTest) resource_attributes["vec_string_value"] = std::vector{"vector", "string"}; auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); - auto processor = std::unique_ptr(new sdk::trace::BatchSpanProcessor( - std::move(exporter), - sdk::trace::BatchSpanProcessorOptions{5, std::chrono::milliseconds(256), 5})); - auto provider = nostd::shared_ptr( + auto processor_opts = sdk::trace::BatchSpanProcessorOptions(); + processor_opts.max_export_batch_size = 5; + processor_opts.max_queue_size = 5; + processor_opts.schedule_delay_millis = std::chrono::milliseconds(256); + + auto processor = std::unique_ptr( + new sdk::trace::BatchSpanProcessor(std::move(exporter), processor_opts)); + auto provider = nostd::shared_ptr( new sdk::trace::TracerProvider(std::move(processor), resource)); std::string report_trace_id;