diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/jaegertracing/opentelemetry-cpp/exporters/otlp | |
parent | Initial commit. (diff) | |
download | ceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/jaegertracing/opentelemetry-cpp/exporters/otlp')
30 files changed, 5594 insertions, 0 deletions
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/BUILD new file mode 100644 index 000000000..496819138 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/BUILD @@ -0,0 +1,286 @@ +# Copyright 2020, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_library( + name = "otlp_recordable", + srcs = [ + "src/otlp_log_recordable.cc", + "src/otlp_recordable.cc", + "src/otlp_recordable_utils.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_log_recordable.h", + "include/opentelemetry/exporters/otlp/otlp_recordable.h", + "include/opentelemetry/exporters/otlp/otlp_recordable_utils.h", + "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", + "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", + ], + strip_include_prefix = "include", + tags = ["otlp"], + deps = [ + "//sdk/src/logs", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_github_opentelemetry_proto//:logs_service_proto_cc", + "@com_github_opentelemetry_proto//:trace_service_proto_cc", + ], +) + +cc_library( + name = "otlp_grpc_exporter", + srcs = [ + "src/otlp_grpc_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_environment.h", + "include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h", + "include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h", + "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", + "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", + ], + strip_include_prefix = "include", + tags = [ + "otlp", + "otlp_grpc", + ], + deps = [ + ":otlp_recordable", + "//ext:headers", + "//sdk/src/trace", + + # For gRPC + "@com_github_opentelemetry_proto//:trace_service_grpc_cc", + "@com_github_grpc_grpc//:grpc++", + ], +) + +cc_library( + name = "otlp_http_client", + srcs = [ + "src/otlp_http_client.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_environment.h", + "include/opentelemetry/exporters/otlp/otlp_http_client.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", + tags = [ + "otlp", + "otlp_http", + "otlp_http_log", + ], + deps = [ + "//api", + "//ext/src/http/client/curl:http_client_curl", + "//sdk:headers", + "@com_github_opentelemetry_proto//:common_proto_cc", + "@github_nlohmann_json//:json", + ], +) + +cc_library( + name = "otlp_http_exporter", + srcs = [ + "src/otlp_http_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_environment.h", + "include/opentelemetry/exporters/otlp/otlp_http_exporter.h", + "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", + "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", + ], + strip_include_prefix = "include", + tags = [ + "otlp", + "otlp_http", + ], + deps = [ + ":otlp_http_client", + ":otlp_recordable", + "//sdk/src/trace", + "@com_github_opentelemetry_proto//:trace_service_proto_cc", + ], +) + +cc_library( + name = "otlp_http_log_exporter", + srcs = [ + "src/otlp_http_log_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_environment.h", + "include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h", + "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", + "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", + ], + strip_include_prefix = "include", + tags = [ + "otlp", + "otlp_http_log", + ], + deps = [ + ":otlp_http_client", + ":otlp_recordable", + "//sdk/src/logs", + "@com_github_opentelemetry_proto//:logs_service_proto_cc", + ], +) + +cc_library( + name = "otlp_grpc_log_exporter", + srcs = [ + "src/otlp_grpc_log_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/otlp/otlp_environment.h", + "include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h", + "include/opentelemetry/exporters/otlp/otlp_grpc_log_exporter.h", + "include/opentelemetry/exporters/otlp/protobuf_include_prefix.h", + "include/opentelemetry/exporters/otlp/protobuf_include_suffix.h", + ], + strip_include_prefix = "include", + tags = [ + "otlp", + "otlp_grpc_log", + ], + deps = [ + ":otlp_recordable", + "//ext:headers", + "//sdk/src/logs", + "@com_github_opentelemetry_proto//:logs_service_proto_cc", + # For gRPC + "@com_github_opentelemetry_proto//:logs_service_grpc_cc", + "@com_github_grpc_grpc//:grpc++", + ], +) + +cc_test( + name = "otlp_recordable_test", + srcs = ["test/otlp_recordable_test.cc"], + tags = [ + "otlp", + "test", + ], + deps = [ + ":otlp_recordable", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "otlp_log_recordable_test", + srcs = ["test/otlp_log_recordable_test.cc"], + tags = [ + "otlp", + "test", + ], + deps = [ + ":otlp_recordable", + "@com_github_opentelemetry_proto//:logs_service_proto_cc", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "otlp_grpc_exporter_test", + srcs = ["test/otlp_grpc_exporter_test.cc"], + tags = [ + "otlp", + "otlp_grpc", + "test", + ], + deps = [ + ":otlp_grpc_exporter", + "//api", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "otlp_http_exporter_test", + srcs = ["test/otlp_http_exporter_test.cc"], + tags = [ + "otlp", + "otlp_http", + "test", + ], + deps = [ + ":otlp_http_exporter", + "//api", + "//ext/src/http/client/nosend:http_client_nosend", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "otlp_http_log_exporter_test", + srcs = ["test/otlp_http_log_exporter_test.cc"], + tags = [ + "otlp", + "otlp_http_log", + "test", + ], + deps = [ + ":otlp_http_log_exporter", + "//api", + "//ext/src/http/client/nosend:http_client_nosend", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "otlp_grpc_log_exporter_test", + srcs = ["test/otlp_grpc_log_exporter_test.cc"], + tags = [ + "otlp", + "otlp_grpc_log", + "test", + ], + deps = [ + ":otlp_grpc_log_exporter", + "//api", + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) + +otel_cc_benchmark( + name = "otlp_grpc_exporter_benchmark", + srcs = ["test/otlp_grpc_exporter_benchmark.cc"], + tags = [ + "otlp", + "otlp_grpc", + "test", + ], + deps = [ + ":otlp_grpc_exporter", + "//examples/common/foo_library:common_foo_library", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/CMakeLists.txt new file mode 100755 index 000000000..b5dfa1e1a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/CMakeLists.txt @@ -0,0 +1,195 @@ +add_library( + opentelemetry_otlp_recordable + src/otlp_log_recordable.cc src/otlp_recordable.cc + src/otlp_recordable_utils.cc) +set_target_properties(opentelemetry_otlp_recordable PROPERTIES EXPORT_NAME + otlp_recordable) + +target_include_directories( + opentelemetry_otlp_recordable + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + +set(OPENTELEMETRY_OTLP_TARGETS opentelemetry_otlp_recordable) +target_link_libraries( + opentelemetry_otlp_recordable + PUBLIC opentelemetry_trace opentelemetry_resources opentelemetry_proto) + +if(WITH_OTLP_GRPC) + 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_grpc_exporter) + + target_link_libraries(opentelemetry_exporter_otlp_grpc + PUBLIC opentelemetry_otlp_recordable gRPC::grpc++) + + list(APPEND OPENTELEMETRY_OTLP_TARGETS opentelemetry_exporter_otlp_grpc) + + add_library(opentelemetry_exporter_otlp_grpc_log + src/otlp_grpc_log_exporter.cc) + + set_target_properties(opentelemetry_exporter_otlp_grpc_log + PROPERTIES EXPORT_NAME otlp_grpc_log_exporter) + + target_link_libraries(opentelemetry_exporter_otlp_grpc_log + PUBLIC opentelemetry_otlp_recordable gRPC::grpc++) + + list(APPEND OPENTELEMETRY_OTLP_TARGETS opentelemetry_exporter_otlp_grpc_log) +endif() + +if(WITH_OTLP_HTTP) + find_package(CURL REQUIRED) + add_library(opentelemetry_exporter_otlp_http_client src/otlp_http_client.cc) + set_target_properties(opentelemetry_exporter_otlp_http_client + PROPERTIES EXPORT_NAME otlp_http_client) + target_link_libraries( + opentelemetry_exporter_otlp_http_client + PUBLIC opentelemetry_sdk opentelemetry_proto opentelemetry_http_client_curl + nlohmann_json::nlohmann_json) + if(nlohmann_json_clone) + add_dependencies(opentelemetry_exporter_otlp_http_client + nlohmann_json::nlohmann_json) + endif() + target_include_directories( + opentelemetry_exporter_otlp_http_client + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + + list(APPEND OPENTELEMETRY_OTLP_TARGETS + opentelemetry_exporter_otlp_http_client) + + 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 + opentelemetry_exporter_otlp_http_client) + + list(APPEND OPENTELEMETRY_OTLP_TARGETS opentelemetry_exporter_otlp_http) + + if(WITH_LOGS_PREVIEW) + add_library(opentelemetry_exporter_otlp_http_log + src/otlp_http_log_exporter.cc) + + set_target_properties(opentelemetry_exporter_otlp_http_log + PROPERTIES EXPORT_NAME otlp_http_log_exporter) + + target_link_libraries( + opentelemetry_exporter_otlp_http_log + PUBLIC opentelemetry_otlp_recordable + opentelemetry_exporter_otlp_http_client) + + list(APPEND OPENTELEMETRY_OTLP_TARGETS opentelemetry_exporter_otlp_http_log) + + endif() +endif() + +install( + TARGETS ${OPENTELEMETRY_OTLP_TARGETS} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/otlp + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h") + +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_otlp_recordable) + gtest_add_tests( + TARGET otlp_recordable_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_recordable_test) + + if(WITH_LOGS_PREVIEW) + add_executable(otlp_log_recordable_test test/otlp_log_recordable_test.cc) + target_link_libraries( + otlp_log_recordable_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_otlp_recordable) + gtest_add_tests( + TARGET otlp_log_recordable_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_log_recordable_test) + endif() + + if(MSVC) + # Explicitly specify that we consume GTest from shared library. The rest of + # code logic below determines whether we link Release or Debug flavor of the + # library. These flavors have different prefix on Windows, gmock and gmockd + # respectively. + add_definitions(-DGTEST_LINKED_AS_SHARED_LIBRARY=1) + if(GMOCK_LIB) + # unset GMOCK_LIB to force find_library to redo the lookup, as the cached + # entry could cause linking to incorrect flavor of gmock and leading to + # runtime error. + unset(GMOCK_LIB CACHE) + endif() + endif() + if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug") + find_library(GMOCK_LIB gmockd PATH_SUFFIXES lib) + else() + find_library(GMOCK_LIB gmock PATH_SUFFIXES lib) + endif() + + if(WITH_OTLP_GRPC) + add_executable(otlp_grpc_exporter_test test/otlp_grpc_exporter_test.cc) + target_link_libraries( + otlp_grpc_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + ${GMOCK_LIB} opentelemetry_exporter_otlp_grpc) + gtest_add_tests( + TARGET otlp_grpc_exporter_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_grpc_exporter_test) + + if(WITH_LOGS_PREVIEW) + add_executable(otlp_grpc_log_exporter_test + test/otlp_grpc_log_exporter_test.cc) + target_link_libraries( + otlp_grpc_log_exporter_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} ${GMOCK_LIB} + opentelemetry_exporter_otlp_grpc_log opentelemetry_logs) + gtest_add_tests( + TARGET otlp_grpc_log_exporter_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_grpc_log_exporter_test) + endif() + endif() + + if(WITH_OTLP_HTTP) + 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 http_client_nosend) + gtest_add_tests( + TARGET otlp_http_exporter_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_http_exporter_test) + + if(WITH_LOGS_PREVIEW) + add_executable(otlp_http_log_exporter_test + test/otlp_http_log_exporter_test.cc) + target_link_libraries( + otlp_http_log_exporter_test + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${GMOCK_LIB} + opentelemetry_exporter_otlp_http_log + opentelemetry_logs + http_client_nosend) + gtest_add_tests( + TARGET otlp_http_log_exporter_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_http_log_exporter_test) + endif() + endif() +endif() # BUILD_TESTING diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/README.md b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/README.md new file mode 100644 index 000000000..9ac6918ba --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/README.md @@ -0,0 +1,79 @@ +# OTLP Exporter + +The [OpenTelemetry +Protocol](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/README.md) +(OTLP) is a vendor-agnostic protocol designed as part of the OpenTelemetry +project. The OTLP exporter can be used to export to any backend that supports +OTLP. + +The [OpenTelemetry +Collector](https://github.com/open-telemetry/opentelemetry-collector) is a +reference implementation of an OTLP backend. The Collector can be configured to +export to many other backends, such as Zipkin and Jaegar. + +For a full list of backends supported by the Collector, see the [main Collector +repo](https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter) +and [Collector +contributions](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter). + +## Configuration + +The OTLP exporter offers some configuration options. To configure the exporter, +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 +OtlpGrpcExporterOptions options; +options.endpoint = "localhost:12345"; +auto exporter = std::unique_ptr<sdktrace::SpanExporter>(new otlp::OtlpGrpcExporter(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<sdktrace::SpanExporter>(new otlp::OtlpHttpExporter(options)); +``` + +### Configuration options ( OTLP GRPC Exporter ) + +| Option | Env Variable |Default | Description | +| ------------ |---------------|------------ |----------------| +| `endpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317`| The OTLP GRPC endpoint to connect to | +| | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | | | +| `use_ssl_credentials` | `OTEL_EXPORTER_OTLP_SSL_ENABLE`| `false` | Whether the endpoint is SSL enabled | +| | `OTEL_EXPORTER_OTLP_TRACES_SSL_ENABLE` | | | +| `ssl_credentials_cacert_path` | `OTEL_EXPORTER_OTLP_CERTIFICATE` | `""` | SSL Certificate file path | +| | `OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE` | | | +| `ssl_credentials_cacert_as_string` | `OTEL_EXPORTER_OTLP_CERTIFICATE_STRING` | `""` | SSL Certifcate as in-memory string | +| | `OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE_STRING` | | | | +| `timeout` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10s` | GRPC deadline | +| | `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | | | +| `metadata` | `OTEL_EXPORTER_OTLP_HEADERS` | | Custom metadata for GRPC | +| | `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | | | + +### Configuration options ( OTLP HTTP Exporter ) + +| Option | Env Variable |Default | Description | +| ------------ |-----|------------ |------| +| `url` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318/v1/traces` | The OTLP HTTP endpoint to connect to | +| | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | | | +| `content_type` | n/a | `application/json` | Data format used - JSON or Binary | +| `json_bytes_mapping` | n/a | `JsonBytesMappingKind::kHexId` | Encoding used for trace_id and span_id | +| `use_json_name` | n/a | `false` | Whether to use json name of protobuf field to set the key of json | +| `timeout` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10s` | http timeout | +| | `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | | +| `http_headers` | `OTEL_EXPORTER_OTLP_HEADERS` | | http headers | +| | `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | | | + +## Example + +For a complete example demonstrating how to use the OTLP exporter, see +[examples/otlp](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/examples/otlp/). diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h new file mode 100644 index 000000000..bf7ea6a61 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h @@ -0,0 +1,272 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/common/kv_properties.h" +#include "opentelemetry/nostd/string_view.h" + +#include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/common/env_variables.h" + +#include <algorithm> +#include <chrono> +#include <map> +#include <string> +#include <unordered_set> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +inline const std::string GetOtlpDefaultGrpcEndpoint() +{ + constexpr char kOtlpTracesEndpointEnv[] = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"; + constexpr char kOtlpEndpointEnv[] = "OTEL_EXPORTER_OTLP_ENDPOINT"; + constexpr char kOtlpEndpointDefault[] = "http://localhost:4317"; + + auto endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTracesEndpointEnv); + if (endpoint.empty()) + { + endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpEndpointEnv); + } + return endpoint.size() ? endpoint : kOtlpEndpointDefault; +} + +inline const std::string GetOtlpDefaultHttpEndpoint() +{ + constexpr char kOtlpTracesEndpointEnv[] = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"; + constexpr char kOtlpEndpointEnv[] = "OTEL_EXPORTER_OTLP_ENDPOINT"; + constexpr char kOtlpEndpointDefault[] = "http://localhost:4318/v1/traces"; + + auto endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTracesEndpointEnv); + if (endpoint.empty()) + { + endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpEndpointEnv); + if (!endpoint.empty()) + { + endpoint += "/v1/traces"; + } + } + return endpoint.size() ? endpoint : kOtlpEndpointDefault; +} + +inline bool GetOtlpDefaultIsSslEnable() +{ + constexpr char kOtlpTracesIsSslEnableEnv[] = "OTEL_EXPORTER_OTLP_TRACES_SSL_ENABLE"; + constexpr char kOtlpIsSslEnableEnv[] = "OTEL_EXPORTER_OTLP_SSL_ENABLE"; + + auto ssl_enable = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTracesIsSslEnableEnv); + if (ssl_enable.empty()) + { + ssl_enable = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpIsSslEnableEnv); + } + if (ssl_enable == "True" || ssl_enable == "TRUE" || ssl_enable == "true" || ssl_enable == "1") + { + return true; + } + return false; +} + +inline const std::string GetOtlpDefaultSslCertificatePath() +{ + constexpr char kOtlpTracesSslCertificate[] = "OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE"; + constexpr char kOtlpSslCertificate[] = "OTEL_EXPORTER_OTLP_CERTIFICATE "; + auto ssl_cert_path = + opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTracesSslCertificate); + if (ssl_cert_path.empty()) + { + ssl_cert_path = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpSslCertificate); + } + return ssl_cert_path.size() ? ssl_cert_path : ""; +} + +inline const std::string GetOtlpDefaultSslCertificateString() +{ + constexpr char kOtlpTracesSslCertificateString[] = "OTEL_EXPORTER_OTLP_CERTIFICATE_STRING"; + constexpr char kOtlpSslCertificateString[] = "OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE_STRING "; + auto ssl_cert = + opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTracesSslCertificateString); + if (ssl_cert.empty()) + { + ssl_cert = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpSslCertificateString); + } + return ssl_cert.size() ? ssl_cert : ""; +} + +inline const std::chrono::system_clock::duration GetOtlpTimeoutFromString(const char *input) +{ + if (nullptr == input || 0 == *input) + { + return std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::seconds{10}); + } + + std::chrono::system_clock::duration::rep result = 0; + // Skip spaces + for (; *input && (' ' == *input || '\t' == *input || '\r' == *input || '\n' == *input); ++input) + ; + + for (; *input && (*input >= '0' && *input <= '9'); ++input) + { + result = result * 10 + (*input - '0'); + } + + opentelemetry::nostd::string_view unit{input}; + if ("ns" == unit) + { + return std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::nanoseconds{result}); + } + else if ("us" == unit) + { + return std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::microseconds{result}); + } + else if ("ms" == unit) + { + return std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::milliseconds{result}); + } + else if ("m" == unit) + { + return std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::minutes{result}); + } + else if ("h" == unit) + { + return std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::hours{result}); + } + else + { + return std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::seconds{result}); + } +} + +inline const std::chrono::system_clock::duration GetOtlpDefaultTimeout() +{ + constexpr char kOtlpTracesTimeoutEnv[] = "OTEL_EXPORTER_OTLP_TRACES_TIMEOUT"; + constexpr char kOtlpTimeoutEnv[] = "OTEL_EXPORTER_OTLP_TIMEOUT"; + + auto timeout = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTracesTimeoutEnv); + if (timeout.empty()) + { + timeout = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTimeoutEnv); + } + return GetOtlpTimeoutFromString(timeout.c_str()); +} + +struct cmp_ic +{ + bool operator()(const std::string &s1, const std::string &s2) const + { + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); }); + } +}; +using OtlpHeaders = std::multimap<std::string, std::string, cmp_ic>; + +inline void DumpOtlpHeaders(OtlpHeaders &output, + const char *env_var_name, + std::unordered_set<std::string> &remove_cache) +{ + auto value = opentelemetry::sdk::common::GetEnvironmentVariable(env_var_name); + if (value.empty()) + { + return; + } + + opentelemetry::common::KeyValueStringTokenizer tokenizer{value}; + opentelemetry::nostd::string_view header_key; + opentelemetry::nostd::string_view header_value; + bool header_valid = true; + + while (tokenizer.next(header_valid, header_key, header_value)) + { + if (header_valid) + { + std::string key = static_cast<std::string>(header_key); + if (remove_cache.end() == remove_cache.find(key)) + { + remove_cache.insert(key); + auto range = output.equal_range(key); + if (range.first != range.second) + { + output.erase(range.first, range.second); + } + } + + output.emplace(std::make_pair(std::move(key), static_cast<std::string>(header_value))); + } + } +} + +inline OtlpHeaders GetOtlpDefaultHeaders() +{ + constexpr char kOtlpTracesHeadersEnv[] = "OTEL_EXPORTER_OTLP_TRACES_HEADERS"; + constexpr char kOtlpHeadersEnv[] = "OTEL_EXPORTER_OTLP_HEADERS"; + + OtlpHeaders result; + std::unordered_set<std::string> trace_remove_cache; + DumpOtlpHeaders(result, kOtlpHeadersEnv, trace_remove_cache); + + trace_remove_cache.clear(); + DumpOtlpHeaders(result, kOtlpTracesHeadersEnv, trace_remove_cache); + + return result; +} + +inline const std::string GetOtlpDefaultHttpLogEndpoint() +{ + constexpr char kOtlpLogsEndpointEnv[] = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"; + constexpr char kOtlpEndpointEnv[] = "OTEL_EXPORTER_OTLP_ENDPOINT"; + constexpr char kOtlpEndpointDefault[] = "http://localhost:4318/v1/logs"; + + auto endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpLogsEndpointEnv); + if (endpoint.empty()) + { + endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpEndpointEnv); + if (!endpoint.empty()) + { + endpoint += "/v1/logs"; + } + } + return endpoint.size() ? endpoint : kOtlpEndpointDefault; +} + +inline const std::chrono::system_clock::duration GetOtlpDefaultLogTimeout() +{ + constexpr char kOtlpLogsTimeoutEnv[] = "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT"; + constexpr char kOtlpTimeoutEnv[] = "OTEL_EXPORTER_OTLP_TIMEOUT"; + + auto timeout = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpLogsTimeoutEnv); + if (timeout.empty()) + { + timeout = opentelemetry::sdk::common::GetEnvironmentVariable(kOtlpTimeoutEnv); + } + return GetOtlpTimeoutFromString(timeout.c_str()); +} + +inline OtlpHeaders GetOtlpDefaultLogHeaders() +{ + constexpr char kOtlpLogsHeadersEnv[] = "OTEL_EXPORTER_OTLP_LOGS_HEADERS"; + constexpr char kOtlpHeadersEnv[] = "OTEL_EXPORTER_OTLP_HEADERS"; + + OtlpHeaders result; + std::unordered_set<std::string> log_remove_cache; + DumpOtlpHeaders(result, kOtlpHeadersEnv, log_remove_cache); + + log_remove_cache.clear(); + DumpOtlpHeaders(result, kOtlpLogsHeadersEnv, log_remove_cache); + + return result; +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h new file mode 100644 index 000000000..a28e6fca8 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter.h @@ -0,0 +1,86 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <chrono> + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/proto/collector/trace/v1/trace_service.grpc.pb.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/exporters/otlp/otlp_environment.h" +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +/** + * The OTLP exporter exports span data in OpenTelemetry Protocol (OTLP) format. + */ +class OtlpGrpcExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * Create an OtlpGrpcExporter using all default options. + */ + OtlpGrpcExporter(); + + /** + * Create an OtlpGrpcExporter using the given options. + */ + explicit OtlpGrpcExporter(const OtlpGrpcExporterOptions &options); + + /** + * Create a span recordable. + * @return a newly initialized Recordable object + */ + std::unique_ptr<sdk::trace::Recordable> MakeRecordable() noexcept override; + + /** + * Export a batch of span recordables in OTLP format. + * @param spans a span of unique pointers to span recordables + */ + sdk::common::ExportResult Export( + const nostd::span<std::unique_ptr<sdk::trace::Recordable>> &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::max()) noexcept override; + +private: + // The configuration options associated with this exporter. + const OtlpGrpcExporterOptions options_; + + // For testing + friend class OtlpGrpcExporterTestPeer; + + // Store service stub internally. Useful for testing. + std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> trace_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 + */ + OtlpGrpcExporter(std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> stub); + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h new file mode 100644 index 000000000..c62a0f0af --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/exporters/otlp/otlp_environment.h" + +#include <memory> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +/** + * Struct to hold OTLP exporter options. + */ +struct OtlpGrpcExporterOptions +{ + // The endpoint to export to. By default the OpenTelemetry Collector's default endpoint. + std::string endpoint = GetOtlpDefaultGrpcEndpoint(); + // By default when false, uses grpc::InsecureChannelCredentials(); If true, + // uses ssl_credentials_cacert_path if non-empty, else uses ssl_credentials_cacert_as_string + bool use_ssl_credentials = GetOtlpDefaultIsSslEnable(); + // ssl_credentials_cacert_path specifies path to .pem file to be used for SSL encryption. + std::string ssl_credentials_cacert_path = GetOtlpDefaultSslCertificatePath(); + // ssl_credentials_cacert_as_string in-memory string representation of .pem file to be used for + // SSL encryption. + std::string ssl_credentials_cacert_as_string = GetOtlpDefaultSslCertificateString(); + // Timeout for grpc deadline + std::chrono::system_clock::duration timeout = GetOtlpDefaultTimeout(); + // Additional HTTP headers + OtlpHeaders metadata = GetOtlpDefaultHeaders(); +}; + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_log_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_log_exporter.h new file mode 100644 index 000000000..a8aeda85b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_log_exporter.h @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +// clang-format off + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" +# include "opentelemetry/proto/collector/logs/v1/logs_service.grpc.pb.h" +# include "opentelemetry/common/spin_lock_mutex.h" +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +// clang-format on + +# include "opentelemetry/exporters/otlp/otlp_environment.h" +# include "opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h" +# include "opentelemetry/sdk/logs/exporter.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +/** + * The OTLP exporter exports log data in OpenTelemetry Protocol (OTLP) format in gRPC. + */ +class OtlpGrpcLogExporter : public opentelemetry::sdk::logs::LogExporter +{ +public: + /** + * Create an OtlpGrpcLogExporter with default exporter options. + */ + OtlpGrpcLogExporter(); + + /** + * Create an OtlpGrpcLogExporter with user specified options. + * @param options An object containing the user's configuration options. + */ + OtlpGrpcLogExporter(const OtlpGrpcExporterOptions &options); + + /** + * Creates a recordable that stores the data in protobuf. + * @return a newly initialized Recordable object. + */ + std::unique_ptr<opentelemetry::sdk::logs::Recordable> MakeRecordable() noexcept override; + + /** + * Exports a vector of log records to the configured gRPC endpoint. Guaranteed to return after a + * timeout specified from the options passed to the constructor. + * @param records A list of log records. + */ + opentelemetry::sdk::common::ExportResult Export( + const nostd::span<std::unique_ptr<opentelemetry::sdk::logs::Recordable>> &records) noexcept + override; + + /** + * Shutdown this exporter. + * @param timeout The maximum time to wait for the shutdown method to return. + */ + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + +private: + // Configuration options for the exporter + const OtlpGrpcExporterOptions options_; + + // For testing + friend class OtlpGrpcLogExporterTestPeer; + + // Store service stub internally. Useful for testing. + std::unique_ptr<proto::collector::logs::v1::LogsService::StubInterface> log_service_stub_; + + /** + * Create an OtlpGrpcLogExporter using the specified service stub. + * Only tests can call this constructor directly. + * @param stub the service stub to be used for exporting + */ + OtlpGrpcLogExporter(std::unique_ptr<proto::collector::logs::v1::LogsService::StubInterface> stub); + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; +}; + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h new file mode 100644 index 000000000..1a199bed4 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h @@ -0,0 +1,144 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include "google/protobuf/message.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/ext/http/client/http_client.h" +#include "opentelemetry/sdk/common/exporter_utils.h" + +#include "opentelemetry/exporters/otlp/otlp_environment.h" + +#include <chrono> +#include <memory> +#include <mutex> +#include <string> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +// The default URL path to post metric data. +constexpr char kDefaultMetricsPath[] = "/v1/metrics"; +// The HTTP header "Content-Type" +constexpr char kHttpJsonContentType[] = "application/json"; +constexpr char kHttpBinaryContentType[] = "application/x-protobuf"; + +enum class JsonBytesMappingKind +{ + kHexId, + kHex, + kBase64, +}; + +enum class HttpRequestContentType +{ + kJson, + kBinary, +}; + +/** + * Struct to hold OTLP HTTP client options. + */ +struct OtlpHttpClientOptions +{ + std::string url; + + // By default, post json data + HttpRequestContentType content_type = HttpRequestContentType::kJson; + + // 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. + bool use_json_name = false; + + // Whether to print the status of the HTTP client in the console + bool console_debug = false; + + // TODO: Enable/disable to verify SSL certificate + std::chrono::system_clock::duration timeout = GetOtlpDefaultTimeout(); + + // Additional HTTP headers + OtlpHeaders http_headers = GetOtlpDefaultHeaders(); + + inline OtlpHttpClientOptions(nostd::string_view input_url, + HttpRequestContentType input_content_type, + JsonBytesMappingKind input_json_bytes_mapping, + bool input_use_json_name, + bool input_console_debug, + std::chrono::system_clock::duration input_timeout, + const OtlpHeaders &input_http_headers) + : url(input_url), + content_type(input_content_type), + json_bytes_mapping(input_json_bytes_mapping), + use_json_name(input_use_json_name), + console_debug(input_console_debug), + timeout(input_timeout), + http_headers(input_http_headers) + {} +}; + +/** + * The OTLP HTTP client exports span data in OpenTelemetry Protocol (OTLP) format. + */ +class OtlpHttpClient +{ +public: + /** + * Create an OtlpHttpClient using the given options. + */ + explicit OtlpHttpClient(OtlpHttpClientOptions &&options); + + /** + * Export + * @param message message to export, it should be ExportTraceServiceRequest, + * ExportMetricsServiceRequest or ExportLogsServiceRequest + */ + sdk::common::ExportResult Export(const google::protobuf::Message &message) noexcept; + + /** + * Shut down the HTTP client. + * @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; + +private: + // Stores if this HTTP client had its Shutdown() method called + bool is_shutdown_ = false; + + // The configuration options associated with this HTTP client. + const OtlpHttpClientOptions options_; + + // Object that stores the HTTP sessions that have been created + std::shared_ptr<ext::http::client::HttpClient> http_client_; + // Cached parsed URI + std::string http_uri_; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; + // For testing + friend class OtlpHttpExporterTestPeer; + friend class OtlpHttpLogExporterTestPeer; + /** + * Create an OtlpHttpClient using the specified http client. + * Only tests can call this constructor directly. + * @param options the Otlp http client options to be used for exporting + * @param http_client the http client to be used for exporting + */ + OtlpHttpClient(OtlpHttpClientOptions &&options, + std::shared_ptr<ext::http::client::HttpClient> http_client); +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h new file mode 100644 index 000000000..3e6a52119 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter.h @@ -0,0 +1,110 @@ +// 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/exporters/otlp/otlp_http_client.h" + +#include "opentelemetry/exporters/otlp/otlp_environment.h" + +#include <chrono> +#include <memory> +#include <string> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +/** + * 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 = GetOtlpDefaultHttpEndpoint(); + + // By default, post json data + HttpRequestContentType content_type = HttpRequestContentType::kJson; + + // 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. + bool use_json_name = false; + + // Whether to print the status of the exporter in the console + bool console_debug = false; + + // TODO: Enable/disable to verify SSL certificate + std::chrono::system_clock::duration timeout = GetOtlpDefaultTimeout(); + + // Additional HTTP headers + OtlpHeaders http_headers = GetOtlpDefaultHeaders(); +}; + +/** + * 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. + */ + explicit OtlpHttpExporter(const OtlpHttpExporterOptions &options); + + /** + * Create a span recordable. + * @return a newly initialized Recordable object + */ + std::unique_ptr<opentelemetry::sdk::trace::Recordable> MakeRecordable() noexcept override; + + /** + * Export + * @param spans a span of unique pointers to span recordables + */ + opentelemetry::sdk::common::ExportResult Export( + const nostd::span<std::unique_ptr<opentelemetry::sdk::trace::Recordable>> &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: + // The configuration options associated with this exporter. + const OtlpHttpExporterOptions options_; + + // Object that stores the HTTP sessions that have been created + std::unique_ptr<OtlpHttpClient> http_client_; + // For testing + friend class OtlpHttpExporterTestPeer; + /** + * Create an OtlpHttpExporter using the specified http client. + * Only tests can call this constructor directly. + * @param http_client the http client to be used for exporting + */ + OtlpHttpExporter(std::unique_ptr<OtlpHttpClient> http_client); +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h new file mode 100644 index 000000000..d330e62be --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_exporter.h @@ -0,0 +1,110 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/sdk/logs/exporter.h" + +# include "opentelemetry/exporters/otlp/otlp_http_client.h" + +# include "opentelemetry/exporters/otlp/otlp_environment.h" + +# include <chrono> +# include <memory> +# include <string> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +/** + * Struct to hold OTLP exporter options. + */ +struct OtlpHttpLogExporterOptions +{ + // 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 = GetOtlpDefaultHttpLogEndpoint(); + + // By default, post json data + HttpRequestContentType content_type = HttpRequestContentType::kJson; + + // 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. + bool use_json_name = false; + + // Whether to print the status of the exporter in the console + bool console_debug = false; + + // TODO: Enable/disable to verify SSL certificate + std::chrono::system_clock::duration timeout = GetOtlpDefaultLogTimeout(); + + // Additional HTTP headers + OtlpHeaders http_headers = GetOtlpDefaultLogHeaders(); +}; + +/** + * The OTLP exporter exports log data in OpenTelemetry Protocol (OTLP) format. + */ +class OtlpHttpLogExporter final : public opentelemetry::sdk::logs::LogExporter +{ +public: + /** + * Create an OtlpHttpLogExporter with default exporter options. + */ + OtlpHttpLogExporter(); + + /** + * Create an OtlpHttpLogExporter with user specified options. + * @param options An object containing the user's configuration options. + */ + OtlpHttpLogExporter(const OtlpHttpLogExporterOptions &options); + + /** + * Creates a recordable that stores the data in a JSON object + */ + std::unique_ptr<opentelemetry::sdk::logs::Recordable> MakeRecordable() noexcept override; + + /** + * Exports a vector of log records to the Elasticsearch instance. Guaranteed to return after a + * timeout specified from the options passed from the constructor. + * @param records A list of log records to send to Elasticsearch. + */ + opentelemetry::sdk::common::ExportResult Export( + const nostd::span<std::unique_ptr<opentelemetry::sdk::logs::Recordable>> &records) noexcept + override; + + /** + * Shutdown this exporter. + * @param timeout The maximum time to wait for the shutdown method to return + */ + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + +private: + // Configuration options for the exporter + const OtlpHttpLogExporterOptions options_; + + // Object that stores the HTTP sessions that have been created + std::unique_ptr<OtlpHttpClient> http_client_; + // For testing + friend class OtlpHttpLogExporterTestPeer; + /** + * Create an OtlpHttpLogExporter using the specified http client. + * Only tests can call this constructor directly. + * @param http_client the http client to be used for exporting + */ + OtlpHttpLogExporter(std::unique_ptr<OtlpHttpClient> http_client); +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h new file mode 100644 index 000000000..57ae94ea0 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h @@ -0,0 +1,118 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +// clang-format off +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +# include "opentelemetry/proto/logs/v1/logs.pb.h" +# include "opentelemetry/proto/resource/v1/resource.pb.h" +# include "opentelemetry/sdk/instrumentationlibrary/instrumentation_library.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" +// clang-format on + +# include "opentelemetry/sdk/common/attribute_utils.h" +# include "opentelemetry/sdk/logs/recordable.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +/** + * An OTLP Recordable implemenation for Logs. + */ +class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable +{ +public: + virtual ~OtlpLogRecordable() = default; + + proto::logs::v1::LogRecord &log_record() noexcept { return log_record_; } + const proto::logs::v1::LogRecord &log_record() const noexcept { return log_record_; } + + /** Dynamically converts the resource of this log into a proto. */ + proto::resource::v1::Resource ProtoResource() const noexcept; + + /** + * Set the timestamp for this log. + * @param timestamp the timestamp to set + */ + void SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept override; + + /** + * Set the severity for this log. + * @param severity the severity of the event + */ + void SetSeverity(opentelemetry::logs::Severity severity) noexcept override; + + /** + * Set body field for this log. + * @param message the body to set + */ + void SetBody(nostd::string_view message) noexcept override; + + /** + * Set Resource of this log + * @param Resource the resource to set + */ + void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override; + + /** Returns the associated resource */ + const opentelemetry::sdk::resource::Resource &GetResource() const noexcept; + + /** + * Set an attribute of a log. + * @param key the name of the attribute + * @param value the attribute value + */ + void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept override; + + /** + * Set the trace id for this log. + * @param trace_id the trace id to set + */ + void SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept override; + + /** + * Set the span id for this log. + * @param span_id the span id to set + */ + void SetSpanId(opentelemetry::trace::SpanId span_id) noexcept override; + + /** + * Inject trace_flags for this log. + * @param trace_flags the trace flags to set + */ + void SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept override; + + /** + * Set instrumentation_library for this log. + * @param instrumentation_library the instrumentation library to set + */ + void SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept override; + + /** Returns the associated instruementation library */ + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary & + GetInstrumentationLibrary() const noexcept; + +private: + proto::logs::v1::LogRecord log_record_; + const opentelemetry::sdk::resource::Resource *resource_ = nullptr; + // TODO shared resource + // const opentelemetry::sdk::resource::Resource *resource_ = nullptr; + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + *instrumentation_library_ = nullptr; +}; + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h new file mode 100644 index 000000000..a29d063a9 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h @@ -0,0 +1,72 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include "opentelemetry/proto/resource/v1/resource.pb.h" +#include "opentelemetry/proto/trace/v1/trace.pb.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable +{ +public: + proto::trace::v1::Span &span() noexcept { return span_; } + const proto::trace::v1::Span &span() const noexcept { return span_; } + + /** Dynamically converts the resource of this span into a proto. */ + proto::resource::v1::Resource ProtoResource() const noexcept; + + const std::string GetResourceSchemaURL() const noexcept; + const std::string GetInstrumentationLibrarySchemaURL() const noexcept; + + proto::common::v1::InstrumentationLibrary GetProtoInstrumentationLibrary() const noexcept; + + void SetIdentity(const opentelemetry::trace::SpanContext &span_context, + opentelemetry::trace::SpanId parent_span_id) noexcept override; + + void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept override; + + void AddEvent(nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept override; + + void AddLink(const opentelemetry::trace::SpanContext &span_context, + const common::KeyValueIterable &attributes) noexcept override; + + void SetStatus(trace::StatusCode code, nostd::string_view description) noexcept override; + + void SetName(nostd::string_view name) noexcept override; + + void SetSpanKind(opentelemetry::trace::SpanKind span_kind) noexcept override; + + void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override; + + void SetStartTime(opentelemetry::common::SystemTimestamp start_time) noexcept override; + + void SetDuration(std::chrono::nanoseconds duration) noexcept override; + + void SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept override; + +private: + proto::trace::v1::Span span_; + const opentelemetry::sdk::resource::Resource *resource_ = nullptr; + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + *instrumentation_library_ = nullptr; +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable_utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable_utils.h new file mode 100644 index 000000000..0c09bc871 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable_utils.h @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include "opentelemetry/proto/collector/logs/v1/logs_service.pb.h" +#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" +#include "opentelemetry/proto/resource/v1/resource.pb.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/version.h" + +#include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/recordable.h" + +#ifdef ENABLE_LOGS_PREVIEW +# include "opentelemetry/sdk/logs/recordable.h" +#endif + +#include <memory> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +/** + * The OtlpRecordableUtils contains utility functions for OTLP recordable + */ +class OtlpRecordableUtils +{ +public: + static void PopulateAttribute(opentelemetry::proto::common::v1::KeyValue *attribute, + nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept; + + static void PopulateAttribute( + opentelemetry::proto::common::v1::KeyValue *attribute, + nostd::string_view key, + const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept; + + static void PopulateAttribute(opentelemetry::proto::resource::v1::Resource *proto, + const opentelemetry::sdk::resource::Resource &resource) noexcept; + + static void PopulateRequest( + const nostd::span<std::unique_ptr<opentelemetry::sdk::trace::Recordable>> &spans, + proto::collector::trace::v1::ExportTraceServiceRequest *request) noexcept; + +#ifdef ENABLE_LOGS_PREVIEW + static void PopulateRequest( + const nostd::span<std::unique_ptr<opentelemetry::sdk::logs::Recordable>> &logs, + proto::collector::logs::v1::ExportLogsServiceRequest *request) noexcept; +#endif +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/protobuf_include_prefix.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/protobuf_include_prefix.h new file mode 100644 index 000000000..ae103b017 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/protobuf_include_prefix.h @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// This file may be include multiple times, do not add #pragma once here + +#if defined(_MSC_VER) +# pragma warning(push) +# if ((defined(__cplusplus) && __cplusplus >= 201704L) || \ + (defined(_MSVC_LANG) && _MSVC_LANG >= 201704L)) +# pragma warning(disable : 4996) +# pragma warning(disable : 4309) +# if _MSC_VER >= 1922 +# pragma warning(disable : 5054) +# endif +# endif + +# if _MSC_VER < 1910 +# pragma warning(disable : 4800) +# endif +# pragma warning(disable : 4244) +# pragma warning(disable : 4251) +# pragma warning(disable : 4267) +# pragma warning(disable : 4668) +# pragma warning(disable : 4946) +#endif + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__apple_build_version__) +# if (__GNUC__ * 100 + __GNUC_MINOR__ * 10) >= 460 +# pragma GCC diagnostic push +# endif +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wtype-limits" +#elif defined(__clang__) || defined(__apple_build_version__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-parameter" +# pragma clang diagnostic ignored "-Wtype-limits" +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/protobuf_include_suffix.h b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/protobuf_include_suffix.h new file mode 100644 index 000000000..26e7a2913 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/include/opentelemetry/exporters/otlp/protobuf_include_suffix.h @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// This file may be include multiple times, do not add #pragma once here + +#if defined(__GNUC__) && !defined(__clang__) && !defined(__apple_build_version__) +# if (__GNUC__ * 100 + __GNUC_MINOR__ * 10) >= 460 +# pragma GCC diagnostic pop +# endif +#elif defined(__clang__) || defined(__apple_build_version__) +# pragma clang diagnostic pop +#endif + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_grpc_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_grpc_exporter.cc new file mode 100644 index 000000000..32f4a60a5 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_grpc_exporter.cc @@ -0,0 +1,161 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" +#include <mutex> +#include "opentelemetry/exporters/otlp/otlp_recordable.h" +#include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" +#include "opentelemetry/ext/http/common/url_parser.h" +#include "opentelemetry/sdk_config.h" + +#include <grpcpp/grpcpp.h> +#include <fstream> +#include <sstream> // std::stringstream + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +// ----------------------------- Helper functions ------------------------------ +static std::string get_file_contents(const char *fpath) +{ + std::ifstream finstream(fpath); + std::string contents; + contents.assign((std::istreambuf_iterator<char>(finstream)), std::istreambuf_iterator<char>()); + finstream.close(); + return contents; +} + +/** + * Create gRPC channel from the exporter options. + */ +std::shared_ptr<grpc::Channel> MakeGrpcChannel(const OtlpGrpcExporterOptions &options) +{ + std::shared_ptr<grpc::Channel> channel; + + // + // Scheme is allowed in OTLP endpoint definition, but is not allowed for creating gRPC channel. + // Passing URI with scheme to grpc::CreateChannel could resolve the endpoint to some unexpected + // address. + // + + ext::http::common::UrlParser url(options.endpoint); + if (!url.success_) + { + OTEL_INTERNAL_LOG_ERROR("[OTLP Exporter] invalid endpoint: " << options.endpoint); + + return nullptr; + } + + std::string grpc_target = url.host_ + ":" + std::to_string(static_cast<int>(url.port_)); + + if (options.use_ssl_credentials) + { + grpc::SslCredentialsOptions ssl_opts; + if (options.ssl_credentials_cacert_path.empty()) + { + ssl_opts.pem_root_certs = options.ssl_credentials_cacert_as_string; + } + else + { + ssl_opts.pem_root_certs = get_file_contents((options.ssl_credentials_cacert_path).c_str()); + } + channel = grpc::CreateChannel(grpc_target, grpc::SslCredentials(ssl_opts)); + } + else + { + channel = grpc::CreateChannel(grpc_target, grpc::InsecureChannelCredentials()); + } + + return channel; +} + +/** + * Create service stub to communicate with the OpenTelemetry Collector. + */ +std::unique_ptr<proto::collector::trace::v1::TraceService::Stub> MakeTraceServiceStub( + const OtlpGrpcExporterOptions &options) +{ + return proto::collector::trace::v1::TraceService::NewStub(MakeGrpcChannel(options)); +} + +// -------------------------------- Constructors -------------------------------- + +OtlpGrpcExporter::OtlpGrpcExporter() : OtlpGrpcExporter(OtlpGrpcExporterOptions()) {} + +OtlpGrpcExporter::OtlpGrpcExporter(const OtlpGrpcExporterOptions &options) + : options_(options), trace_service_stub_(MakeTraceServiceStub(options)) +{} + +OtlpGrpcExporter::OtlpGrpcExporter( + std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> stub) + : options_(OtlpGrpcExporterOptions()), trace_service_stub_(std::move(stub)) +{} + +// ----------------------------- Exporter methods ------------------------------ + +std::unique_ptr<sdk::trace::Recordable> OtlpGrpcExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<sdk::trace::Recordable>(new OtlpRecordable); +} + +sdk::common::ExportResult OtlpGrpcExporter::Export( + const nostd::span<std::unique_ptr<sdk::trace::Recordable>> &spans) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[OTLP gRPC] Exporting " << spans.size() + << " span(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + if (spans.empty()) + { + return sdk::common::ExportResult::kSuccess; + } + + proto::collector::trace::v1::ExportTraceServiceRequest request; + OtlpRecordableUtils::PopulateRequest(spans, &request); + + grpc::ClientContext context; + proto::collector::trace::v1::ExportTraceServiceResponse response; + + if (options_.timeout.count() > 0) + { + context.set_deadline(std::chrono::system_clock::now() + options_.timeout); + } + + for (auto &header : options_.metadata) + { + context.AddMetadata(header.first, header.second); + } + + grpc::Status status = trace_service_stub_->Export(&context, request, &response); + + if (!status.ok()) + { + + OTEL_INTERNAL_LOG_ERROR( + "[OTLP TRACE GRPC Exporter] Export() failed: " << status.error_message()); + return sdk::common::ExportResult::kFailure; + } + return sdk::common::ExportResult::kSuccess; +} + +bool OtlpGrpcExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; +} + +bool OtlpGrpcExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_grpc_log_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_grpc_log_exporter.cc new file mode 100644 index 000000000..38bfb0a5b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_grpc_log_exporter.cc @@ -0,0 +1,181 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/exporters/otlp/otlp_grpc_log_exporter.h" +# include "opentelemetry/exporters/otlp/otlp_log_recordable.h" +# include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" + +// clang-format off + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +# include "opentelemetry/proto/collector/logs/v1/logs_service.pb.h" +# include "opentelemetry/proto/collector/logs/v1/logs_service.grpc.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +// clang-format on + +# include "opentelemetry/ext/http/common/url_parser.h" +# include "opentelemetry/sdk/common/global_log_handler.h" + +# include <chrono> +# include <fstream> +# include <memory> + +# include <grpcpp/grpcpp.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +// ----------------------------- Helper functions ------------------------------ +// TODO: move exporters shared function to OTLP common library. +static std::string get_file_contents(const char *fpath) +{ + std::ifstream finstream(fpath); + std::string contents; + contents.assign((std::istreambuf_iterator<char>(finstream)), std::istreambuf_iterator<char>()); + finstream.close(); + return contents; +} + +struct OtlpGrpcExporterOptions; + +/** + * Create gRPC channel from the exporter options. + */ +static std::shared_ptr<grpc::Channel> MakeGrpcChannel(const OtlpGrpcExporterOptions &options) +{ + std::shared_ptr<grpc::Channel> channel; + + // + // Scheme is allowed in OTLP endpoint definition, but is not allowed for creating gRPC channel. + // Passing URI with scheme to grpc::CreateChannel could resolve the endpoint to some unexpected + // address. + // + + ext::http::common::UrlParser url(options.endpoint); + if (!url.success_) + { + OTEL_INTERNAL_LOG_ERROR("[OTLP Exporter] invalid endpoint: " << options.endpoint); + + return nullptr; + } + + std::string grpc_target = url.host_ + ":" + std::to_string(static_cast<int>(url.port_)); + + if (options.use_ssl_credentials) + { + grpc::SslCredentialsOptions ssl_opts; + if (options.ssl_credentials_cacert_path.empty()) + { + ssl_opts.pem_root_certs = options.ssl_credentials_cacert_as_string; + } + else + { + ssl_opts.pem_root_certs = get_file_contents((options.ssl_credentials_cacert_path).c_str()); + } + channel = grpc::CreateChannel(grpc_target, grpc::SslCredentials(ssl_opts)); + } + else + { + channel = grpc::CreateChannel(grpc_target, grpc::InsecureChannelCredentials()); + } + + return channel; +} + +// ----------------------------- Helper functions ------------------------------ + +/** + * Create log service stub to communicate with the OpenTelemetry Collector. + */ +std::unique_ptr<::opentelemetry::proto::collector::logs::v1::LogsService::Stub> MakeLogServiceStub( + const OtlpGrpcExporterOptions &options) +{ + return proto::collector::logs::v1::LogsService::NewStub(MakeGrpcChannel(options)); +} + +// -------------------------------- Constructors -------------------------------- + +OtlpGrpcLogExporter::OtlpGrpcLogExporter() : OtlpGrpcLogExporter(OtlpGrpcExporterOptions()) {} + +OtlpGrpcLogExporter::OtlpGrpcLogExporter(const OtlpGrpcExporterOptions &options) + : options_(options), log_service_stub_(MakeLogServiceStub(options)) +{} + +OtlpGrpcLogExporter::OtlpGrpcLogExporter( + std::unique_ptr<proto::collector::logs::v1::LogsService::StubInterface> stub) + : options_(OtlpGrpcExporterOptions()), log_service_stub_(std::move(stub)) +{} + +// ----------------------------- Exporter methods ------------------------------ + +std::unique_ptr<opentelemetry::sdk::logs::Recordable> OtlpGrpcLogExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<opentelemetry::sdk::logs::Recordable>( + new exporter::otlp::OtlpLogRecordable()); +} + +opentelemetry::sdk::common::ExportResult OtlpGrpcLogExporter::Export( + const nostd::span<std::unique_ptr<opentelemetry::sdk::logs::Recordable>> &logs) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[OTLP gRPC log] Exporting " << logs.size() + << " log(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + if (logs.empty()) + { + return sdk::common::ExportResult::kSuccess; + } + + proto::collector::logs::v1::ExportLogsServiceRequest request; + OtlpRecordableUtils::PopulateRequest(logs, &request); + + grpc::ClientContext context; + proto::collector::logs::v1::ExportLogsServiceResponse response; + + if (options_.timeout.count() > 0) + { + context.set_deadline(std::chrono::system_clock::now() + options_.timeout); + } + + for (auto &header : options_.metadata) + { + context.AddMetadata(header.first, header.second); + } + grpc::Status status = log_service_stub_->Export(&context, request, &response); + + if (!status.ok()) + { + OTEL_INTERNAL_LOG_ERROR("[OTLP LOG GRPC Exporter] Export() failed: " << status.error_message()); + return sdk::common::ExportResult::kFailure; + } + return sdk::common::ExportResult::kSuccess; +} + +bool OtlpGrpcLogExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; +} + +bool OtlpGrpcLogExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_client.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_client.cc new file mode 100644 index 000000000..e3e1a8c46 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_client.cc @@ -0,0 +1,721 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_http_client.h" + +#if defined(HAVE_GSL) +# include <gsl/gsl> +#else +# include <assert.h> +#endif + +#include "opentelemetry/ext/http/client/http_client_factory.h" +#include "opentelemetry/ext/http/common/url_parser.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include <mutex> +#include "google/protobuf/message.h" +#include "google/protobuf/reflection.h" +#include "google/protobuf/stubs/common.h" +#include "google/protobuf/stubs/stringpiece.h" +#include "nlohmann/json.hpp" + +#if defined(GOOGLE_PROTOBUF_VERSION) && GOOGLE_PROTOBUF_VERSION >= 3007000 +# include "google/protobuf/stubs/strutil.h" +#else +# include "google/protobuf/stubs/port.h" +namespace google +{ +namespace protobuf +{ +LIBPROTOBUF_EXPORT void Base64Escape(StringPiece src, std::string *dest); +} // namespace protobuf +} // namespace google +#endif + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk_config.h" + +#include <condition_variable> +#include <fstream> +#include <mutex> +#include <sstream> +#include <string> +#include <vector> + +#ifdef GetMessage +# undef GetMessage +#endif + +namespace nostd = opentelemetry::nostd; +namespace http_client = opentelemetry::ext::http::client; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +namespace +{ + +/** + * 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<std::mutex> lk(mutex_); + + // Store the body of the request + body_ = std::string(response.GetBody().begin(), response.GetBody().end()); + + if (console_debug_) + { + std::stringstream ss; + ss << "[OTLP HTTP Client] Status:" << response.GetStatusCode() << "Header:"; + response.ForEachHeader([&ss](opentelemetry::nostd::string_view header_name, + opentelemetry::nostd::string_view header_value) { + ss << "\t" << header_name.data() << " : " << header_value.data() << ","; + return true; + }); + ss << "Body:" << body_; + OTEL_INTERNAL_LOG_DEBUG(ss.str()); + } + + // Set the response_received_ flag to true and notify any threads waiting on this result + response_received_ = true; + stop_waiting_ = true; + } + cv_.notify_all(); + } + + /**resource + * 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<std::mutex> lk(mutex_); + cv_.wait(lk, [this] { return stop_waiting_; }); + return response_received_; + } + + /** + * Returns the body of the response + */ + std::string GetResponseBody() + { + // Lock so that body_ can't be written to while returning it + std::unique_lock<std::mutex> lk(mutex_); + return body_; + } + + // Callback method when an http event occurs + void OnEvent(http_client::SessionState state, + opentelemetry::nostd::string_view reason) noexcept override + { + // need to modify stop_waiting_ under lock before calling notify_all + switch (state) + { + case http_client::SessionState::CreateFailed: + case http_client::SessionState::ConnectFailed: + case http_client::SessionState::SendFailed: + case http_client::SessionState::SSLHandshakeFailed: + case http_client::SessionState::TimedOut: + case http_client::SessionState::NetworkError: + case http_client::SessionState::Cancelled: { + std::unique_lock<std::mutex> lk(mutex_); + stop_waiting_ = true; + } + break; + + default: + break; + } + + // If any failure event occurs, release the condition variable to unblock main thread + switch (state) + { + case http_client::SessionState::CreateFailed: + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Session state: session create failed"); + cv_.notify_all(); + break; + + case http_client::SessionState::Created: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Session state: session created"); + } + break; + + case http_client::SessionState::Destroyed: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Session state: session destroyed"); + } + break; + + case http_client::SessionState::Connecting: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Session state: connecting to peer"); + } + break; + + case http_client::SessionState::ConnectFailed: + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Session state: connection failed"); + cv_.notify_all(); + break; + + case http_client::SessionState::Connected: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Session state: connected"); + } + break; + + case http_client::SessionState::Sending: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Session state: sending request"); + } + break; + + case http_client::SessionState::SendFailed: + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Session state: request send failed"); + cv_.notify_all(); + break; + + case http_client::SessionState::Response: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Session state: response received"); + } + break; + + case http_client::SessionState::SSLHandshakeFailed: + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Session state: SSL handshake failed"); + cv_.notify_all(); + break; + + case http_client::SessionState::TimedOut: + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Session state: request time out"); + cv_.notify_all(); + break; + + case http_client::SessionState::NetworkError: + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Session state: network error"); + cv_.notify_all(); + break; + + case http_client::SessionState::ReadError: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Session state: error reading response"); + } + break; + + case http_client::SessionState::WriteError: + if (console_debug_) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] DEBUG:Session state: error writing request"); + } + break; + + case http_client::SessionState::Cancelled: + OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Session state: (manually) cancelled\n"); + cv_.notify_all(); + break; + + default: + break; + } + } + +private: + // Define a condition variable and mutex + std::condition_variable cv_; + std::mutex mutex_; + + // Whether notify has been called + bool stop_waiting_ = false; + + // Whether the response has been received + bool response_received_ = false; + + // A string to store the response body + std::string body_ = ""; + + // Whether to print the results from the callback + bool console_debug_ = false; +}; + +static inline char HexEncode(unsigned char byte) +{ +#if defined(HAVE_GSL) + Expects(byte <= 16); +#else + assert(byte <= 16); +#endif + 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<unsigned char>(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, + JsonBytesMappingKind kind) +{ + switch (kind) + { + case JsonBytesMappingKind::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 JsonBytesMappingKind::kBase64: { + // Base64 is the default bytes mapping of protobuf + std::string base64_value; + google::protobuf::Base64Escape(bytes, &base64_value); + return base64_value; + } + case JsonBytesMappingKind::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 OtlpHttpClientOptions &options); + +static void ConvertListFieldToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const google::protobuf::FieldDescriptor *field_descriptor, + const OtlpHttpClientOptions &options); + +static void ConvertGenericMessageToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const OtlpHttpClientOptions &options) +{ + std::vector<const google::protobuf::FieldDescriptor *> 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); + } + } +} + +bool SerializeToHttpBody(http_client::Body &output, const google::protobuf::Message &message) +{ + auto body_size = message.ByteSizeLong(); + if (body_size > 0) + { + output.resize(body_size); + return message.SerializeWithCachedSizesToArray( + reinterpret_cast<google::protobuf::uint8 *>(&output[0])); + } + return true; +} + +void ConvertGenericFieldToJson(nlohmann::json &value, + const google::protobuf::Message &message, + const google::protobuf::FieldDescriptor *field_descriptor, + const OtlpHttpClientOptions &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: { + // 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: { + value = message.GetReflection()->GetUInt32(message, field_descriptor); + break; + } + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: { + // 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: { + 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 OtlpHttpClientOptions &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) + { + // 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; + } + 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) + { + // 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; + } + 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; + } + } +} + +} // namespace + +OtlpHttpClient::OtlpHttpClient(OtlpHttpClientOptions &&options) + : options_(options), http_client_(http_client::HttpClientFactory::Create()) +{} + +OtlpHttpClient::OtlpHttpClient(OtlpHttpClientOptions &&options, + std::shared_ptr<ext::http::client::HttpClient> http_client) + : options_(options), http_client_(http_client) +{} + +// ----------------------------- HTTP Client methods ------------------------------ +opentelemetry::sdk::common::ExportResult OtlpHttpClient::Export( + const google::protobuf::Message &message) noexcept +{ + // Return failure if this exporter has been shutdown + if (isShutdown()) + { + const char *error_message = "[OTLP HTTP Client] Export failed, exporter is shutdown"; + if (options_.console_debug) + { + std::cerr << error_message << std::endl; + } + OTEL_INTERNAL_LOG_ERROR(error_message); + + return opentelemetry::sdk::common::ExportResult::kFailure; + } + + // Parse uri and store it to cache + if (http_uri_.empty()) + { + auto parse_url = opentelemetry::ext::http::common::UrlParser(std::string(options_.url)); + if (!parse_url.success_) + { + std::string error_message = "[OTLP HTTP Client] Export failed, invalid url: " + options_.url; + if (options_.console_debug) + { + std::cerr << error_message << std::endl; + } + OTEL_INTERNAL_LOG_ERROR(error_message.c_str()); + + return opentelemetry::sdk::common::ExportResult::kFailure; + } + + if (!parse_url.path_.empty() && parse_url.path_[0] == '/') + { + http_uri_ = parse_url.path_.substr(1); + } + else + { + http_uri_ = parse_url.path_; + } + } + + http_client::Body body_vec; + std::string content_type; + if (options_.content_type == HttpRequestContentType::kBinary) + { + if (SerializeToHttpBody(body_vec, message)) + { + if (options_.console_debug) + { + OTEL_INTERNAL_LOG_DEBUG( + "[OTLP HTTP Client] Request body(Binary): " << message.Utf8DebugString()); + } + } + else + { + if (options_.console_debug) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Serialize body failed(Binary):" + << message.InitializationErrorString()); + } + return opentelemetry::sdk::common::ExportResult::kFailure; + } + content_type = kHttpBinaryContentType; + } + else + { + nlohmann::json json_request; + + // Convert from proto into json object + ConvertGenericMessageToJson(json_request, message, options_); + + std::string post_body_json = + json_request.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); + if (options_.console_debug) + { + OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Request body(Json)" << post_body_json); + } + body_vec.assign(post_body_json.begin(), post_body_json.end()); + content_type = kHttpJsonContentType; + } + + // Send the request + auto session = http_client_->CreateSession(options_.url); + auto request = session->CreateRequest(); + + for (auto &header : options_.http_headers) + { + request->AddHeader(header.first, header.second); + } + request->SetUri(http_uri_); + request->SetTimeoutMs(std::chrono::duration_cast<std::chrono::milliseconds>(options_.timeout)); + request->SetMethod(http_client::Method::Post); + request->SetBody(body_vec); + request->ReplaceHeader("Content-Type", content_type); + + // Send the request + std::unique_ptr<ResponseHandler> handler(new ResponseHandler(options_.console_debug)); + session->SendRequest(*handler); + + // Wait for the response to be received + if (options_.console_debug) + { + OTEL_INTERNAL_LOG_DEBUG( + "[OTLP HTTP Client] DEBUG: Waiting for response from " + << options_.url << " (timeout = " + << std::chrono::duration_cast<std::chrono::milliseconds>(options_.timeout).count() + << " milliseconds)"); + } + bool write_successful = handler->waitForResponse(); + + // End the session + session->FinishSession(); + + // If an error occurred with the HTTP request + if (!write_successful) + { + // TODO: retry logic + return opentelemetry::sdk::common::ExportResult::kFailure; + } + + return opentelemetry::sdk::common::ExportResult::kSuccess; +} + +bool OtlpHttpClient::Shutdown(std::chrono::microseconds) noexcept +{ + { + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + } + + // Shutdown the session manager + http_client_->CancelAllSessions(); + http_client_->FinishAllSessions(); + + return true; +} + +bool OtlpHttpClient::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_exporter.cc new file mode 100644 index 000000000..92155dd00 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_exporter.cc @@ -0,0 +1,66 @@ +// 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 "opentelemetry/exporters/otlp/otlp_recordable_utils.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +OtlpHttpExporter::OtlpHttpExporter() : OtlpHttpExporter(OtlpHttpExporterOptions()) {} + +OtlpHttpExporter::OtlpHttpExporter(const OtlpHttpExporterOptions &options) + : options_(options), + http_client_(new OtlpHttpClient(OtlpHttpClientOptions(options.url, + options.content_type, + options.json_bytes_mapping, + options.use_json_name, + options.console_debug, + options.timeout, + options.http_headers))) +{} + +OtlpHttpExporter::OtlpHttpExporter(std::unique_ptr<OtlpHttpClient> http_client) + : options_(OtlpHttpExporterOptions()), http_client_(std::move(http_client)) +{} +// ----------------------------- Exporter methods ------------------------------ + +std::unique_ptr<opentelemetry::sdk::trace::Recordable> OtlpHttpExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<opentelemetry::sdk::trace::Recordable>( + new exporter::otlp::OtlpRecordable()); +} + +opentelemetry::sdk::common::ExportResult OtlpHttpExporter::Export( + const nostd::span<std::unique_ptr<opentelemetry::sdk::trace::Recordable>> &spans) noexcept +{ + if (spans.empty()) + { + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + + proto::collector::trace::v1::ExportTraceServiceRequest service_request; + OtlpRecordableUtils::PopulateRequest(spans, &service_request); + return http_client_->Export(service_request); +} + +bool OtlpHttpExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + return http_client_->Shutdown(timeout); +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_log_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_log_exporter.cc new file mode 100644 index 000000000..436c77bea --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_http_log_exporter.cc @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/exporters/otlp/otlp_http_log_exporter.h" +# include "opentelemetry/exporters/otlp/otlp_log_recordable.h" +# include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +# include "opentelemetry/proto/collector/logs/v1/logs_service.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +OtlpHttpLogExporter::OtlpHttpLogExporter() : OtlpHttpLogExporter(OtlpHttpLogExporterOptions()) {} + +OtlpHttpLogExporter::OtlpHttpLogExporter(const OtlpHttpLogExporterOptions &options) + : options_(options), + http_client_(new OtlpHttpClient(OtlpHttpClientOptions(options.url, + options.content_type, + options.json_bytes_mapping, + options.use_json_name, + options.console_debug, + options.timeout, + options.http_headers))) +{} + +OtlpHttpLogExporter::OtlpHttpLogExporter(std::unique_ptr<OtlpHttpClient> http_client) + : options_(OtlpHttpLogExporterOptions()), http_client_(std::move(http_client)) +{} +// ----------------------------- Exporter methods ------------------------------ + +std::unique_ptr<opentelemetry::sdk::logs::Recordable> OtlpHttpLogExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<opentelemetry::sdk::logs::Recordable>( + new exporter::otlp::OtlpLogRecordable()); +} + +opentelemetry::sdk::common::ExportResult OtlpHttpLogExporter::Export( + const nostd::span<std::unique_ptr<opentelemetry::sdk::logs::Recordable>> &logs) noexcept +{ + if (logs.empty()) + { + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + proto::collector::logs::v1::ExportLogsServiceRequest service_request; + OtlpRecordableUtils::PopulateRequest(logs, &service_request); + return http_client_->Export(service_request); +} + +bool OtlpHttpLogExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + return http_client_->Shutdown(timeout); +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_log_recordable.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_log_recordable.cc new file mode 100644 index 000000000..d38fe241f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_log_recordable.cc @@ -0,0 +1,235 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/common/macros.h" + +# include "opentelemetry/exporters/otlp/otlp_log_recordable.h" + +# include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" + +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +proto::resource::v1::Resource OtlpLogRecordable::ProtoResource() const noexcept +{ + proto::resource::v1::Resource proto; + if (nullptr == resource_) + { + OtlpRecordableUtils::PopulateAttribute(&proto, sdk::resource::Resource::GetDefault()); + } + else + { + OtlpRecordableUtils::PopulateAttribute(&proto, *resource_); + } + + return proto; +} + +void OtlpLogRecordable::SetTimestamp(opentelemetry::common::SystemTimestamp timestamp) noexcept +{ + log_record_.set_time_unix_nano(timestamp.time_since_epoch().count()); +} + +void OtlpLogRecordable::SetSeverity(opentelemetry::logs::Severity severity) noexcept +{ + switch (severity) + { + case opentelemetry::logs::Severity::kTrace: { + log_record_.set_severity_text("TRACE"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE); + break; + } + case opentelemetry::logs::Severity::kTrace2: { + log_record_.set_severity_text("TRACE2"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE2); + break; + } + case opentelemetry::logs::Severity::kTrace3: { + log_record_.set_severity_text("TRACE3"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE3); + break; + } + case opentelemetry::logs::Severity::kTrace4: { + log_record_.set_severity_text("TRACE4"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_TRACE4); + break; + } + case opentelemetry::logs::Severity::kDebug: { + log_record_.set_severity_text("DEBUG"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG); + break; + } + case opentelemetry::logs::Severity::kDebug2: { + log_record_.set_severity_text("DEBUG2"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG2); + break; + } + case opentelemetry::logs::Severity::kDebug3: { + log_record_.set_severity_text("DEBUG3"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG3); + break; + } + case opentelemetry::logs::Severity::kDebug4: { + log_record_.set_severity_text("DEBUG4"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_DEBUG4); + break; + } + case opentelemetry::logs::Severity::kInfo: { + log_record_.set_severity_text("INFO"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO); + break; + } + case opentelemetry::logs::Severity::kInfo2: { + log_record_.set_severity_text("INFO2"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO2); + break; + } + case opentelemetry::logs::Severity::kInfo3: { + log_record_.set_severity_text("INFO3"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO3); + break; + } + case opentelemetry::logs::Severity::kInfo4: { + log_record_.set_severity_text("INFO4"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_INFO4); + break; + } + case opentelemetry::logs::Severity::kWarn: { + log_record_.set_severity_text("WARN"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN); + break; + } + case opentelemetry::logs::Severity::kWarn2: { + log_record_.set_severity_text("WARN2"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN2); + break; + } + case opentelemetry::logs::Severity::kWarn3: { + log_record_.set_severity_text("WARN3"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN3); + break; + } + case opentelemetry::logs::Severity::kWarn4: { + log_record_.set_severity_text("WARN4"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_WARN4); + break; + } + case opentelemetry::logs::Severity::kError: { + log_record_.set_severity_text("ERROR"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR); + break; + } + case opentelemetry::logs::Severity::kError2: { + log_record_.set_severity_text("ERROR2"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR2); + break; + } + case opentelemetry::logs::Severity::kError3: { + log_record_.set_severity_text("ERROR3"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR3); + break; + } + case opentelemetry::logs::Severity::kError4: { + log_record_.set_severity_text("ERROR4"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_ERROR4); + break; + } + case opentelemetry::logs::Severity::kFatal: { + log_record_.set_severity_text("FATAL"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL); + break; + } + case opentelemetry::logs::Severity::kFatal2: { + log_record_.set_severity_text("FATAL2"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL2); + break; + } + case opentelemetry::logs::Severity::kFatal3: { + log_record_.set_severity_text("FATAL3"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL3); + break; + } + case opentelemetry::logs::Severity::kFatal4: { + log_record_.set_severity_text("FATAL4"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_FATAL4); + break; + } + default: { + log_record_.set_severity_text("INVALID"); + log_record_.set_severity_number(proto::logs::v1::SEVERITY_NUMBER_UNSPECIFIED); + break; + } + } +} + +void OtlpLogRecordable::SetBody(nostd::string_view message) noexcept +{ + log_record_.mutable_body()->set_string_value(message.data(), message.size()); +} + +void OtlpLogRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept +{ + resource_ = &resource; +} + +const opentelemetry::sdk::resource::Resource &OtlpLogRecordable::GetResource() const noexcept +{ + OPENTELEMETRY_LIKELY_IF(nullptr != resource_) { return *resource_; } + + return opentelemetry::sdk::resource::Resource::GetDefault(); +} + +void OtlpLogRecordable::SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept +{ + OtlpRecordableUtils::PopulateAttribute(log_record_.add_attributes(), key, value); +} + +void OtlpLogRecordable::SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept +{ + log_record_.set_trace_id(reinterpret_cast<const char *>(trace_id.Id().data()), + trace::TraceId::kSize); +} + +void OtlpLogRecordable::SetSpanId(opentelemetry::trace::SpanId span_id) noexcept +{ + log_record_.set_span_id(reinterpret_cast<const char *>(span_id.Id().data()), + trace::SpanId::kSize); +} + +void OtlpLogRecordable::SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept +{ + log_record_.set_flags(trace_flags.flags()); +} + +void OtlpLogRecordable::SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept +{ + instrumentation_library_ = &instrumentation_library; +} + +const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary & +OtlpLogRecordable::GetInstrumentationLibrary() const noexcept +{ + OPENTELEMETRY_LIKELY_IF(nullptr != instrumentation_library_) { return *instrumentation_library_; } + + static opentelemetry::nostd::unique_ptr< + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary> + default_instrumentation = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create( + "default", "1.0.0", "https://opentelemetry.io/schemas/1.11.0"); + return *default_instrumentation; +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_recordable.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_recordable.cc new file mode 100644 index 000000000..0e6aa8f8a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_recordable.cc @@ -0,0 +1,187 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_recordable.h" + +#include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" + +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +void OtlpRecordable::SetIdentity(const opentelemetry::trace::SpanContext &span_context, + opentelemetry::trace::SpanId parent_span_id) noexcept +{ + span_.set_trace_id(reinterpret_cast<const char *>(span_context.trace_id().Id().data()), + trace::TraceId::kSize); + span_.set_span_id(reinterpret_cast<const char *>(span_context.span_id().Id().data()), + trace::SpanId::kSize); + if (parent_span_id.IsValid()) + { + span_.set_parent_span_id(reinterpret_cast<const char *>(parent_span_id.Id().data()), + trace::SpanId::kSize); + } + span_.set_trace_state(span_context.trace_state()->ToHeader()); +} + +proto::resource::v1::Resource OtlpRecordable::ProtoResource() const noexcept +{ + proto::resource::v1::Resource proto; + if (resource_) + { + OtlpRecordableUtils::PopulateAttribute(&proto, *resource_); + } + + return proto; +} + +const std::string OtlpRecordable::GetResourceSchemaURL() const noexcept +{ + std::string schema_url; + if (resource_) + { + schema_url = resource_->GetSchemaURL(); + } + + return schema_url; +} + +const std::string OtlpRecordable::GetInstrumentationLibrarySchemaURL() const noexcept +{ + std::string schema_url; + if (instrumentation_library_) + { + schema_url = instrumentation_library_->GetSchemaURL(); + } + + return schema_url; +} + +proto::common::v1::InstrumentationLibrary OtlpRecordable::GetProtoInstrumentationLibrary() + const noexcept +{ + proto::common::v1::InstrumentationLibrary instrumentation_library; + if (instrumentation_library_) + { + instrumentation_library.set_name(instrumentation_library_->GetName()); + instrumentation_library.set_version(instrumentation_library_->GetVersion()); + } + return instrumentation_library; +} + +void OtlpRecordable::SetResource(const sdk::resource::Resource &resource) noexcept +{ + resource_ = &resource; +}; + +void OtlpRecordable::SetAttribute(nostd::string_view key, + const common::AttributeValue &value) noexcept +{ + auto *attribute = span_.add_attributes(); + OtlpRecordableUtils::PopulateAttribute(attribute, key, value); +} + +void OtlpRecordable::AddEvent(nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept +{ + auto *event = span_.add_events(); + event->set_name(name.data(), name.size()); + event->set_time_unix_nano(timestamp.time_since_epoch().count()); + + attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + OtlpRecordableUtils::PopulateAttribute(event->add_attributes(), key, value); + return true; + }); +} + +void OtlpRecordable::AddLink(const trace::SpanContext &span_context, + const common::KeyValueIterable &attributes) noexcept +{ + auto *link = span_.add_links(); + link->set_trace_id(reinterpret_cast<const char *>(span_context.trace_id().Id().data()), + trace::TraceId::kSize); + link->set_span_id(reinterpret_cast<const char *>(span_context.span_id().Id().data()), + trace::SpanId::kSize); + link->set_trace_state(span_context.trace_state()->ToHeader()); + attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + OtlpRecordableUtils::PopulateAttribute(link->add_attributes(), key, value); + return true; + }); +} + +void OtlpRecordable::SetStatus(trace::StatusCode code, nostd::string_view description) noexcept +{ + span_.mutable_status()->set_code(proto::trace::v1::Status_StatusCode(code)); + if (code == trace::StatusCode::kError) + { + span_.mutable_status()->set_message(description.data(), description.size()); + } +} + +void OtlpRecordable::SetName(nostd::string_view name) noexcept +{ + span_.set_name(name.data(), name.size()); +} + +void OtlpRecordable::SetSpanKind(trace::SpanKind span_kind) noexcept +{ + proto::trace::v1::Span_SpanKind proto_span_kind = + proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_UNSPECIFIED; + + switch (span_kind) + { + + case trace::SpanKind::kInternal: + proto_span_kind = proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_INTERNAL; + break; + + case trace::SpanKind::kServer: + proto_span_kind = proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_SERVER; + break; + + case trace::SpanKind::kClient: + proto_span_kind = proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_CLIENT; + break; + + case trace::SpanKind::kProducer: + proto_span_kind = proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_PRODUCER; + break; + + case trace::SpanKind::kConsumer: + proto_span_kind = proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_CONSUMER; + break; + + default: + // shouldn't reach here. + proto_span_kind = proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_UNSPECIFIED; + } + + span_.set_kind(proto_span_kind); +} + +void OtlpRecordable::SetStartTime(common::SystemTimestamp start_time) noexcept +{ + span_.set_start_time_unix_nano(start_time.time_since_epoch().count()); +} + +void OtlpRecordable::SetDuration(std::chrono::nanoseconds duration) noexcept +{ + const uint64_t unix_end_time = span_.start_time_unix_nano() + duration.count(); + span_.set_end_time_unix_nano(unix_end_time); +} + +void OtlpRecordable::SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept +{ + instrumentation_library_ = &instrumentation_library; +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_recordable_utils.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_recordable_utils.cc new file mode 100644 index 000000000..bb3c84604 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/src/otlp_recordable_utils.cc @@ -0,0 +1,362 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +#include "opentelemetry/proto/logs/v1/logs.pb.h" +#include "opentelemetry/proto/trace/v1/trace.pb.h" + +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +#include "opentelemetry/exporters/otlp/otlp_log_recordable.h" +#include "opentelemetry/exporters/otlp/otlp_recordable.h" + +#include <list> +#include <unordered_map> + +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +namespace +{ +struct InstrumentationLibraryPointerHasher +{ + std::size_t operator()(const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + *instrumentation) const noexcept + { + return instrumentation->HashCode(); + } +}; + +struct InstrumentationLibraryPointerEqual +{ + std::size_t operator()( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary *left, + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary *right) + const noexcept + { + return *left == *right; + } +}; +} // namespace + +// +// See `attribute_value.h` for details. +// +const int kAttributeValueSize = 16; +const int kOwnedAttributeValueSize = 15; + +void OtlpRecordableUtils::PopulateAttribute( + opentelemetry::proto::common::v1::KeyValue *attribute, + nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept +{ + if (nullptr == attribute) + { + return; + } + + // Assert size of variant to ensure that this method gets updated if the variant + // definition changes + static_assert( + nostd::variant_size<opentelemetry::common::AttributeValue>::value == kAttributeValueSize, + "AttributeValue contains unknown type"); + + attribute->set_key(key.data(), key.size()); + + if (nostd::holds_alternative<bool>(value)) + { + attribute->mutable_value()->set_bool_value(nostd::get<bool>(value)); + } + else if (nostd::holds_alternative<int>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<int>(value)); + } + else if (nostd::holds_alternative<int64_t>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<int64_t>(value)); + } + else if (nostd::holds_alternative<unsigned int>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<unsigned int>(value)); + } + else if (nostd::holds_alternative<uint64_t>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<uint64_t>(value)); + } + else if (nostd::holds_alternative<double>(value)) + { + attribute->mutable_value()->set_double_value(nostd::get<double>(value)); + } + else if (nostd::holds_alternative<const char *>(value)) + { + attribute->mutable_value()->set_string_value(nostd::get<const char *>(value)); + } + else if (nostd::holds_alternative<nostd::string_view>(value)) + { + attribute->mutable_value()->set_string_value(nostd::get<nostd::string_view>(value).data(), + nostd::get<nostd::string_view>(value).size()); + } + else if (nostd::holds_alternative<nostd::span<const uint8_t>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const uint8_t>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<nostd::span<const bool>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const bool>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_bool_value(val); + } + } + else if (nostd::holds_alternative<nostd::span<const int>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const int>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<nostd::span<const int64_t>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const int64_t>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<nostd::span<const unsigned int>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const unsigned int>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<nostd::span<const uint64_t>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const uint64_t>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<nostd::span<const double>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const double>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_double_value(val); + } + } + else if (nostd::holds_alternative<nostd::span<const nostd::string_view>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const nostd::string_view>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_string_value(val.data(), + val.size()); + } + } +} + +/** Maps from C++ attribute into OTLP proto attribute. */ +void OtlpRecordableUtils::PopulateAttribute( + opentelemetry::proto::common::v1::KeyValue *attribute, + nostd::string_view key, + const opentelemetry::sdk::common::OwnedAttributeValue &value) noexcept +{ + if (nullptr == attribute) + { + return; + } + + // Assert size of variant to ensure that this method gets updated if the variant + // definition changes + static_assert(nostd::variant_size<opentelemetry::sdk::common::OwnedAttributeValue>::value == + kOwnedAttributeValueSize, + "OwnedAttributeValue contains unknown type"); + + attribute->set_key(key.data(), key.size()); + + if (nostd::holds_alternative<bool>(value)) + { + attribute->mutable_value()->set_bool_value(nostd::get<bool>(value)); + } + else if (nostd::holds_alternative<int32_t>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<int32_t>(value)); + } + else if (nostd::holds_alternative<int64_t>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<int64_t>(value)); + } + else if (nostd::holds_alternative<uint32_t>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<uint32_t>(value)); + } + else if (nostd::holds_alternative<uint64_t>(value)) + { + attribute->mutable_value()->set_int_value(nostd::get<uint64_t>(value)); + } + else if (nostd::holds_alternative<double>(value)) + { + attribute->mutable_value()->set_double_value(nostd::get<double>(value)); + } + else if (nostd::holds_alternative<std::string>(value)) + { + attribute->mutable_value()->set_string_value(nostd::get<std::string>(value)); + } + else if (nostd::holds_alternative<std::vector<bool>>(value)) + { + for (const auto &val : nostd::get<std::vector<bool>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_bool_value(val); + } + } + else if (nostd::holds_alternative<std::vector<int32_t>>(value)) + { + for (const auto &val : nostd::get<std::vector<int32_t>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<std::vector<uint32_t>>(value)) + { + for (const auto &val : nostd::get<std::vector<uint32_t>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<std::vector<int64_t>>(value)) + { + for (const auto &val : nostd::get<std::vector<int64_t>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<std::vector<uint64_t>>(value)) + { + for (const auto &val : nostd::get<std::vector<uint64_t>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_int_value(val); + } + } + else if (nostd::holds_alternative<std::vector<double>>(value)) + { + for (const auto &val : nostd::get<std::vector<double>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_double_value(val); + } + } + else if (nostd::holds_alternative<std::vector<std::string>>(value)) + { + for (const auto &val : nostd::get<std::vector<std::string>>(value)) + { + attribute->mutable_value()->mutable_array_value()->add_values()->set_string_value(val); + } + } +} + +void OtlpRecordableUtils::PopulateAttribute( + opentelemetry::proto::resource::v1::Resource *proto, + const opentelemetry::sdk::resource::Resource &resource) noexcept +{ + if (nullptr == proto) + { + return; + } + + for (const auto &kv : resource.GetAttributes()) + { + OtlpRecordableUtils::PopulateAttribute(proto->add_attributes(), kv.first, kv.second); + } +} + +void OtlpRecordableUtils::PopulateRequest( + const nostd::span<std::unique_ptr<opentelemetry::sdk::trace::Recordable>> &spans, + proto::collector::trace::v1::ExportTraceServiceRequest *request) noexcept +{ + if (nullptr == request) + { + return; + } + + for (auto &recordable : spans) + { + auto rec = std::unique_ptr<OtlpRecordable>(static_cast<OtlpRecordable *>(recordable.release())); + auto resource_span = request->add_resource_spans(); + auto instrumentation_lib = resource_span->add_instrumentation_library_spans(); + + *instrumentation_lib->add_spans() = std::move(rec->span()); + *instrumentation_lib->mutable_instrumentation_library() = rec->GetProtoInstrumentationLibrary(); + + instrumentation_lib->set_schema_url(rec->GetInstrumentationLibrarySchemaURL()); + + *resource_span->mutable_resource() = rec->ProtoResource(); + resource_span->set_schema_url(rec->GetResourceSchemaURL()); + } +} + +#ifdef ENABLE_LOGS_PREVIEW +void OtlpRecordableUtils::PopulateRequest( + const nostd::span<std::unique_ptr<opentelemetry::sdk::logs::Recordable>> &logs, + proto::collector::logs::v1::ExportLogsServiceRequest *request) noexcept +{ + if (nullptr == request) + { + return; + } + + using logs_index_by_instrumentation_type = + std::unordered_map<const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary *, + std::list<std::unique_ptr<OtlpLogRecordable>>, + InstrumentationLibraryPointerHasher, InstrumentationLibraryPointerEqual>; + std::unordered_map<const opentelemetry::sdk::resource::Resource *, + logs_index_by_instrumentation_type> + logs_index_by_resource; + + for (auto &recordable : logs) + { + auto rec = + std::unique_ptr<OtlpLogRecordable>(static_cast<OtlpLogRecordable *>(recordable.release())); + auto instrumentation = &rec->GetInstrumentationLibrary(); + auto resource = &rec->GetResource(); + + logs_index_by_resource[resource][instrumentation].emplace_back(std::move(rec)); + } + + for (auto &input_resource_log : logs_index_by_resource) + { + auto output_resource_log = request->add_resource_logs(); + for (auto &input_scope_log : input_resource_log.second) + { + auto output_scope_log = output_resource_log->add_scope_logs(); + for (auto &input_log_record : input_scope_log.second) + { + if (!output_resource_log->has_resource()) + { + *output_resource_log->mutable_resource() = input_log_record->ProtoResource(); + output_resource_log->set_schema_url(input_resource_log.first->GetSchemaURL()); + } + + if (!output_scope_log->has_scope()) + { + output_scope_log->mutable_scope()->set_name(input_scope_log.first->GetName()); + output_scope_log->mutable_scope()->set_version(input_scope_log.first->GetVersion()); + output_scope_log->set_schema_url(input_scope_log.first->GetSchemaURL()); + } + + *output_scope_log->add_log_records() = std::move(input_log_record->log_record()); + } + } + } +} +#endif + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_exporter_benchmark.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_exporter_benchmark.cc new file mode 100644 index 000000000..c86f5e53b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_exporter_benchmark.cc @@ -0,0 +1,216 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_grpc_exporter.h" +#include "opentelemetry/exporters/otlp/otlp_recordable.h" + +#include <benchmark/benchmark.h> +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +#ifdef BAZEL_BUILD +# include "examples/common/foo_library/foo_library.h" +#else +# include "foo_library/foo_library.h" +#endif + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace otlp = opentelemetry::exporter::otlp; + +#include <benchmark/benchmark.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +namespace trace_api = opentelemetry::trace; + +const int kBatchSize = 200; +const int kNumAttributes = 5; +const int kNumIterations = 1000; + +const trace::TraceId kTraceId(std::array<const uint8_t, trace::TraceId::kSize>( + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})); +const trace::SpanId kSpanId(std::array<const uint8_t, trace::SpanId::kSize>({0, 0, 0, 0, 0, 0, 0, + 2})); +const trace::SpanId kParentSpanId(std::array<const uint8_t, trace::SpanId::kSize>({0, 0, 0, 0, 0, 0, + 0, 3})); +const auto kTraceState = trace_api::TraceState::GetDefault() -> Set("key1", "value"); +const trace_api::SpanContext kSpanContext{ + kTraceId, kSpanId, trace_api::TraceFlags{trace_api::TraceFlags::kIsSampled}, true, kTraceState}; + +// ----------------------- Helper classes and functions ------------------------ + +// Create a fake service stub to avoid dependency on gmock +class FakeServiceStub : public proto::collector::trace::v1::TraceService::StubInterface +{ + grpc::Status Export(grpc::ClientContext *, + const proto::collector::trace::v1::ExportTraceServiceRequest &, + proto::collector::trace::v1::ExportTraceServiceResponse *) override + { + return grpc::Status::OK; + } + + grpc::ClientAsyncResponseReaderInterface<proto::collector::trace::v1::ExportTraceServiceResponse> + *AsyncExportRaw(grpc::ClientContext *, + const proto::collector::trace::v1::ExportTraceServiceRequest &, + grpc::CompletionQueue *) override + { + return nullptr; + } + + grpc::ClientAsyncResponseReaderInterface<proto::collector::trace::v1::ExportTraceServiceResponse> + *PrepareAsyncExportRaw(grpc::ClientContext *, + const proto::collector::trace::v1::ExportTraceServiceRequest &, + grpc::CompletionQueue *) override + { + return nullptr; + } +}; + +// OtlpGrpcExporterTestPeer is a friend class of OtlpGrpcExporter +class OtlpGrpcExporterTestPeer +{ +public: + std::unique_ptr<sdk::trace::SpanExporter> GetExporter() + { + auto mock_stub = new FakeServiceStub(); + std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> stub_interface( + mock_stub); + return std::unique_ptr<sdk::trace::SpanExporter>( + new exporter::otlp::OtlpGrpcExporter(std::move(stub_interface))); + } +}; + +// Helper function to create empty spans +void CreateEmptySpans(std::array<std::unique_ptr<sdk::trace::Recordable>, kBatchSize> &recordables) +{ + for (int i = 0; i < kBatchSize; i++) + { + auto recordable = std::unique_ptr<sdk::trace::Recordable>(new OtlpRecordable); + recordables[i] = std::move(recordable); + } +} + +// Helper function to create sparse spans +void CreateSparseSpans(std::array<std::unique_ptr<sdk::trace::Recordable>, kBatchSize> &recordables) +{ + for (int i = 0; i < kBatchSize; i++) + { + auto recordable = std::unique_ptr<sdk::trace::Recordable>(new OtlpRecordable); + + recordable->SetIdentity(kSpanContext, kParentSpanId); + recordable->SetName("TestSpan"); + recordable->SetStartTime(common::SystemTimestamp(std::chrono::system_clock::now())); + recordable->SetDuration(std::chrono::nanoseconds(10)); + + recordables[i] = std::move(recordable); + } +} + +// Helper function to create dense spans +void CreateDenseSpans(std::array<std::unique_ptr<sdk::trace::Recordable>, kBatchSize> &recordables) +{ + for (int i = 0; i < kBatchSize; i++) + { + auto recordable = std::unique_ptr<sdk::trace::Recordable>(new OtlpRecordable); + + recordable->SetIdentity(kSpanContext, kParentSpanId); + recordable->SetName("TestSpan"); + recordable->SetStartTime(common::SystemTimestamp(std::chrono::system_clock::now())); + recordable->SetDuration(std::chrono::nanoseconds(10)); + + for (int i = 0; i < kNumAttributes; i++) + { + recordable->SetAttribute("int_key_" + i, static_cast<int64_t>(i)); + recordable->SetAttribute("str_key_" + i, "string_val_" + i); + recordable->SetAttribute("bool_key_" + i, true); + } + + recordables[i] = std::move(recordable); + } +} + +// ------------------------------ Benchmark tests ------------------------------ + +// Benchmark Export() with empty spans +void BM_OtlpExporterEmptySpans(benchmark::State &state) +{ + std::unique_ptr<OtlpGrpcExporterTestPeer> testpeer(new OtlpGrpcExporterTestPeer()); + auto exporter = testpeer->GetExporter(); + + while (state.KeepRunningBatch(kNumIterations)) + { + std::array<std::unique_ptr<sdk::trace::Recordable>, kBatchSize> recordables; + CreateEmptySpans(recordables); + exporter->Export(nostd::span<std::unique_ptr<sdk::trace::Recordable>>(recordables)); + } +} +BENCHMARK(BM_OtlpExporterEmptySpans); + +// Benchmark Export() with sparse spans +void BM_OtlpExporterSparseSpans(benchmark::State &state) +{ + std::unique_ptr<OtlpGrpcExporterTestPeer> testpeer(new OtlpGrpcExporterTestPeer()); + auto exporter = testpeer->GetExporter(); + + while (state.KeepRunningBatch(kNumIterations)) + { + std::array<std::unique_ptr<sdk::trace::Recordable>, kBatchSize> recordables; + CreateSparseSpans(recordables); + exporter->Export(nostd::span<std::unique_ptr<sdk::trace::Recordable>>(recordables)); + } +} +BENCHMARK(BM_OtlpExporterSparseSpans); + +// Benchmark Export() with dense spans +void BM_OtlpExporterDenseSpans(benchmark::State &state) +{ + std::unique_ptr<OtlpGrpcExporterTestPeer> testpeer(new OtlpGrpcExporterTestPeer()); + auto exporter = testpeer->GetExporter(); + + while (state.KeepRunningBatch(kNumIterations)) + { + std::array<std::unique_ptr<sdk::trace::Recordable>, kBatchSize> recordables; + CreateDenseSpans(recordables); + exporter->Export(nostd::span<std::unique_ptr<sdk::trace::Recordable>>(recordables)); + } +} +BENCHMARK(BM_OtlpExporterDenseSpans); + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +namespace +{ +opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts; +void InitTracer() +{ + // Create OTLP exporter instance + auto exporter = std::unique_ptr<trace_sdk::SpanExporter>(new otlp::OtlpGrpcExporter(opts)); + auto processor = std::unique_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + auto provider = + nostd::shared_ptr<trace::TracerProvider>(new trace_sdk::TracerProvider(std::move(processor))); + // Set the global trace provider + trace::Provider::SetTracerProvider(provider); +} + +void BM_otlp_grpc_with_collector(benchmark::State &state) +{ + InitTracer(); + while (state.KeepRunning()) + { + foo_library(); + } +} +BENCHMARK(BM_otlp_grpc_with_collector); +} // namespace + +BENCHMARK_MAIN(); diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_exporter_test.cc new file mode 100755 index 000000000..091cb0d0c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_exporter_test.cc @@ -0,0 +1,217 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef HAVE_CPP_STDLIB +// Unfortunately as of 04/27/2021 the fix is NOT in the vcpkg snapshot of Google Test. +// Remove above `#ifdef` once the GMock fix for C++20 is in the mainline. +// +// Please refer to this GitHub issue for additional details: +// https://github.com/google/googletest/issues/2914 +// https://github.com/google/googletest/commit/61f010d703b32de9bfb20ab90ece38ab2f25977f +// +// If we compile using Visual Studio 2019 with `c++latest` (C++20) without the GMock fix, +// then the compilation here fails in `gmock-actions.h` from: +// .\tools\vcpkg\installed\x64-windows\include\gmock\gmock-actions.h(819): +// error C2653: 'result_of': is not a class or namespace name +// +// That is because `std::result_of` has been removed in C++20. + +# include "opentelemetry/exporters/otlp/otlp_grpc_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/sdk/trace/simple_processor.h" +# include "opentelemetry/sdk/trace/tracer_provider.h" +# include "opentelemetry/trace/provider.h" + +# include <gtest/gtest.h> + +# if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +# endif + +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +class OtlpGrpcExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr<sdk::trace::SpanExporter> GetExporter( + std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> &stub_interface) + { + return std::unique_ptr<sdk::trace::SpanExporter>( + new OtlpGrpcExporter(std::move(stub_interface))); + } + + // Get the options associated with the given exporter. + const OtlpGrpcExporterOptions &GetOptions(std::unique_ptr<OtlpGrpcExporter> &exporter) + { + return exporter->options_; + } +}; + +TEST_F(OtlpGrpcExporterTestPeer, ShutdownTest) +{ + auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); + std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> 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"); + + // exporter shuold not be shutdown by default + nostd::span<std::unique_ptr<sdk::trace::Recordable>> 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); + + exporter->Shutdown(); + + nostd::span<std::unique_ptr<sdk::trace::Recordable>> batch_2(&recordable_2, 1); + result = exporter->Export(batch_2); + EXPECT_EQ(sdk::common::ExportResult::kFailure, result); +} + +// Call Export() directly +TEST_F(OtlpGrpcExporterTestPeer, ExportUnitTest) +{ + auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); + std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> 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<std::unique_ptr<sdk::trace::Recordable>> 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<std::unique_ptr<sdk::trace::Recordable>> 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(OtlpGrpcExporterTestPeer, ExportIntegrationTest) +{ + auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); + std::unique_ptr<proto::collector::trace::v1::TraceService::StubInterface> stub_interface( + mock_stub); + + auto exporter = GetExporter(stub_interface); + + auto processor = std::unique_ptr<sdk::trace::SpanProcessor>( + new sdk::trace::SimpleSpanProcessor(std::move(exporter))); + auto provider = nostd::shared_ptr<trace::TracerProvider>( + 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(OtlpGrpcExporterTestPeer, ConfigTest) +{ + OtlpGrpcExporterOptions opts; + opts.endpoint = "localhost:45454"; + std::unique_ptr<OtlpGrpcExporter> exporter(new OtlpGrpcExporter(opts)); + EXPECT_EQ(GetOptions(exporter).endpoint, "localhost:45454"); +} + +// Test exporter configuration options with use_ssl_credentials +TEST_F(OtlpGrpcExporterTestPeer, 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<OtlpGrpcExporter> exporter(new OtlpGrpcExporter(opts)); + EXPECT_EQ(GetOptions(exporter).ssl_credentials_cacert_as_string, cacert_str); + EXPECT_EQ(GetOptions(exporter).use_ssl_credentials, true); +} + +# ifndef NO_GETENV +// Test exporter configuration options with use_ssl_credentials +TEST_F(OtlpGrpcExporterTestPeer, ConfigFromEnv) +{ + const std::string cacert_str = "--begin and end fake cert--"; + setenv("OTEL_EXPORTER_OTLP_CERTIFICATE_STRING", cacert_str.c_str(), 1); + setenv("OTEL_EXPORTER_OTLP_SSL_ENABLE", "True", 1); + const std::string endpoint = "http://localhost:9999"; + setenv("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint.c_str(), 1); + setenv("OTEL_EXPORTER_OTLP_TIMEOUT", "20050ms", 1); + setenv("OTEL_EXPORTER_OTLP_HEADERS", "k1=v1,k2=v2", 1); + setenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "k1=v3,k1=v4", 1); + + std::unique_ptr<OtlpGrpcExporter> exporter(new OtlpGrpcExporter()); + EXPECT_EQ(GetOptions(exporter).ssl_credentials_cacert_as_string, cacert_str); + EXPECT_EQ(GetOptions(exporter).use_ssl_credentials, true); + EXPECT_EQ(GetOptions(exporter).endpoint, endpoint); + EXPECT_EQ(GetOptions(exporter).timeout.count(), + std::chrono::duration_cast<std::chrono::system_clock::duration>( + std::chrono::milliseconds{20050}) + .count()); + EXPECT_EQ(GetOptions(exporter).metadata.size(), 3); + { + // Test k2 + auto range = GetOptions(exporter).metadata.equal_range("k2"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v2")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + { + // Test k1 + auto range = GetOptions(exporter).metadata.equal_range("k1"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v3")); + ++range.first; + EXPECT_EQ(range.first->second, std::string("v4")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + + unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT"); + unsetenv("OTEL_EXPORTER_OTLP_CERTIFICATE_STRING"); + unsetenv("OTEL_EXPORTER_OTLP_SSL_ENABLE"); + unsetenv("OTEL_EXPORTER_OTLP_TIMEOUT"); + unsetenv("OTEL_EXPORTER_OTLP_HEADERS"); + unsetenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS"); +} +# endif + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_log_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_log_exporter_test.cc new file mode 100644 index 000000000..b60aac61a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_grpc_log_exporter_test.cc @@ -0,0 +1,163 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/exporters/otlp/otlp_grpc_log_exporter.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +# include "opentelemetry/proto/collector/logs/v1/logs_service_mock.grpc.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +# include "opentelemetry/logs/provider.h" +# include "opentelemetry/sdk/logs/batch_log_processor.h" +# include "opentelemetry/sdk/logs/exporter.h" +# include "opentelemetry/sdk/logs/log_record.h" +# include "opentelemetry/sdk/logs/logger_provider.h" +# include "opentelemetry/sdk/resource/resource.h" + +# include <gtest/gtest.h> + +# if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +# endif + +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +class OtlpGrpcLogExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr<sdk::logs::LogExporter> GetExporter( + std::unique_ptr<proto::collector::logs::v1::LogsService::StubInterface> &stub_interface) + { + return std::unique_ptr<sdk::logs::LogExporter>( + new OtlpGrpcLogExporter(std::move(stub_interface))); + } + + // Get the options associated with the given exporter. + const OtlpGrpcExporterOptions &GetOptions(std::unique_ptr<OtlpGrpcLogExporter> &exporter) + { + return exporter->options_; + } +}; + +TEST_F(OtlpGrpcLogExporterTestPeer, ShutdownTest) +{ + auto mock_stub = new proto::collector::logs::v1::MockLogsServiceStub(); + std::unique_ptr<proto::collector::logs::v1::LogsService::StubInterface> stub_interface(mock_stub); + auto exporter = GetExporter(stub_interface); + + auto recordable_1 = exporter->MakeRecordable(); + auto recordable_2 = exporter->MakeRecordable(); + + // exporter shuold not be shutdown by default + nostd::span<std::unique_ptr<sdk::logs::Recordable>> batch_1(&recordable_1, 1); + EXPECT_CALL(*mock_stub, Export(_, _, _)) + .Times(Exactly(1)) + .WillOnce(Return(grpc::Status::OK)) + .WillOnce(Return(grpc::Status::CANCELLED)); + + auto result = exporter->Export(batch_1); + EXPECT_EQ(sdk::common::ExportResult::kSuccess, result); + + exporter->Shutdown(); + + nostd::span<std::unique_ptr<sdk::logs::Recordable>> batch_2(&recordable_2, 1); + result = exporter->Export(batch_2); + EXPECT_EQ(sdk::common::ExportResult::kFailure, result); +} + +// Call Export() directly +TEST_F(OtlpGrpcLogExporterTestPeer, ExportUnitTest) +{ + auto mock_stub = new proto::collector::logs::v1::MockLogsServiceStub(); + std::unique_ptr<proto::collector::logs::v1::LogsService::StubInterface> stub_interface(mock_stub); + auto exporter = GetExporter(stub_interface); + + auto recordable_1 = exporter->MakeRecordable(); + auto recordable_2 = exporter->MakeRecordable(); + + // Test successful RPC + nostd::span<std::unique_ptr<sdk::logs::Recordable>> 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<std::unique_ptr<sdk::logs::Recordable>> 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(OtlpGrpcLogExporterTestPeer, ExportIntegrationTest) +{ + auto mock_stub = new proto::collector::logs::v1::MockLogsServiceStub(); + std::unique_ptr<proto::collector::logs::v1::LogsService::StubInterface> stub_interface(mock_stub); + + auto exporter = GetExporter(stub_interface); + + bool attribute_storage_bool_value[] = {true, false, true}; + int32_t attribute_storage_int32_value[] = {1, 2}; + uint32_t attribute_storage_uint32_value[] = {3, 4}; + int64_t attribute_storage_int64_value[] = {5, 6}; + uint64_t attribute_storage_uint64_value[] = {7, 8}; + double attribute_storage_double_value[] = {3.2, 3.3}; + std::string attribute_storage_string_value[] = {"vector", "string"}; + + auto provider = nostd::shared_ptr<sdk::logs::LoggerProvider>(new sdk::logs::LoggerProvider()); + provider->AddProcessor(std::unique_ptr<sdk::logs::LogProcessor>( + new sdk::logs::BatchLogProcessor(std::move(exporter), 5, std::chrono::milliseconds(256), 1))); + + EXPECT_CALL(*mock_stub, Export(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(grpc::Status::OK)); + + uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + opentelemetry::trace::TraceId trace_id{trace_id_bin}; + uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', + '3', '2', '1', '0'}; + opentelemetry::trace::SpanId span_id{span_id_bin}; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger = provider->GetLogger("test", "", "opentelelemtry_library", "", schema_url); + logger->Log(opentelemetry::logs::Severity::kInfo, "Log message", + {{"service.name", "unit_test_service"}, + {"tenant.id", "test_user"}, + {"bool_value", true}, + {"int32_value", static_cast<int32_t>(1)}, + {"uint32_value", static_cast<uint32_t>(2)}, + {"int64_value", static_cast<int64_t>(0x1100000000LL)}, + {"uint64_value", static_cast<uint64_t>(0x1200000000ULL)}, + {"double_value", static_cast<double>(3.1)}, + {"vec_bool_value", attribute_storage_bool_value}, + {"vec_int32_value", attribute_storage_int32_value}, + {"vec_uint32_value", attribute_storage_uint32_value}, + {"vec_int64_value", attribute_storage_int64_value}, + {"vec_uint64_value", attribute_storage_uint64_value}, + {"vec_double_value", attribute_storage_double_value}, + {"vec_string_value", attribute_storage_string_value}}, + trace_id, span_id, + opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, + std::chrono::system_clock::now()); +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif // ENABLE_LOGS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_http_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_http_exporter_test.cc new file mode 100644 index 000000000..ef0b5a509 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_http_exporter_test.cc @@ -0,0 +1,357 @@ +// 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" + +# include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +# include "opentelemetry/ext/http/client/http_client_factory.h" +# include "opentelemetry/ext/http/client/nosend/http_client_nosend.h" +# include "opentelemetry/ext/http/server/http_server.h" +# include "opentelemetry/sdk/trace/batch_span_processor.h" +# include "opentelemetry/sdk/trace/tracer_provider.h" +# include "opentelemetry/trace/provider.h" + +# include <gtest/gtest.h> +# include "gmock/gmock.h" + +# include "nlohmann/json.hpp" + +# if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +# endif +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +namespace trace_api = opentelemetry::trace; +namespace resource = opentelemetry::sdk::resource; + +template <class T, size_t N> +static nostd::span<T, N> MakeSpan(T (&array)[N]) +{ + return nostd::span<T, N>(array); +} + +OtlpHttpClientOptions MakeOtlpHttpClientOptions(HttpRequestContentType content_type) +{ + OtlpHttpExporterOptions options; + options.content_type = content_type; + options.console_debug = true; + options.timeout = std::chrono::system_clock::duration::zero(); + options.http_headers.insert( + std::make_pair<const std::string, std::string>("Custom-Header-Key", "Custom-Header-Value")); + OtlpHttpClientOptions otlp_http_client_options( + options.url, options.content_type, options.json_bytes_mapping, options.use_json_name, + options.console_debug, options.timeout, options.http_headers); + return otlp_http_client_options; +} + +namespace http_client = opentelemetry::ext::http::client; + +class OtlpHttpExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr<sdk::trace::SpanExporter> GetExporter(std::unique_ptr<OtlpHttpClient> http_client) + { + return std::unique_ptr<sdk::trace::SpanExporter>(new OtlpHttpExporter(std::move(http_client))); + } + + // Get the options associated with the given exporter. + const OtlpHttpExporterOptions &GetOptions(std::unique_ptr<OtlpHttpExporter> &exporter) + { + return exporter->options_; + } + + static std::pair<OtlpHttpClient *, std::shared_ptr<http_client::HttpClient>> + GetMockOtlpHttpClient(HttpRequestContentType content_type) + { + auto http_client = http_client::HttpClientFactory::CreateNoSend(); + return {new OtlpHttpClient(MakeOtlpHttpClientOptions(content_type), http_client), http_client}; + } +}; + +// Create spans, let processor call Export() +TEST_F(OtlpHttpExporterTestPeer, ExportJsonIntegrationTest) +{ + auto mock_otlp_client = + OtlpHttpExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kJson); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr<OtlpHttpClient>{mock_otlp_http_client}); + + resource::ResourceAttributes resource_attributes = {{"service.name", "unit_test_service"}, + {"tenant.id", "test_user"}}; + resource_attributes["bool_value"] = true; + resource_attributes["int32_value"] = static_cast<int32_t>(1); + resource_attributes["uint32_value"] = static_cast<uint32_t>(2); + resource_attributes["int64_value"] = static_cast<int64_t>(0x1100000000LL); + resource_attributes["uint64_value"] = static_cast<uint64_t>(0x1200000000ULL); + resource_attributes["double_value"] = static_cast<double>(3.1); + resource_attributes["vec_bool_value"] = std::vector<bool>{true, false, true}; + resource_attributes["vec_int32_value"] = std::vector<int32_t>{1, 2}; + resource_attributes["vec_uint32_value"] = std::vector<uint32_t>{3, 4}; + resource_attributes["vec_int64_value"] = std::vector<int64_t>{5, 6}; + resource_attributes["vec_uint64_value"] = std::vector<uint64_t>{7, 8}; + resource_attributes["vec_double_value"] = std::vector<double>{3.2, 3.3}; + resource_attributes["vec_string_value"] = std::vector<std::string>{"vector", "string"}; + auto resource = resource::Resource::Create(resource_attributes); + + 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<sdk::trace::SpanProcessor>( + new sdk::trace::BatchSpanProcessor(std::move(exporter), processor_opts)); + auto provider = nostd::shared_ptr<trace::TracerProvider>( + new sdk::trace::TracerProvider(std::move(processor), resource)); + + std::string report_trace_id; + + char trace_id_hex[2 * trace_api::TraceId::kSize] = {0}; + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); + + trace_api::StartSpanOptions child_span_opts = {}; + child_span_opts.parent = parent_span->GetContext(); + + auto child_span = tracer->StartSpan("Test child span", child_span_opts); + + nostd::get<trace_api::SpanContext>(child_span_opts.parent) + .trace_id() + .ToLowerBase16(MakeSpan(trace_id_hex)); + report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); + + auto no_send_client = std::static_pointer_cast<http_client::nosend::HttpClient>(client); + auto mock_session = + std::static_pointer_cast<http_client::nosend::Session>(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, + report_trace_id](opentelemetry::ext::http::client::EventHandler &callback) { + auto check_json = nlohmann::json::parse(mock_session->GetRequest()->body_, nullptr, false); + auto resource_span = *check_json["resource_spans"].begin(); + auto instrumentation_library_span = *resource_span["instrumentation_library_spans"].begin(); + auto span = *instrumentation_library_span["spans"].begin(); + auto received_trace_id = span["trace_id"].get<std::string>(); + EXPECT_EQ(received_trace_id, report_trace_id); + + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + // let the otlp_http_client to continue + http_client::nosend::Response response; + callback.OnResponse(response); + }); + + child_span->End(); + parent_span->End(); +} + +// Create spans, let processor call Export() +TEST_F(OtlpHttpExporterTestPeer, ExportBinaryIntegrationTest) +{ + auto mock_otlp_client = + OtlpHttpExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kBinary); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr<OtlpHttpClient>{mock_otlp_http_client}); + + resource::ResourceAttributes resource_attributes = {{"service.name", "unit_test_service"}, + {"tenant.id", "test_user"}}; + resource_attributes["bool_value"] = true; + resource_attributes["int32_value"] = static_cast<int32_t>(1); + resource_attributes["uint32_value"] = static_cast<uint32_t>(2); + resource_attributes["int64_value"] = static_cast<int64_t>(0x1100000000LL); + resource_attributes["uint64_value"] = static_cast<uint64_t>(0x1200000000ULL); + resource_attributes["double_value"] = static_cast<double>(3.1); + resource_attributes["vec_bool_value"] = std::vector<bool>{true, false, true}; + resource_attributes["vec_int32_value"] = std::vector<int32_t>{1, 2}; + resource_attributes["vec_uint32_value"] = std::vector<uint32_t>{3, 4}; + resource_attributes["vec_int64_value"] = std::vector<int64_t>{5, 6}; + resource_attributes["vec_uint64_value"] = std::vector<uint64_t>{7, 8}; + resource_attributes["vec_double_value"] = std::vector<double>{3.2, 3.3}; + resource_attributes["vec_string_value"] = std::vector<std::string>{"vector", "string"}; + auto resource = resource::Resource::Create(resource_attributes); + + 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<sdk::trace::SpanProcessor>( + new sdk::trace::BatchSpanProcessor(std::move(exporter), processor_opts)); + auto provider = nostd::shared_ptr<trace::TracerProvider>( + new sdk::trace::TracerProvider(std::move(processor), resource)); + + std::string report_trace_id; + + uint8_t trace_id_binary[trace_api::TraceId::kSize] = {0}; + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); + + trace_api::StartSpanOptions child_span_opts = {}; + child_span_opts.parent = parent_span->GetContext(); + + auto child_span = tracer->StartSpan("Test child span", child_span_opts); + nostd::get<trace_api::SpanContext>(child_span_opts.parent) + .trace_id() + .CopyBytesTo(MakeSpan(trace_id_binary)); + report_trace_id.assign(reinterpret_cast<char *>(trace_id_binary), sizeof(trace_id_binary)); + + auto no_send_client = std::static_pointer_cast<http_client::nosend::HttpClient>(client); + auto mock_session = + std::static_pointer_cast<http_client::nosend::Session>(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, + report_trace_id](opentelemetry::ext::http::client::EventHandler &callback) { + opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request_body; + request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], + static_cast<int>(mock_session->GetRequest()->body_.size())); + auto received_trace_id = + request_body.resource_spans(0).instrumentation_library_spans(0).spans(0).trace_id(); + EXPECT_EQ(received_trace_id, report_trace_id); + + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + // let the otlp_http_client to continue + http_client::nosend::Response response; + callback.OnResponse(response); + }); + + child_span->End(); + parent_span->End(); +} + +// Test exporter configuration options +TEST_F(OtlpHttpExporterTestPeer, ConfigTest) +{ + OtlpHttpExporterOptions opts; + opts.url = "http://localhost:45455/v1/traces"; + std::unique_ptr<OtlpHttpExporter> exporter(new OtlpHttpExporter(opts)); + EXPECT_EQ(GetOptions(exporter).url, "http://localhost:45455/v1/traces"); +} + +// Test exporter configuration options with use_json_name +TEST_F(OtlpHttpExporterTestPeer, ConfigUseJsonNameTest) +{ + OtlpHttpExporterOptions opts; + opts.use_json_name = true; + std::unique_ptr<OtlpHttpExporter> 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, ConfigJsonBytesMappingTest) +{ + OtlpHttpExporterOptions opts; + opts.json_bytes_mapping = JsonBytesMappingKind::kHex; + std::unique_ptr<OtlpHttpExporter> exporter(new OtlpHttpExporter(opts)); + EXPECT_EQ(GetOptions(exporter).json_bytes_mapping, JsonBytesMappingKind::kHex); +} + +# ifndef NO_GETENV +// Test exporter configuration options with use_ssl_credentials +TEST_F(OtlpHttpExporterTestPeer, ConfigFromEnv) +{ + const std::string url = "http://localhost:9999/v1/traces"; + setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:9999", 1); + setenv("OTEL_EXPORTER_OTLP_TIMEOUT", "20s", 1); + setenv("OTEL_EXPORTER_OTLP_HEADERS", "k1=v1,k2=v2", 1); + setenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "k1=v3,k1=v4", 1); + + std::unique_ptr<OtlpHttpExporter> exporter(new OtlpHttpExporter()); + EXPECT_EQ(GetOptions(exporter).url, url); + EXPECT_EQ( + GetOptions(exporter).timeout.count(), + std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::seconds{20}) + .count()); + EXPECT_EQ(GetOptions(exporter).http_headers.size(), 3); + { + // Test k2 + auto range = GetOptions(exporter).http_headers.equal_range("k2"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v2")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + { + // k1 + auto range = GetOptions(exporter).http_headers.equal_range("k1"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v3")); + ++range.first; + EXPECT_EQ(range.first->second, std::string("v4")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + + unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT"); + unsetenv("OTEL_EXPORTER_OTLP_TIMEOUT"); + unsetenv("OTEL_EXPORTER_OTLP_HEADERS"); + unsetenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS"); +} + +TEST_F(OtlpHttpExporterTestPeer, ConfigFromTracesEnv) +{ + const std::string url = "http://localhost:9999/v1/traces"; + setenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", url.c_str(), 1); + setenv("OTEL_EXPORTER_OTLP_TIMEOUT", "20s", 1); + setenv("OTEL_EXPORTER_OTLP_HEADERS", "k1=v1,k2=v2", 1); + setenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "k1=v3,k1=v4", 1); + + std::unique_ptr<OtlpHttpExporter> exporter(new OtlpHttpExporter()); + EXPECT_EQ(GetOptions(exporter).url, url); + EXPECT_EQ( + GetOptions(exporter).timeout.count(), + std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::seconds{20}) + .count()); + EXPECT_EQ(GetOptions(exporter).http_headers.size(), 3); + { + // Test k2 + auto range = GetOptions(exporter).http_headers.equal_range("k2"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v2")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + { + // k1 + auto range = GetOptions(exporter).http_headers.equal_range("k1"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v3")); + ++range.first; + EXPECT_EQ(range.first->second, std::string("v4")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + + unsetenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"); + unsetenv("OTEL_EXPORTER_OTLP_TIMEOUT"); + unsetenv("OTEL_EXPORTER_OTLP_HEADERS"); + unsetenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS"); +} +# endif + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_http_log_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_http_log_exporter_test.cc new file mode 100644 index 000000000..7e2c0808e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_http_log_exporter_test.cc @@ -0,0 +1,381 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef HAVE_CPP_STDLIB +# ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/exporters/otlp/otlp_http_log_exporter.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" + +# include "opentelemetry/proto/collector/logs/v1/logs_service.pb.h" + +# include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" + +# include "opentelemetry/common/key_value_iterable_view.h" +# include "opentelemetry/ext/http/client/http_client_factory.h" +# include "opentelemetry/ext/http/client/nosend/http_client_nosend.h" +# include "opentelemetry/ext/http/server/http_server.h" +# include "opentelemetry/logs/provider.h" +# include "opentelemetry/sdk/logs/batch_log_processor.h" +# include "opentelemetry/sdk/logs/exporter.h" +# include "opentelemetry/sdk/logs/log_record.h" +# include "opentelemetry/sdk/logs/logger_provider.h" +# include "opentelemetry/sdk/resource/resource.h" + +# include <gtest/gtest.h> +# include "gmock/gmock.h" + +# include "nlohmann/json.hpp" + +# if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +# endif + +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +template <class T, size_t N> +static nostd::span<T, N> MakeSpan(T (&array)[N]) +{ + return nostd::span<T, N>(array); +} + +OtlpHttpClientOptions MakeOtlpHttpClientOptions(HttpRequestContentType content_type) +{ + OtlpHttpLogExporterOptions options; + options.content_type = content_type; + options.console_debug = true; + options.http_headers.insert( + std::make_pair<const std::string, std::string>("Custom-Header-Key", "Custom-Header-Value")); + OtlpHttpClientOptions otlp_http_client_options( + options.url, options.content_type, options.json_bytes_mapping, options.use_json_name, + options.console_debug, options.timeout, options.http_headers); + return otlp_http_client_options; +} + +namespace http_client = opentelemetry::ext::http::client; + +class OtlpHttpLogExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr<sdk::logs::LogExporter> GetExporter(std::unique_ptr<OtlpHttpClient> http_client) + { + return std::unique_ptr<sdk::logs::LogExporter>(new OtlpHttpLogExporter(std::move(http_client))); + } + + // Get the options associated with the given exporter. + const OtlpHttpLogExporterOptions &GetOptions(std::unique_ptr<OtlpHttpLogExporter> &exporter) + { + return exporter->options_; + } + static std::pair<OtlpHttpClient *, std::shared_ptr<http_client::HttpClient>> + GetMockOtlpHttpClient(HttpRequestContentType content_type) + { + auto http_client = http_client::HttpClientFactory::CreateNoSend(); + return {new OtlpHttpClient(MakeOtlpHttpClientOptions(content_type), http_client), http_client}; + } +}; + +// Create log records, let processor call Export() +TEST_F(OtlpHttpLogExporterTestPeer, ExportJsonIntegrationTest) +{ + auto mock_otlp_client = + OtlpHttpLogExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kJson); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr<OtlpHttpClient>{mock_otlp_http_client}); + + bool attribute_storage_bool_value[] = {true, false, true}; + int32_t attribute_storage_int32_value[] = {1, 2}; + uint32_t attribute_storage_uint32_value[] = {3, 4}; + int64_t attribute_storage_int64_value[] = {5, 6}; + uint64_t attribute_storage_uint64_value[] = {7, 8}; + double attribute_storage_double_value[] = {3.2, 3.3}; + std::string attribute_storage_string_value[] = {"vector", "string"}; + + auto provider = nostd::shared_ptr<sdk::logs::LoggerProvider>(new sdk::logs::LoggerProvider()); + provider->AddProcessor(std::unique_ptr<sdk::logs::LogProcessor>( + new sdk::logs::BatchLogProcessor(std::move(exporter), 5, std::chrono::milliseconds(256), 5))); + + std::string report_trace_id; + std::string report_span_id; + uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + char trace_id_hex[2 * opentelemetry::trace::TraceId::kSize] = {0}; + opentelemetry::trace::TraceId trace_id{trace_id_bin}; + uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', + '3', '2', '1', '0'}; + char span_id_hex[2 * opentelemetry::trace::SpanId::kSize] = {0}; + opentelemetry::trace::SpanId span_id{span_id_bin}; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = provider->GetLogger("test", "", "opentelelemtry_library", "", schema_url); + logger->Log(opentelemetry::logs::Severity::kInfo, "Log message", + {{"service.name", "unit_test_service"}, + {"tenant.id", "test_user"}, + {"bool_value", true}, + {"int32_value", static_cast<int32_t>(1)}, + {"uint32_value", static_cast<uint32_t>(2)}, + {"int64_value", static_cast<int64_t>(0x1100000000LL)}, + {"uint64_value", static_cast<uint64_t>(0x1200000000ULL)}, + {"double_value", static_cast<double>(3.1)}, + {"vec_bool_value", attribute_storage_bool_value}, + {"vec_int32_value", attribute_storage_int32_value}, + {"vec_uint32_value", attribute_storage_uint32_value}, + {"vec_int64_value", attribute_storage_int64_value}, + {"vec_uint64_value", attribute_storage_uint64_value}, + {"vec_double_value", attribute_storage_double_value}, + {"vec_string_value", attribute_storage_string_value}}, + trace_id, span_id, + opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, + std::chrono::system_clock::now()); + + trace_id.ToLowerBase16(MakeSpan(trace_id_hex)); + report_trace_id.assign(trace_id_hex, sizeof(trace_id_hex)); + + span_id.ToLowerBase16(MakeSpan(span_id_hex)); + report_span_id.assign(span_id_hex, sizeof(span_id_hex)); + + auto no_send_client = std::static_pointer_cast<http_client::nosend::HttpClient>(client); + auto mock_session = + std::static_pointer_cast<http_client::nosend::Session>(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, report_trace_id, + report_span_id](opentelemetry::ext::http::client::EventHandler &callback) { + auto check_json = nlohmann::json::parse(mock_session->GetRequest()->body_, nullptr, false); + auto resource_logs = *check_json["resource_logs"].begin(); + auto scope_logs = *resource_logs["scope_logs"].begin(); + auto log = *scope_logs["log_records"].begin(); + auto received_trace_id = log["trace_id"].get<std::string>(); + auto received_span_id = log["span_id"].get<std::string>(); + EXPECT_EQ(received_trace_id, report_trace_id); + EXPECT_EQ(received_span_id, report_span_id); + EXPECT_EQ("Log message", log["body"]["string_value"].get<std::string>()); + EXPECT_LE(15, log["attributes"].size()); + auto custom_header = mock_session->GetRequest()->headers_.find("Custom-Header-Key"); + ASSERT_TRUE(custom_header != mock_session->GetRequest()->headers_.end()); + if (custom_header != mock_session->GetRequest()->headers_.end()) + { + EXPECT_EQ("Custom-Header-Value", custom_header->second); + } + // let the otlp_http_client to continue + http_client::nosend::Response response; + callback.OnResponse(response); + }); +} + +// Create log records, let processor call Export() +TEST_F(OtlpHttpLogExporterTestPeer, ExportBinaryIntegrationTest) +{ + auto mock_otlp_client = + OtlpHttpLogExporterTestPeer::GetMockOtlpHttpClient(HttpRequestContentType::kBinary); + auto mock_otlp_http_client = mock_otlp_client.first; + auto client = mock_otlp_client.second; + auto exporter = GetExporter(std::unique_ptr<OtlpHttpClient>{mock_otlp_http_client}); + + bool attribute_storage_bool_value[] = {true, false, true}; + int32_t attribute_storage_int32_value[] = {1, 2}; + uint32_t attribute_storage_uint32_value[] = {3, 4}; + int64_t attribute_storage_int64_value[] = {5, 6}; + uint64_t attribute_storage_uint64_value[] = {7, 8}; + double attribute_storage_double_value[] = {3.2, 3.3}; + std::string attribute_storage_string_value[] = {"vector", "string"}; + + auto provider = nostd::shared_ptr<sdk::logs::LoggerProvider>(new sdk::logs::LoggerProvider()); + provider->AddProcessor(std::unique_ptr<sdk::logs::LogProcessor>( + new sdk::logs::BatchLogProcessor(std::move(exporter), 5, std::chrono::milliseconds(256), 5))); + + std::string report_trace_id; + std::string report_span_id; + uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + opentelemetry::trace::TraceId trace_id{trace_id_bin}; + uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', + '3', '2', '1', '0'}; + opentelemetry::trace::SpanId span_id{span_id_bin}; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = provider->GetLogger("test", "", "opentelelemtry_library", "", schema_url); + logger->Log(opentelemetry::logs::Severity::kInfo, "Log message", + {{"service.name", "unit_test_service"}, + {"tenant.id", "test_user"}, + {"bool_value", true}, + {"int32_value", static_cast<int32_t>(1)}, + {"uint32_value", static_cast<uint32_t>(2)}, + {"int64_value", static_cast<int64_t>(0x1100000000LL)}, + {"uint64_value", static_cast<uint64_t>(0x1200000000ULL)}, + {"double_value", static_cast<double>(3.1)}, + {"vec_bool_value", attribute_storage_bool_value}, + {"vec_int32_value", attribute_storage_int32_value}, + {"vec_uint32_value", attribute_storage_uint32_value}, + {"vec_int64_value", attribute_storage_int64_value}, + {"vec_uint64_value", attribute_storage_uint64_value}, + {"vec_double_value", attribute_storage_double_value}, + {"vec_string_value", attribute_storage_string_value}}, + trace_id, span_id, + opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, + std::chrono::system_clock::now()); + + report_trace_id.assign(reinterpret_cast<const char *>(trace_id_bin), sizeof(trace_id_bin)); + report_span_id.assign(reinterpret_cast<const char *>(span_id_bin), sizeof(span_id_bin)); + + auto no_send_client = std::static_pointer_cast<http_client::nosend::HttpClient>(client); + auto mock_session = + std::static_pointer_cast<http_client::nosend::Session>(no_send_client->session_); + EXPECT_CALL(*mock_session, SendRequest) + .WillOnce([&mock_session, report_trace_id, + report_span_id](opentelemetry::ext::http::client::EventHandler &callback) { + opentelemetry::proto::collector::logs::v1::ExportLogsServiceRequest request_body; + request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], + static_cast<int>(mock_session->GetRequest()->body_.size())); + auto &received_log = request_body.resource_logs(0).scope_logs(0).log_records(0); + EXPECT_EQ(received_log.trace_id(), report_trace_id); + EXPECT_EQ(received_log.span_id(), report_span_id); + EXPECT_EQ("Log message", received_log.body().string_value()); + EXPECT_LE(15, received_log.attributes_size()); + bool check_service_name = false; + for (auto &attribute : received_log.attributes()) + { + if ("service.name" == attribute.key()) + { + check_service_name = true; + EXPECT_EQ("unit_test_service", attribute.value().string_value()); + } + } + ASSERT_TRUE(check_service_name); + http_client::nosend::Response response; + callback.OnResponse(response); + }); +} + +// Test exporter configuration options +TEST_F(OtlpHttpLogExporterTestPeer, ConfigTest) +{ + OtlpHttpLogExporterOptions opts; + opts.url = "http://localhost:45456/v1/logs"; + std::unique_ptr<OtlpHttpLogExporter> exporter(new OtlpHttpLogExporter(opts)); + EXPECT_EQ(GetOptions(exporter).url, "http://localhost:45456/v1/logs"); +} + +// Test exporter configuration options with use_json_name +TEST_F(OtlpHttpLogExporterTestPeer, ConfigUseJsonNameTest) +{ + OtlpHttpLogExporterOptions opts; + opts.use_json_name = true; + std::unique_ptr<OtlpHttpLogExporter> exporter(new OtlpHttpLogExporter(opts)); + EXPECT_EQ(GetOptions(exporter).use_json_name, true); +} + +// Test exporter configuration options with json_bytes_mapping=JsonBytesMappingKind::kHex +TEST_F(OtlpHttpLogExporterTestPeer, ConfigJsonBytesMappingTest) +{ + OtlpHttpLogExporterOptions opts; + opts.json_bytes_mapping = JsonBytesMappingKind::kHex; + std::unique_ptr<OtlpHttpLogExporter> exporter(new OtlpHttpLogExporter(opts)); + EXPECT_EQ(GetOptions(exporter).json_bytes_mapping, JsonBytesMappingKind::kHex); +} + +# ifndef NO_GETENV +// Test exporter configuration options with use_ssl_credentials +TEST_F(OtlpHttpLogExporterTestPeer, ConfigFromEnv) +{ + const std::string url = "http://localhost:9999/v1/logs"; + setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:9999", 1); + setenv("OTEL_EXPORTER_OTLP_TIMEOUT", "20s", 1); + setenv("OTEL_EXPORTER_OTLP_HEADERS", "k1=v1,k2=v2", 1); + setenv("OTEL_EXPORTER_OTLP_LOGS_HEADERS", "k1=v3,k1=v4", 1); + + std::unique_ptr<OtlpHttpLogExporter> exporter(new OtlpHttpLogExporter()); + EXPECT_EQ(GetOptions(exporter).url, url); + EXPECT_EQ( + GetOptions(exporter).timeout.count(), + std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::seconds{20}) + .count()); + EXPECT_EQ(GetOptions(exporter).http_headers.size(), 3); + { + // Test k2 + auto range = GetOptions(exporter).http_headers.equal_range("k2"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v2")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + { + // k1 + auto range = GetOptions(exporter).http_headers.equal_range("k1"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v3")); + ++range.first; + EXPECT_EQ(range.first->second, std::string("v4")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + + unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT"); + unsetenv("OTEL_EXPORTER_OTLP_TIMEOUT"); + unsetenv("OTEL_EXPORTER_OTLP_HEADERS"); + unsetenv("OTEL_EXPORTER_OTLP_LOGS_HEADERS"); +} + +TEST_F(OtlpHttpLogExporterTestPeer, ConfigFromLogsEnv) +{ + const std::string url = "http://localhost:9999/v1/logs"; + setenv("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", url.c_str(), 1); + setenv("OTEL_EXPORTER_OTLP_TIMEOUT", "20s", 1); + setenv("OTEL_EXPORTER_OTLP_HEADERS", "k1=v1,k2=v2", 1); + setenv("OTEL_EXPORTER_OTLP_LOGS_HEADERS", "k1=v3,k1=v4", 1); + + std::unique_ptr<OtlpHttpLogExporter> exporter(new OtlpHttpLogExporter()); + EXPECT_EQ(GetOptions(exporter).url, url); + EXPECT_EQ( + GetOptions(exporter).timeout.count(), + std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::seconds{20}) + .count()); + EXPECT_EQ(GetOptions(exporter).http_headers.size(), 3); + { + // Test k2 + auto range = GetOptions(exporter).http_headers.equal_range("k2"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v2")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + { + // k1 + auto range = GetOptions(exporter).http_headers.equal_range("k1"); + EXPECT_TRUE(range.first != range.second); + EXPECT_EQ(range.first->second, std::string("v3")); + ++range.first; + EXPECT_EQ(range.first->second, std::string("v4")); + ++range.first; + EXPECT_TRUE(range.first == range.second); + } + + unsetenv("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"); + unsetenv("OTEL_EXPORTER_OTLP_TIMEOUT"); + unsetenv("OTEL_EXPORTER_OTLP_HEADERS"); + unsetenv("OTEL_EXPORTER_OTLP_LOGS_HEADERS"); +} + +TEST_F(OtlpHttpLogExporterTestPeer, DefaultEndpoint) +{ + EXPECT_EQ("http://localhost:4318/v1/logs", GetOtlpDefaultHttpLogEndpoint()); + EXPECT_EQ("http://localhost:4318/v1/traces", GetOtlpDefaultHttpEndpoint()); + EXPECT_EQ("http://localhost:4317", GetOtlpDefaultGrpcEndpoint()); +} + +# endif + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +# endif +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_log_recordable_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_log_recordable_test.cc new file mode 100644 index 000000000..bb48fd54e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_log_recordable_test.cc @@ -0,0 +1,240 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include <gtest/gtest.h> + +# include "opentelemetry/exporters/otlp/otlp_log_recordable.h" +# include "opentelemetry/sdk/resource/experimental_semantic_conventions.h" +# include "opentelemetry/sdk/resource/resource.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +namespace resource = opentelemetry::sdk::resource; +namespace proto = opentelemetry::proto; + +TEST(OtlpLogRecordable, SetTimestamp) +{ + OtlpLogRecordable rec; + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + common::SystemTimestamp start_timestamp(now); + + uint64_t unix_start = + std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count(); + + rec.SetTimestamp(start_timestamp); + EXPECT_EQ(rec.log_record().time_unix_nano(), unix_start); +} + +TEST(OtlpLogRecordable, SetSeverity) +{ + OtlpLogRecordable rec; + rec.SetSeverity(opentelemetry::logs::Severity::kError); + + EXPECT_EQ(rec.log_record().severity_number(), proto::logs::v1::SEVERITY_NUMBER_ERROR); + EXPECT_EQ(rec.log_record().severity_text(), "ERROR"); +} + +TEST(OtlpLogRecordable, SetBody) +{ + OtlpLogRecordable rec; + nostd::string_view name = "Test Log Message"; + rec.SetBody(name); + EXPECT_EQ(rec.log_record().body().string_value(), name); +} + +TEST(OtlpLogRecordable, SetTraceId) +{ + OtlpLogRecordable rec; + uint8_t trace_id_bin[opentelemetry::trace::TraceId::kSize] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + std::string expected_bytes; + expected_bytes.assign( + reinterpret_cast<char *>(trace_id_bin), + reinterpret_cast<char *>(trace_id_bin) + opentelemetry::trace::TraceId::kSize); + rec.SetTraceId(opentelemetry::trace::TraceId{trace_id_bin}); + EXPECT_EQ(rec.log_record().trace_id(), expected_bytes); +} + +TEST(OtlpLogRecordable, SetSpanId) +{ + OtlpLogRecordable rec; + uint8_t span_id_bin[opentelemetry::trace::SpanId::kSize] = {'7', '6', '5', '4', + '3', '2', '1', '0'}; + std::string expected_bytes; + expected_bytes.assign( + reinterpret_cast<char *>(span_id_bin), + reinterpret_cast<char *>(span_id_bin) + opentelemetry::trace::SpanId::kSize); + rec.SetSpanId(opentelemetry::trace::SpanId{span_id_bin}); + EXPECT_EQ(rec.log_record().span_id(), expected_bytes); +} + +TEST(OtlpLogRecordable, SetResource) +{ + OtlpLogRecordable rec; + const std::string service_name_key = "service.name"; + std::string service_name = "test-otlp"; + auto resource = + opentelemetry::sdk::resource::Resource::Create({{service_name_key, service_name}}); + rec.SetResource(resource); + + auto proto_resource = rec.ProtoResource(); + bool found_service_name = false; + for (int i = 0; i < proto_resource.attributes_size(); i++) + { + auto attr = proto_resource.attributes(static_cast<int>(i)); + if (attr.key() == service_name_key && attr.value().string_value() == service_name) + { + found_service_name = true; + } + } + EXPECT_TRUE(found_service_name); +} + +TEST(OtlpLogRecordable, DefaultResource) +{ + OtlpLogRecordable rec; + + auto proto_resource = rec.ProtoResource(); + int found_resource_count = 0; + for (int i = 0; i < proto_resource.attributes_size(); i++) + { + auto attr = proto_resource.attributes(static_cast<int>(i)); + if (attr.key() == + opentelemetry::sdk::resource::attr(OTEL_CPP_CONST_HASHCODE(AttrTelemetrySdkLanguage))) + { + EXPECT_EQ(attr.value().string_value(), "cpp"); + ++found_resource_count; + } + else if (attr.key() == + opentelemetry::sdk::resource::attr(OTEL_CPP_CONST_HASHCODE(AttrTelemetrySdkName))) + { + EXPECT_EQ(attr.value().string_value(), "opentelemetry"); + ++found_resource_count; + } + else if (attr.key() == + opentelemetry::sdk::resource::attr(OTEL_CPP_CONST_HASHCODE(AttrTelemetrySdkVersion))) + { + EXPECT_EQ(attr.value().string_value(), OPENTELEMETRY_SDK_VERSION); + ++found_resource_count; + } + } + EXPECT_EQ(3, found_resource_count); +} + +// Test non-int single types. Int single types are tested using templates (see IntAttributeTest) +TEST(OtlpLogRecordable, SetSingleAttribute) +{ + OtlpLogRecordable rec; + nostd::string_view bool_key = "bool_attr"; + common::AttributeValue bool_val(true); + rec.SetAttribute(bool_key, bool_val); + + nostd::string_view double_key = "double_attr"; + common::AttributeValue double_val(3.3); + rec.SetAttribute(double_key, double_val); + + nostd::string_view str_key = "str_attr"; + common::AttributeValue str_val(nostd::string_view("Test")); + rec.SetAttribute(str_key, str_val); + + EXPECT_EQ(rec.log_record().attributes(0).key(), bool_key); + EXPECT_EQ(rec.log_record().attributes(0).value().bool_value(), nostd::get<bool>(bool_val)); + + EXPECT_EQ(rec.log_record().attributes(1).key(), double_key); + EXPECT_EQ(rec.log_record().attributes(1).value().double_value(), nostd::get<double>(double_val)); + + EXPECT_EQ(rec.log_record().attributes(2).key(), str_key); + EXPECT_EQ(rec.log_record().attributes(2).value().string_value(), + nostd::get<nostd::string_view>(str_val).data()); +} + +// Test non-int array types. Int array types are tested using templates (see IntAttributeTest) +TEST(OtlpLogRecordable, SetArrayAttribute) +{ + OtlpLogRecordable rec; + const int kArraySize = 3; + + bool bool_arr[kArraySize] = {true, false, true}; + nostd::span<const bool> bool_span(bool_arr); + rec.SetAttribute("bool_arr_attr", bool_span); + + double double_arr[kArraySize] = {22.3, 33.4, 44.5}; + nostd::span<const double> double_span(double_arr); + rec.SetAttribute("double_arr_attr", double_span); + + nostd::string_view str_arr[kArraySize] = {"Hello", "World", "Test"}; + nostd::span<const nostd::string_view> str_span(str_arr); + rec.SetAttribute("str_arr_attr", str_span); + + for (int i = 0; i < kArraySize; i++) + { + EXPECT_EQ(rec.log_record().attributes(0).value().array_value().values(i).bool_value(), + bool_span[i]); + EXPECT_EQ(rec.log_record().attributes(1).value().array_value().values(i).double_value(), + double_span[i]); + EXPECT_EQ(rec.log_record().attributes(2).value().array_value().values(i).string_value(), + str_span[i]); + } +} + +TEST(OtlpLogRecordable, SetInstrumentationLibrary) +{ + OtlpLogRecordable rec; + auto inst_lib = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("test", "v1"); + rec.SetInstrumentationLibrary(*inst_lib); + EXPECT_EQ(rec.GetInstrumentationLibrary(), *inst_lib); +} + +/** + * AttributeValue can contain different int types, such as int, int64_t, + * unsigned int, and uint64_t. To avoid writing test cases for each, we can + * use a template approach to test all int types. + */ +template <typename T> +struct OtlpLogRecordableIntAttributeTest : public testing::Test +{ + using IntParamType = T; +}; + +using IntTypes = testing::Types<int, int64_t, unsigned int, uint64_t>; +TYPED_TEST_SUITE(OtlpLogRecordableIntAttributeTest, IntTypes); + +TYPED_TEST(OtlpLogRecordableIntAttributeTest, SetIntSingleAttribute) +{ + using IntType = typename TestFixture::IntParamType; + IntType i = 2; + common::AttributeValue int_val(i); + + OtlpLogRecordable rec; + rec.SetAttribute("int_attr", int_val); + EXPECT_EQ(rec.log_record().attributes(0).value().int_value(), nostd::get<IntType>(int_val)); +} + +TYPED_TEST(OtlpLogRecordableIntAttributeTest, SetIntArrayAttribute) +{ + using IntType = typename TestFixture::IntParamType; + + const int kArraySize = 3; + IntType int_arr[kArraySize] = {4, 5, 6}; + nostd::span<const IntType> int_span(int_arr); + + OtlpLogRecordable rec; + rec.SetAttribute("int_arr_attr", int_span); + + for (int i = 0; i < kArraySize; i++) + { + EXPECT_EQ(rec.log_record().attributes(0).value().array_value().values(i).int_value(), + int_span[i]); + } +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_recordable_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_recordable_test.cc new file mode 100644 index 000000000..b46802d87 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/otlp/test/otlp_recordable_test.cc @@ -0,0 +1,321 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/otlp/otlp_recordable.h" +#include <gtest/gtest.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +namespace trace_api = opentelemetry::trace; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace resource = opentelemetry::sdk::resource; +namespace proto = opentelemetry::proto; + +TEST(OtlpRecordable, SetIdentity) +{ + constexpr uint8_t trace_id_buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}; + constexpr uint8_t span_id_buf[] = {1, 2, 3, 4, 5, 6, 7, 8}; + constexpr uint8_t parent_span_id_buf[] = {8, 7, 6, 5, 4, 3, 2, 1}; + trace_api::TraceId trace_id{trace_id_buf}; + trace_api::SpanId span_id{span_id_buf}; + trace_api::SpanId parent_span_id{parent_span_id_buf}; + const auto trace_state = trace_api::TraceState::GetDefault()->Set("key1", "value"); + const trace_api::SpanContext span_context{ + trace_id, span_id, trace_api::TraceFlags{trace_api::TraceFlags::kIsSampled}, true, + trace_state}; + + OtlpRecordable rec; + + rec.SetIdentity(span_context, parent_span_id); + + EXPECT_EQ(rec.span().trace_id(), std::string(reinterpret_cast<const char *>(trace_id.Id().data()), + trace::TraceId::kSize)); + EXPECT_EQ(rec.span().span_id(), + std::string(reinterpret_cast<const char *>(span_id.Id().data()), trace::SpanId::kSize)); + EXPECT_EQ(rec.span().trace_state(), "key1=value"); + EXPECT_EQ(rec.span().parent_span_id(), + std::string(reinterpret_cast<const char *>(parent_span_id.Id().data()), + trace::SpanId::kSize)); + + OtlpRecordable rec_invalid_parent; + + constexpr uint8_t invalid_parent_span_id_buf[] = {0, 0, 0, 0, 0, 0, 0, 0}; + trace_api::SpanId invalid_parent_span_id{invalid_parent_span_id_buf}; + rec_invalid_parent.SetIdentity(span_context, invalid_parent_span_id); + + EXPECT_EQ(rec_invalid_parent.span().parent_span_id(), std::string{}); +} + +TEST(OtlpRecordable, SetSpanKind) +{ + OtlpRecordable rec; + trace_api::SpanKind span_kind = trace_api::SpanKind::kServer; + rec.SetSpanKind(span_kind); + EXPECT_EQ(rec.span().kind(), proto::trace::v1::Span_SpanKind::Span_SpanKind_SPAN_KIND_SERVER); +} + +TEST(OtlpRecordable, SetInstrumentationLibrary) +{ + OtlpRecordable rec; + auto inst_lib = trace_sdk::InstrumentationLibrary::Create("test", "v1"); + rec.SetInstrumentationLibrary(*inst_lib); + auto proto_instr_libr = rec.GetProtoInstrumentationLibrary(); + EXPECT_EQ(proto_instr_libr.name(), inst_lib->GetName()); + EXPECT_EQ(proto_instr_libr.version(), inst_lib->GetVersion()); +} + +TEST(OtlpRecordable, SetInstrumentationLibraryWithSchemaURL) +{ + OtlpRecordable rec; + const std::string expected_schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto inst_lib = trace_sdk::InstrumentationLibrary::Create("test", "v1", expected_schema_url); + rec.SetInstrumentationLibrary(*inst_lib); + EXPECT_EQ(expected_schema_url, rec.GetInstrumentationLibrarySchemaURL()); +} + +TEST(OtlpRecordable, SetStartTime) +{ + OtlpRecordable rec; + std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); + common::SystemTimestamp start_timestamp(start_time); + + uint64_t unix_start = + std::chrono::duration_cast<std::chrono::nanoseconds>(start_time.time_since_epoch()).count(); + + rec.SetStartTime(start_timestamp); + EXPECT_EQ(rec.span().start_time_unix_nano(), unix_start); +} + +TEST(OtlpRecordable, SetDuration) +{ + OtlpRecordable rec; + // Start time is 0 + common::SystemTimestamp start_timestamp; + + std::chrono::nanoseconds duration(10); + uint64_t unix_end = duration.count(); + + rec.SetStartTime(start_timestamp); + rec.SetDuration(duration); + + EXPECT_EQ(rec.span().end_time_unix_nano(), unix_end); +} + +TEST(OtlpRecordable, SetStatus) +{ + OtlpRecordable rec1; + trace::StatusCode code_error(trace::StatusCode::kError); + nostd::string_view description = "For test"; + rec1.SetStatus(code_error, description); + + EXPECT_EQ(rec1.span().status().code(), proto::trace::v1::Status_StatusCode(code_error)); + EXPECT_EQ(rec1.span().status().message(), description); + + OtlpRecordable rec2; + trace::StatusCode code_ok(trace::StatusCode::kOk); + rec2.SetStatus(code_ok, description); + EXPECT_EQ(rec2.span().status().code(), proto::trace::v1::Status_StatusCode(code_ok)); + EXPECT_EQ(rec2.span().status().message(), ""); +} + +TEST(OtlpRecordable, AddEventDefault) +{ + OtlpRecordable rec; + nostd::string_view name = "Test Event"; + + std::chrono::system_clock::time_point event_time = std::chrono::system_clock::now(); + common::SystemTimestamp event_timestamp(event_time); + + rec.sdk::trace::Recordable::AddEvent(name, event_timestamp); + + uint64_t unix_event_time = + std::chrono::duration_cast<std::chrono::nanoseconds>(event_time.time_since_epoch()).count(); + + EXPECT_EQ(rec.span().events(0).name(), name); + EXPECT_EQ(rec.span().events(0).time_unix_nano(), unix_event_time); + EXPECT_EQ(rec.span().events(0).attributes().size(), 0); +} + +TEST(OtlpRecordable, AddEventWithAttributes) +{ + OtlpRecordable rec; + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int values[kNumAttributes] = {4, 7, 23}; + std::map<std::string, int> attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + rec.AddEvent("Test Event", std::chrono::system_clock::now(), + common::KeyValueIterableView<std::map<std::string, int>>(attributes)); + + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(rec.span().events(0).attributes(i).key(), keys[i]); + EXPECT_EQ(rec.span().events(0).attributes(i).value().int_value(), values[i]); + } +} + +TEST(OtlpRecordable, AddLink) +{ + OtlpRecordable rec; + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int values[kNumAttributes] = {5, 12, 40}; + std::map<std::string, int> attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + auto trace_id = rec.span().trace_id(); + auto span_id = rec.span().span_id(); + + trace::TraceFlags flags; + std::string trace_state_header = "k1=v1,k2=v2"; + auto ts = trace::TraceState::FromHeader(trace_state_header); + + rec.AddLink(trace::SpanContext(trace::TraceId(), trace::SpanId(), flags, false, ts), + common::KeyValueIterableView<std::map<std::string, int>>(attributes)); + + EXPECT_EQ(rec.span().trace_id(), trace_id); + EXPECT_EQ(rec.span().span_id(), span_id); + EXPECT_EQ(rec.span().links(0).trace_state(), trace_state_header); + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(rec.span().links(0).attributes(i).key(), keys[i]); + EXPECT_EQ(rec.span().links(0).attributes(i).value().int_value(), values[i]); + } +} + +TEST(OtlpRecordable, SetResource) +{ + OtlpRecordable rec; + const std::string service_name_key = "service.name"; + std::string service_name = "test-otlp"; + auto resource = resource::Resource::Create({{service_name_key, service_name}}); + rec.SetResource(resource); + + auto proto_resource = rec.ProtoResource(); + bool found_service_name = false; + for (int i = 0; i < proto_resource.attributes_size(); i++) + { + auto attr = proto_resource.attributes(static_cast<int>(i)); + if (attr.key() == service_name_key && attr.value().string_value() == service_name) + { + found_service_name = true; + } + } + EXPECT_TRUE(found_service_name); +} + +TEST(OtlpRecordable, SetResourceWithSchemaURL) +{ + OtlpRecordable rec; + const std::string service_name_key = "service.name"; + const std::string service_name = "test-otlp"; + const std::string expected_schema_url = "https://opentelemetry.io/schemas/1.11.0"; + auto resource = + resource::Resource::Create({{service_name_key, service_name}}, expected_schema_url); + rec.SetResource(resource); + + EXPECT_EQ(expected_schema_url, rec.GetResourceSchemaURL()); +} + +// Test non-int single types. Int single types are tested using templates (see IntAttributeTest) +TEST(OtlpRecordable, SetSingleAttribute) +{ + OtlpRecordable rec; + nostd::string_view bool_key = "bool_attr"; + common::AttributeValue bool_val(true); + rec.SetAttribute(bool_key, bool_val); + + nostd::string_view double_key = "double_attr"; + common::AttributeValue double_val(3.3); + rec.SetAttribute(double_key, double_val); + + nostd::string_view str_key = "str_attr"; + common::AttributeValue str_val(nostd::string_view("Test")); + rec.SetAttribute(str_key, str_val); + + EXPECT_EQ(rec.span().attributes(0).key(), bool_key); + EXPECT_EQ(rec.span().attributes(0).value().bool_value(), nostd::get<bool>(bool_val)); + + EXPECT_EQ(rec.span().attributes(1).key(), double_key); + EXPECT_EQ(rec.span().attributes(1).value().double_value(), nostd::get<double>(double_val)); + + EXPECT_EQ(rec.span().attributes(2).key(), str_key); + EXPECT_EQ(rec.span().attributes(2).value().string_value(), + nostd::get<nostd::string_view>(str_val).data()); +} + +// Test non-int array types. Int array types are tested using templates (see IntAttributeTest) +TEST(OtlpRecordable, SetArrayAttribute) +{ + OtlpRecordable rec; + const int kArraySize = 3; + + bool bool_arr[kArraySize] = {true, false, true}; + nostd::span<const bool> bool_span(bool_arr); + rec.SetAttribute("bool_arr_attr", bool_span); + + double double_arr[kArraySize] = {22.3, 33.4, 44.5}; + nostd::span<const double> double_span(double_arr); + rec.SetAttribute("double_arr_attr", double_span); + + nostd::string_view str_arr[kArraySize] = {"Hello", "World", "Test"}; + nostd::span<const nostd::string_view> str_span(str_arr); + rec.SetAttribute("str_arr_attr", str_span); + + for (int i = 0; i < kArraySize; i++) + { + EXPECT_EQ(rec.span().attributes(0).value().array_value().values(i).bool_value(), bool_span[i]); + EXPECT_EQ(rec.span().attributes(1).value().array_value().values(i).double_value(), + double_span[i]); + EXPECT_EQ(rec.span().attributes(2).value().array_value().values(i).string_value(), str_span[i]); + } +} + +/** + * AttributeValue can contain different int types, such as int, int64_t, + * unsigned int, and uint64_t. To avoid writing test cases for each, we can + * use a template approach to test all int types. + */ +template <typename T> +struct IntAttributeTest : public testing::Test +{ + using IntParamType = T; +}; + +using IntTypes = testing::Types<int, int64_t, unsigned int, uint64_t>; +TYPED_TEST_SUITE(IntAttributeTest, IntTypes); + +TYPED_TEST(IntAttributeTest, SetIntSingleAttribute) +{ + using IntType = typename TestFixture::IntParamType; + IntType i = 2; + common::AttributeValue int_val(i); + + OtlpRecordable rec; + rec.SetAttribute("int_attr", int_val); + EXPECT_EQ(rec.span().attributes(0).value().int_value(), nostd::get<IntType>(int_val)); +} + +TYPED_TEST(IntAttributeTest, SetIntArrayAttribute) +{ + using IntType = typename TestFixture::IntParamType; + + const int kArraySize = 3; + IntType int_arr[kArraySize] = {4, 5, 6}; + nostd::span<const IntType> int_span(int_arr); + + OtlpRecordable rec; + rec.SetAttribute("int_arr_attr", int_span); + + for (int i = 0; i < kArraySize; i++) + { + EXPECT_EQ(rec.span().attributes(0).value().array_value().values(i).int_value(), int_span[i]); + } +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE |