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 | |
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')
143 files changed, 30258 insertions, 0 deletions
diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/CMakeLists.txt new file mode 100644 index 000000000..862d2c779 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright 2021, 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. + +if(WITH_OTLP) + add_subdirectory(otlp) +endif() + +add_subdirectory(ostream) +add_subdirectory(memory) + +if(WITH_PROMETHEUS) + add_subdirectory(prometheus) +endif() + +if(WITH_ZIPKIN) + add_subdirectory(zipkin) +endif() + +if(WITH_ELASTICSEARCH AND WITH_LOGS_PREVIEW) + add_subdirectory(elasticsearch) +endif() + +if(WITH_ETW) + add_subdirectory(etw) +endif() + +if(WITH_JAEGER) + add_subdirectory(jaeger) +endif() diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/BUILD new file mode 100644 index 000000000..78ff94d46 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/BUILD @@ -0,0 +1,43 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "es_log_exporter", + srcs = [ + "src/es_log_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/elasticsearch/es_log_exporter.h", + "include/opentelemetry/exporters/elasticsearch/es_log_recordable.h", + ], + linkopts = select({ + "//bazel:windows": [ + "-DEFAULTLIB:advapi32.lib", + "-DEFAULTLIB:crypt32.lib", + "-DEFAULTLIB:Normaliz.lib", + ], + "//conditions:default": [], + }), + strip_include_prefix = "include", + tags = ["es"], + deps = [ + "//ext:headers", + "//ext/src/http/client/curl:http_client_curl", + "//sdk/src/logs", + "@curl", + "@github_nlohmann_json//:json", + ], +) + +cc_test( + name = "es_log_exporter_test", + srcs = ["test/es_log_exporter_test.cc"], + tags = [ + "es", + "test", + ], + deps = [ + ":es_log_exporter", + "@com_google_googletest//:gtest_main", + "@curl", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/CMakeLists.txt new file mode 100644 index 000000000..ed6488316 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/CMakeLists.txt @@ -0,0 +1,41 @@ +add_library(opentelemetry_exporter_elasticsearch_logs src/es_log_exporter.cc) + +set_target_properties(opentelemetry_exporter_elasticsearch_logs + PROPERTIES EXPORT_NAME elasticsearch_log_exporter) + +target_include_directories( + opentelemetry_exporter_elasticsearch_logs + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + +target_link_libraries( + opentelemetry_exporter_elasticsearch_logs + PUBLIC opentelemetry_trace opentelemetry_logs opentelemetry_http_client_curl + nlohmann_json::nlohmann_json) + +install( + TARGETS opentelemetry_exporter_elasticsearch_logs + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/elasticsearch + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h" + PATTERN "es_log_recordable.h" EXCLUDE) + +if(BUILD_TESTING) + add_executable(es_log_exporter_test test/es_log_exporter_test.cc) + + target_link_libraries( + es_log_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_elasticsearch_logs) + + gtest_add_tests( + TARGET es_log_exporter_test + TEST_PREFIX exporter. + TEST_LIST es_log_exporter_test) +endif() # BUILD_TESTING diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_exporter.h new file mode 100644 index 000000000..ea58807e9 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_exporter.h @@ -0,0 +1,114 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +# include "nlohmann/json.hpp" +# include "opentelemetry/common/spin_lock_mutex.h" +# include "opentelemetry/ext/http/client/curl/http_client_curl.h" +# include "opentelemetry/nostd/type_traits.h" +# include "opentelemetry/sdk/logs/exporter.h" +# include "opentelemetry/sdk/logs/log_record.h" + +# include <time.h> +# include <iostream> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ +/** + * Struct to hold Elasticsearch exporter configuration options. + */ +struct ElasticsearchExporterOptions +{ + // Configuration options to establish Elasticsearch connection + std::string host_; + int port_; + std::string index_; + + // Maximum time to wait for response after sending request to Elasticsearch + int response_timeout_; + + // Whether to print the status of the exporter in the console + bool console_debug_; + + /** + * Constructor for the ElasticsearchExporterOptions. By default, the endpoint is + * localhost:9200/logs with a timeout of 30 seconds and disabled console debugging + * @param host The host of the Elasticsearch instance + * @param port The port of the Elasticsearch instance + * @param index The index/shard that the logs will be written to + * @param response_timeout The maximum time in seconds the exporter should wait for a response + * from elasticsearch + * @param console_debug If true, print the status of the exporter methods in the console + */ + ElasticsearchExporterOptions(std::string host = "localhost", + int port = 9200, + std::string index = "logs", + int response_timeout = 30, + bool console_debug = false) + : host_{host}, + port_{port}, + index_{index}, + response_timeout_{response_timeout}, + console_debug_{console_debug} + {} +}; + +/** + * The ElasticsearchLogExporter exports logs to Elasticsearch in JSON format + */ +class ElasticsearchLogExporter final : public opentelemetry::sdk::logs::LogExporter +{ +public: + /** + * Create an ElasticsearchLogExporter with default exporter options. + */ + ElasticsearchLogExporter(); + + /** + * Create an ElasticsearchLogExporter with user specified options. + * @param options An object containing the user's configuration options. + */ + ElasticsearchLogExporter(const ElasticsearchExporterOptions &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. + */ + sdk::common::ExportResult Export( + const opentelemetry::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: + // Stores if this exporter had its Shutdown() method called + bool is_shutdown_ = false; + + // Configuration options for the exporter + ElasticsearchExporterOptions options_; + + // Object that stores the HTTP sessions that have been created + std::unique_ptr<ext::http::client::HttpClient> http_client_; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; +}; +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h new file mode 100755 index 000000000..c38bf8769 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h @@ -0,0 +1,229 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +# include <map> +# include <sstream> +# include <type_traits> +# include <unordered_map> + +# include "nlohmann/json.hpp" +# include "opentelemetry/sdk/common/attribute_utils.h" +# include "opentelemetry/sdk/logs/recordable.h" +# include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ + +/** + * An Elasticsearch Recordable implemenation that stores the 10 fields of the Log Data Model inside + * a JSON object, + */ +class ElasticSearchRecordable final : public sdk::logs::Recordable +{ +private: + // Define a JSON object that will be populated with the log data + nlohmann::json json_; + + /** + * A helper method that writes a key/value pair under a specified name, the two names used here + * being "attributes" and "resources" + */ + void WriteKeyValue(nostd::string_view key, + const opentelemetry::common::AttributeValue &value, + std::string name) + { + switch (value.index()) + { + case common::AttributeType::kTypeBool: + json_[name][key.data()] = opentelemetry::nostd::get<bool>(value) ? true : false; + return; + case common::AttributeType::kTypeInt: + json_[name][key.data()] = opentelemetry::nostd::get<int>(value); + return; + case common::AttributeType::kTypeInt64: + json_[name][key.data()] = opentelemetry::nostd::get<int64_t>(value); + return; + case common::AttributeType::kTypeUInt: + json_[name][key.data()] = opentelemetry::nostd::get<unsigned int>(value); + return; + case common::AttributeType::kTypeUInt64: + json_[name][key.data()] = opentelemetry::nostd::get<uint64_t>(value); + return; + case common::AttributeType::kTypeDouble: + json_[name][key.data()] = opentelemetry::nostd::get<double>(value); + return; + case common::AttributeType::kTypeCString: + json_[name][key.data()] = opentelemetry::nostd::get<const char *>(value); + return; + case common::AttributeType::kTypeString: + json_[name][key.data()] = + opentelemetry::nostd::get<opentelemetry::nostd::string_view>(value).data(); + return; + default: + return; + } + } + + void WriteKeyValue(nostd::string_view key, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + std::string name) + { + namespace common = opentelemetry::sdk::common; + switch (value.index()) + { + case common::kTypeBool: + json_[name][key.data()] = opentelemetry::nostd::get<bool>(value) ? true : false; + return; + case common::kTypeInt: + json_[name][key.data()] = opentelemetry::nostd::get<int>(value); + return; + case common::kTypeInt64: + json_[name][key.data()] = opentelemetry::nostd::get<int64_t>(value); + return; + case common::kTypeUInt: + json_[name][key.data()] = opentelemetry::nostd::get<unsigned int>(value); + return; + case common::kTypeUInt64: + json_[name][key.data()] = opentelemetry::nostd::get<uint64_t>(value); + return; + case common::kTypeDouble: + json_[name][key.data()] = opentelemetry::nostd::get<double>(value); + return; + case common::kTypeString: + json_[name][key.data()] = opentelemetry::nostd::get<std::string>(value).data(); + return; + default: + return; + } + } + +public: + /** + * Set the severity for this log. + * @param severity the severity of the event + */ + void SetSeverity(opentelemetry::logs::Severity severity) noexcept override + { + // Convert the severity enum to a string + std::uint32_t severity_index = static_cast<std::uint32_t>(severity); + if (severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value) + { + std::stringstream sout; + sout << "Invalid severity(" << severity_index << ")"; + json_["severity"] = sout.str(); + } + else + { + json_["severity"] = opentelemetry::logs::SeverityNumToText[severity_index]; + } + } + + /** + * Set body field for this log. + * @param message the body to set + */ + void SetBody(nostd::string_view message) noexcept override { json_["body"] = message.data(); } + + /** + * Set Resource of this log + * @param Resource the resource to set + */ + void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override + { + for (auto &kv : resource.GetAttributes()) + { + WriteKeyValue(kv.first, kv.second, "resource"); + } + } + + /** + * Set an attribute of a log. + * @param key the key of the attribute + * @param value the attribute value + */ + void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept override + { + WriteKeyValue(key, value, "attributes"); + } + + /** + * Set trace id for this log. + * @param trace_id the trace id to set + */ + void SetTraceId(opentelemetry::trace::TraceId trace_id) noexcept override + { + char trace_buf[32]; + trace_id.ToLowerBase16(trace_buf); + json_["traceid"] = std::string(trace_buf, sizeof(trace_buf)); + } + + /** + * Set span id for this log. + * @param span_id the span id to set + */ + virtual void SetSpanId(opentelemetry::trace::SpanId span_id) noexcept override + { + char span_buf[16]; + span_id.ToLowerBase16(span_buf); + json_["spanid"] = std::string(span_buf, sizeof(span_buf)); + } + + /** + * Inject a trace_flags for this log. + * @param trace_flags the span id to set + */ + void SetTraceFlags(opentelemetry::trace::TraceFlags trace_flags) noexcept override + { + char flag_buf[2]; + trace_flags.ToLowerBase16(flag_buf); + json_["traceflags"] = std::string(flag_buf, sizeof(flag_buf)); + } + + /** + * Set the timestamp for this log. + * @param timestamp the timestamp of the event + */ + void SetTimestamp(common::SystemTimestamp timestamp) noexcept override + { + json_["timestamp"] = timestamp.time_since_epoch().count(); + } + + /** + * Returns a JSON object contain the log information + */ + nlohmann::json GetJSON() noexcept { return json_; } + + /** + * Set instrumentation_library for this log. + * @param instrumentation_library the instrumentation library to set + */ + void SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept + { + json_["name"] = instrumentation_library.GetName(); + instrumentation_library_ = &instrumentation_library; + } + + /** Returns the associated instruementation library */ + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary & + GetInstrumentationLibrary() const noexcept + { + return *instrumentation_library_; + } + +private: + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + *instrumentation_library_ = nullptr; +}; +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/src/es_log_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/src/es_log_exporter.cc new file mode 100644 index 000000000..a5a66ebe0 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/src/es_log_exporter.cc @@ -0,0 +1,221 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include <sstream> // std::stringstream + +# include <mutex> +# include "opentelemetry/exporters/elasticsearch/es_log_exporter.h" +# include "opentelemetry/exporters/elasticsearch/es_log_recordable.h" +# include "opentelemetry/sdk_config.h" + +namespace nostd = opentelemetry::nostd; +namespace sdklogs = opentelemetry::sdk::logs; +namespace http_client = opentelemetry::ext::http::client; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ +/** + * 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()); + + // Set the response_received_ flag to true and notify any threads waiting on this result + response_received_ = true; + } + cv_.notify_all(); + } + + /** + * A method the user calls to block their thread until the response is received. The longest + * duration is the timeout of the request, set by SetTimeoutMs() + */ + bool waitForResponse() + { + std::unique_lock<std::mutex> lk(mutex_); + cv_.wait(lk); + 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, nostd::string_view reason) noexcept override + { + // If any failure event occurs, release the condition variable to unblock main thread + switch (state) + { + case http_client::SessionState::ConnectFailed: + OTEL_INTERNAL_LOG_ERROR("[ES Trace Exporter] Connection to elasticsearch failed"); + cv_.notify_all(); + break; + case http_client::SessionState::SendFailed: + OTEL_INTERNAL_LOG_ERROR("[ES Trace Exporter] Request failed to be sent to elasticsearch"); + cv_.notify_all(); + break; + case http_client::SessionState::TimedOut: + OTEL_INTERNAL_LOG_ERROR("[ES Trace Exporter] Request to elasticsearch timed out"); + cv_.notify_all(); + break; + case http_client::SessionState::NetworkError: + OTEL_INTERNAL_LOG_ERROR("[ES Trace Exporter] Network error to elasticsearch"); + cv_.notify_all(); + break; + } + } + +private: + // Define a condition variable and mutex + std::condition_variable cv_; + std::mutex mutex_; + + // Whether the response from Elasticsearch has been received + bool response_received_ = false; + + // A string to store the response body + std::string body_ = ""; + + // Whether to print the results from the callback + bool console_debug_ = false; +}; + +ElasticsearchLogExporter::ElasticsearchLogExporter() + : options_{ElasticsearchExporterOptions()}, + http_client_{new ext::http::client::curl::HttpClient()} +{} + +ElasticsearchLogExporter::ElasticsearchLogExporter(const ElasticsearchExporterOptions &options) + : options_{options}, http_client_{new ext::http::client::curl::HttpClient()} +{} + +std::unique_ptr<sdklogs::Recordable> ElasticsearchLogExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<sdklogs::Recordable>(new ElasticSearchRecordable); +} + +sdk::common::ExportResult ElasticsearchLogExporter::Export( + const nostd::span<std::unique_ptr<sdklogs::Recordable>> &records) noexcept +{ + // Return failure if this exporter has been shutdown + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[ES Log Exporter] Exporting " + << records.size() << " log(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + + // Create a connection to the ElasticSearch instance + auto session = http_client_->CreateSession(options_.host_ + std::to_string(options_.port_)); + auto request = session->CreateRequest(); + + // Populate the request with headers and methods + request->SetUri(options_.index_ + "/_bulk?pretty"); + request->SetMethod(http_client::Method::Post); + request->AddHeader("Content-Type", "application/json"); + request->SetTimeoutMs(std::chrono::milliseconds(1000 * options_.response_timeout_)); + + // Create the request body + std::string body = ""; + for (auto &record : records) + { + // Append {"index":{}} before JSON body, which tells Elasticsearch to write to index specified + // in URI + body += "{\"index\" : {}}\n"; + + // Add the context of the Recordable + auto json_record = std::unique_ptr<ElasticSearchRecordable>( + static_cast<ElasticSearchRecordable *>(record.release())); + body += json_record->GetJSON().dump() + "\n"; + } + std::vector<uint8_t> body_vec(body.begin(), body.end()); + request->SetBody(body_vec); + + // 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( + "[ES Trace Exporter] waiting for response from Elasticsearch (timeout = " + << options_.response_timeout_ << " seconds)"); + } + 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 sdk::common::ExportResult::kFailure; + } + + // Parse the response output to determine if Elasticsearch consumed it correctly + std::string responseBody = handler->GetResponseBody(); + if (responseBody.find("\"failed\" : 0") == std::string::npos) + { + OTEL_INTERNAL_LOG_ERROR( + "[ES Trace Exporter] Logs were not written to Elasticsearch correctly, response body: " + << responseBody); + // TODO: Retry logic + return sdk::common::ExportResult::kFailure; + } + + return sdk::common::ExportResult::kSuccess; +} + +bool ElasticsearchLogExporter::Shutdown(std::chrono::microseconds timeout) 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 ElasticsearchLogExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/test/es_log_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/test/es_log_exporter_test.cc new file mode 100644 index 000000000..943f9fa7b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/elasticsearch/test/es_log_exporter_test.cc @@ -0,0 +1,86 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/exporters/elasticsearch/es_log_exporter.h" +# include "opentelemetry/ext/http/server/http_server.h" +# include "opentelemetry/logs/provider.h" +# include "opentelemetry/sdk/logs/exporter.h" +# include "opentelemetry/sdk/logs/log_record.h" +# include "opentelemetry/sdk/logs/logger_provider.h" +# include "opentelemetry/sdk/logs/simple_log_processor.h" + +# include <gtest/gtest.h> +# include <iostream> + +namespace sdklogs = opentelemetry::sdk::logs; +namespace logs_api = opentelemetry::logs; +namespace nostd = opentelemetry::nostd; +namespace logs_exporter = opentelemetry::exporter::logs; + +TEST(ElasticsearchLogsExporterTests, Dummy) +{ + // to enable linking +} + +# if 0 +// Attempt to write a log to an invalid host/port, test that the Export() returns failure +TEST(ElasticsearchLogsExporterTests, InvalidEndpoint) +{ + // Create invalid connection options for the elasticsearch exporter + logs_exporter::ElasticsearchExporterOptions options("localhost", -1); + + // Create an elasticsearch exporter + auto exporter = + std::unique_ptr<sdklogs::LogExporter>(new logs_exporter::ElasticsearchLogExporter(options)); + + // Create a log record and send to the exporter + auto record = exporter->MakeRecordable(); + auto result = exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1)); + + // Ensure the return value is failure + ASSERT_EQ(result, sdk::common::ExportResult::kFailure); +} + +// Test that when the exporter is shutdown, any call to Export should return failure +TEST(ElasticsearchLogsExporterTests, Shutdown) +{ + // Create an elasticsearch exporter and immediately shut it down + auto exporter = + std::unique_ptr<sdklogs::LogExporter>(new logs_exporter::ElasticsearchLogExporter); + bool shutdownResult = exporter->Shutdown(); + ASSERT_TRUE(shutdownResult); + + // Write a log to the shutdown exporter + auto record = exporter->MakeRecordable(); + auto result = exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1)); + + // Ensure the return value is failure + ASSERT_EQ(result, sdk::common::ExportResult::kFailure); +} + +// Test the elasticsearch recordable object +TEST(ElasticsearchLogsExporterTests, RecordableCreation) +{ + // Create an elasticsearch exporter + auto exporter = + std::unique_ptr<sdklogs::LogExporter>(new logs_exporter::ElasticsearchLogExporter); + + // Create a recordable + auto record = exporter->MakeRecordable(); + record->SetSeverity(logs_api::Severity::kFatal); + record->SetTimestamp(std::chrono::system_clock::now()); + record->SetBody("Body of the log message"); + + // Attributes and resource support different types + record->SetAttribute("key0", false); + record->SetAttribute("key1", "1"); + + auto resource = opentelemetry::sdk::resource::Resource::Create({{"key2", 2}, {"key3", 3142}}); + record->SetResource(resource); + + exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1)); +} +# endif +#endif
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/etw/BUILD new file mode 100644 index 000000000..c2328ed4e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/BUILD @@ -0,0 +1,69 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "etw_exporter", + hdrs = glob([ + "include/opentelemetry/exporters/etw/*.h", + ]), + includes = ["include"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = ["etw"], + deps = [ + "//api", + "//sdk/src/trace", + "@github_nlohmann_json//:json", + ], +) + +cc_test( + name = "etw_provider_test", + srcs = ["test/etw_provider_test.cc"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = [ + "etw", + "test", + ], + deps = [ + ":etw_exporter", + "@com_google_googletest//:gtest_main", + "@github_nlohmann_json//:json", + ], +) + +cc_test( + name = "etw_tracer_test", + srcs = ["test/etw_tracer_test.cc"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = [ + "etw", + "test", + ], + deps = [ + ":etw_exporter", + "@com_google_googletest//:gtest_main", + "@github_nlohmann_json//:json", + ], +) + +cc_test( + name = "etw_logger_test", + srcs = ["test/etw_logger_test.cc"], + local_defines = [ + "HAVE_MSGPACK", + ], + tags = [ + "etw", + "test", + ], + deps = [ + ":etw_exporter", + "@com_google_googletest//:gtest_main", + "@github_nlohmann_json//:json", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/etw/CMakeLists.txt new file mode 100644 index 000000000..47791b4ab --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/CMakeLists.txt @@ -0,0 +1,63 @@ +add_library(opentelemetry_exporter_etw INTERFACE) + +target_include_directories( + opentelemetry_exporter_etw + INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + +set_target_properties(opentelemetry_exporter_etw PROPERTIES EXPORT_NAME + etw_exporter) + +target_link_libraries(opentelemetry_exporter_etw + INTERFACE nlohmann_json::nlohmann_json) +if(nlohmann_json_clone) + add_dependencies(opentelemetry_exporter_etw nlohmann_json::nlohmann_json) +endif() + +install( + TARGETS opentelemetry_exporter_etw + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/etw + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h") + +if(BUILD_TESTING) + add_executable(etw_provider_test test/etw_provider_test.cc) + add_executable(etw_tracer_test test/etw_tracer_test.cc) + add_executable(etw_logger_test test/etw_logger_test.cc) + + add_executable(etw_perf_test test/etw_perf_test.cc) + + target_link_libraries(etw_provider_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + target_link_libraries(etw_tracer_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + target_link_libraries(etw_logger_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + target_link_libraries( + etw_perf_test benchmark::benchmark ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_etw ${CMAKE_THREAD_LIBS_INIT}) + + gtest_add_tests( + TARGET etw_provider_test + TEST_PREFIX exporter. + TEST_LIST etw_provider_test) + gtest_add_tests( + TARGET etw_tracer_test + TEST_PREFIX exporter. + TEST_LIST etw_tracer_test) + gtest_add_tests( + TARGET etw_logger_test + TEST_PREFIX exporter. + TEST_LIST etw_logger_test) + +endif() # BUILD_TESTING diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/README.md b/src/jaegertracing/opentelemetry-cpp/exporters/etw/README.md new file mode 100644 index 000000000..29dc3de43 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/README.md @@ -0,0 +1,226 @@ +# Getting Started with OpenTelemetry C++ SDK and ETW exporter on Windows + +Event Tracing for Windows (ETW) is an efficient kernel-level tracing facility +that lets you log kernel or application-defined events to a log file. You can +consume the events in real time or from a log file and use them to debug an +application or to determine where performance issues are occurring in the +application. + +OpenTelemetry C++ SDK ETW exporter allows the code instrumented using +OpenTelemetry API to forward events to out-of-process ETW listener, for +subsequent data recording or forwarding to alternate pipelines and flows. +Windows Event Tracing infrastructure is available to any vendor or application +being deployed on Windows. + +## API support + +These are the features planned to be supported by ETW exporter: + +- [x] OpenTelemetry Tracing API and SDK headers are **stable** and moving + towards GA. +- [ ] OpenTelemetry Logging API is work-in-progress, pending implementation of + [Latest Logging API spec + here](https://github.com/open-telemetry/oteps/pull/150) +- [ ] OpenTelemetry Metrics API is not implemented yet. + +Implementation of OpenTelemetry C++ SDK ETW exporter on Windows OS is `header +only` : + +- full definitions of all macros, functions and classes comprising the library +are visible to the compiler in a header file form. +- implementation does not need to be separately compiled, packaged and installed + in order to be used. + +All that is required is to point the compiler at the location of the headers, +and then `#include` the header files into the application source. Compiler's +optimizer can do a much better job when all the library's source code is +available. Several options below may be turned on to optimize the code with the +usage of standard C++ library, Microsoft Guidelines Support library, Google +Abseil Variant library. Or enabling support for non-standard features, such as +8-bit byte arrays support that enables performance-efficient representation of +binary blobs on ETW wire. + +## Example project + +The following include directories are required, relative to the top-level source +tree of OpenTelemetry C++ repo: + +- api/include/ +- exporters/etw/include/ +- sdk/include/ + +Code that instantiates ETW TracerProvider, subsequently obtaining a Tracer bound +to `OpenTelemetry-ETW-Provider`, and emitting a span named `MySpan` with +attributes on it, as well as `MyEvent` within that span. + +```cpp + +#include <map> +#include <string> + +#include "opentelemetry/exporters/etw/etw_tracer_exporter.h" + +using namespace OPENTELEMETRY_NAMESPACE; +using namespace opentelemetry::exporter::etw; + +// Supply unique instrumentation name (ETW Provider Name) here: +std::string providerName = "OpenTelemetry-ETW-Provider"; + +exporter::etw::TracerProvider tp; + +int main(int argc, const char* argv[]) +{ + // Obtain a Tracer object for instrumentation name. + // Each Tracer is associated with unique TraceId. + auto tracer = tp.GetTracer(providerName, "TLD"); + + // Properties is a helper class in ETW namespace that is otherwise compatible + // with Key-Value Iterable accepted by OpenTelemetry API. Using Properties + // should enable more efficient data transfer without unnecessary memcpy. + + // Span attributes + Properties attribs = + { + {"attrib1", 1}, + {"attrib2", 2} + }; + + // Start Span with attributes + auto span = tracer->StartSpan("MySpan", attribs); + + // Emit an event on Span + std::string eventName = "MyEvent1"; + Properties event = + { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"} + }; + span->AddEvent(eventName, event); + + // End Span. + span->End(); + + // Close the Tracer on application stop. + tracer->CloseWithMicroseconds(0); + + return 0; +} +``` + +Note that different `Tracer` objects may be bound to different ETW destinations. + +## Build options and Compiler Defines + +While including OpenTelemetry C++ SDK with ETW exporter, the customers are in +complete control of what options they would like to enable for their project +using `Preprocessor Definitions`. + +These options affect how "embedded in application" OpenTelemetry C++ SDK code is +compiled: + +| Name | Description | +|---------------------|------------------------------------------------------------------------------------------------------------------------| +| HAVE_CPP_STDLIB | Use STL classes for API surface. This option requires at least C++17. C++20 is recommended. Some customers may benefit from STL library provided with the compiler instead of using custom OpenTelemetry `nostd::` implementation due to security and performance considerations. | +| HAVE_GSL | Use [Microsoft GSL](https://github.com/microsoft/GSL) for `gsl::span` implementation. Library must be in include path. Microsoft GSL claims to be the most feature-complete implementation of `std::span`. It may be used instead of `nostd::span` implementation in projects that statically link OpenTelemetry SDK. | +| HAVE_TLD | Use ETW/TraceLogging Dynamic protocol. This is the default implementation compatible with existing C# "listeners" / "decoders" of ETW events. This option requires an additional optional Microsoft MIT-licensed `TraceLoggingDynamic.h` header. | + +## Debugging + +### ETW TraceLogging Dynamic Events + +ETW supports a mode that is called "Dynamic Manifest", where event may contain +strongly-typed key-value pairs, with primitive types such as `integer`, +`double`, `string`, etc. This mode requires `TraceLoggingDynamic.h` header. +Please refer to upstream repository containining [Microsoft TraceLogging Dynamic +framework](https://github.com/microsoft/tracelogging-dynamic-windows) licensed +under [MIT +License](https://github.com/microsoft/tracelogging-dynamic-windows/blob/main/LICENSE). + +Complete [list of ETW +types](https://docs.microsoft.com/en-us/windows/win32/wes/eventmanifestschema-outputtype-complextype#remarks). + +OpenTelemetry C++ ETW exporter implements the following type mapping: + +| OpenTelemetry C++ API type | ETW type | +|----------------------------|-----------------| +| bool | xs:byte | +| int (32-bit) | xs:int | +| int (64-bit) | xs:long | +| uint (32-bit) | xs:unsignedInt | +| uint (64-bit) | xs:unsignedLong | +| double | xs:double | +| string | win:Utf8 | + +Support for arrays of primitive types is not implemented yet. + +Visual Studio 2019 allows to use `View -> Other Windows -> Diagnostic Events` to +capture events that are emitted by instrumented application and sent to ETW +provider in a live view. Instrumentation name passed to `GetTracer` API above +corresponds to `ETW Provider Name`. If Instrumentation name contains a GUID - +starts with a curly brace, e.g. `{deadbeef-fade-dead-c0de-cafebabefeed}` then +the parameter is assumed to be `ETW Provider GUID`. + +Click on `Settings` and add the provider to monitor either by its Name or by +GUID. In above example, the provider name is `OpenTelemetry-ETW-Provider`. +Please refer to Diagnostic Events usage instructions +[here](https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-diagnostics-how-to-monitor-and-diagnose-services-locally#view-service-fabric-system-events-in-visual-studio) +to learn more. Note that running ETW Listener in Visual Studio requires +Elevation, i.e. Visual Studio would prompt you to confirm that you accept to run +the ETW Listener process as Administrator. This is a limitation of ETW +Listeners, they must be run as privileged process. + +### ETW events encoded in MessagePack + +OpenTelemetry ETW exporter optionally allows to encode the incoming event +payload using [MessagePack](https://msgpack.org/index.html) compact binary +protocol. ETW/MsgPack encoding requires +[nlohmann/json](https://github.com/nlohmann/json) library to be included in the +build of OpenTelemetry ETW exporter. Any recent version of `nlohmann/json` is +compatible with ETW exporter. For example, the version included in +`third_party/nlohmann-json` directory may be used. + +There is currently **no built-in decoder available** for this format. However, +there is ongoing effort to include the ETW/MsgPack decoder in +[Azure/diagnostics-eventflow](https://github.com/Azure/diagnostics-eventflow) +project, which may be used as a side-car listener to forward incoming +ETW/MsgPack events to many other destinations, such as: + +- StdOutput (console output) +- HTTP (json via http) +- Application Insights +- Azure EventHub +- Elasticsearch +- Azure Monitor Logs + +And community-contributed exporters: + +- Google Big Query output +- SQL Server output +- ReflectInsight output +- Splunk output + +[This PR](https://github.com/Azure/diagnostics-eventflow/pull/382) implements +the `Input adapter` for OpenTelemetry ETW/MsgPack protocol encoded events for +Azure EventFlow. + +Other standard tools for processing ETW events on Windows OS, such as: + +- [PerfView](https://github.com/microsoft/perfview) +- [PerfViewJS](https://github.com/microsoft/perfview/tree/main/src/PerfViewJS) + +will be augmented in future with support for ETW/MsgPack encoding. + +## Addendum + +This document needs to be supplemented with additional information: + +- [ ] mapping between OpenTelemetry fields and concepts and their corresponding + ETW counterparts +- [ ] links to E2E instrumentation example and ETW listener +- [ ] Logging API example +- [ ] Metrics API example (once Metrics spec is finalized) +- [ ] example how ETW Listener may employ OpenTelemetry .NET SDK to 1-1 + transform from ETW events back to OpenTelemetry flow +- [ ] links to NuGet package that contains the source code of SDK that includes + OpenTelemetry SDK and ETW exporter diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/LICENSE b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/LICENSE new file mode 100644 index 000000000..cfc21fd2c --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/LICENSE @@ -0,0 +1,23 @@ +TraceLogging Dynamic for Windows + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/TraceLoggingDynamic.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/TraceLoggingDynamic.h new file mode 100644 index 000000000..17ee108c6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/TraceLoggingDynamic.h @@ -0,0 +1,3458 @@ +/* ++ + +Licensed under the MIT License <http://opensource.org/licenses/MIT>. +SPDX-License-Identifier: MIT +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + TraceLoggingDynamic.h + +Abstract: + + Dynamic TraceLogging Provider API for C++. + +Environment: + + User mode. + +--*/ + +#pragma once +#include <windows.h> +#include <evntprov.h> +#include <stdlib.h> // byteswap + +namespace tld +{ +#pragma region Public interface + + /* + GENERAL: + + - This implementation of manifest-free ETW supports more functionality + than the implementation in TraceLoggingProvider.h, but it also has + higher runtime costs. This implementation is intended for use only when + the set of events is not known at compile-time. For example, + TraceLoggingDynamic.h might be used to implement a library providing + manifest-free ETW to a scripting language like JavaScript or Perl. + - This header is not optimized for direct use by developers adding events + to their code. There is no way to make an optimal solution that + works for all of the intended target users. Instead, this header + provides various pieces that you can build upon to create a user-friendly + implementation of manifest-free ETW tailored for a specific scenario. + + HIGH-LEVEL API: + + The high-level API provides an easy way to get up and running with + TraceLogging ETW events. + + Contents: + - Class: tld::Provider + - Class: tld::Event + - Class: tld::EventBuilder + - Enum: tld::Type + + Basic usage: + - Create a Provider object. + - Check provider.IsEnabled(...) so that you don't do the remaining steps + if nobody is listening for your event. + - Create an Event<std::vector<BYTE>> object. + - Add fields definitions (metadata) and values (data) using methods on + Event. (You are responsible for making sure that the metadata you add + matches the data you add -- the Event object does not validate this.) + - Some methods on the Event object return EventBuilder objects, which are + used to build the fields of nested structures. Note that Event inherits + from EventBuilder, so if you write a function that accepts an + EventBuilder&, it will also accept an Event&. + - Once you've added all data and metadata, call event.Write() to send the + event to ETW. + + LOW-LEVEL API: + + The low-level API provides components that you can mix and match to build + your own solution when the high-level components don't precisely meet your + needs. For example, you might use the high-level Provider class to manage + your REGHANDLE and the ETW callbacks, but you might use + EventMetadataBuilder and EventDataBuilder directly instead of using Event + so that you can cache event definitions. + + Contents: + - Function: tld::RegisterProvider + - Function: tld::UnregisterProvider + - Function: tld::GetGuidForName + - Function: tld::SetInformation + - Class: tld::ProviderMetadataBuilder + - Class: tld::EventMetadataBuilder + - Class: tld::EventDataBuilder + - Struct: tld::EventDescriptor + - Class: tld::ByteArrayWrapper + - Enum: tld::InType + - Enum: tld::OutType + - Enum: tld::ProviderTraitType + - Function: tld::WriteEvent + - Function: tld::WriteEventEx + - Function: tld::PushBackAsUtf8 + - Function: tld::MakeType + - Macro: TLD_HAVE_EVENT_SET_INFORMATION + - Macro: TLD_HAVE_EVENT_WRITE_EX + + Notes: + - EventDataDescCreate is a native ETW API from <evntprov.h>. It is not + part of this API, but you may want to use it to initialize + EVENT_DATA_DESCRIPTOR structures instead of doing it directly. + - If you directly initialize your EVENT_DATA_DESCRIPTOR structures instead + of using EventDataDescCreate, be sure to properly initialize the + EVENT_DATA_DESCRIPTOR.Reserved field (e.g. by setting it to 0). + Initializing the Reserved field is NOT optional. (EventDataDescCreate + does correctly initialize the Reserved field.) + - When the API asks for a byte-buffer, you can use std::vector<BYTE> or + another similar type. If you don't want to use a vector, the provided + ByteArrayWrapper type allows you to use your own allocation strategy for + the buffer. + - By default, TraceLogging events have Id=0 and Version=0, indicating + that the event does not have an assigned Id. However, events can have + Id and Version assigned (typically assigned manually). If you don't want + to manage event IDs, set both Id and Version to 0. If you do assign + IDs to your events, the Id must be non-zero and there should be a + one-to-one mapping between {provider.Id + event.Id + event.Version} and + {event metadata}. In other words, any event with a given non-zero + Id+Version combination must always have exactly the same event metadata. + (Note to decoders: this can be used as an optimization, but do not rely + on providers to follow this rule.) + - TraceLogging events default to channel 11 (WINEVENT_CHANNEL_TRACELOGGING). + This channel has no effect other than to mark the event as TraceLogging- + compatible. Other channels can be used, but channels other than 11 will + only work if the provider is running on a version of Windows that + understands TraceLogging (Windows 7sp1 with latest updates, Windows 8.1 + with latest updates, Windows 10, or later). If your provider is running + on a version of Windows that does not understand TraceLogging and you use + a channel other than 11, the resulting events will not decode correctly. + + Low-level provider management: + - Use tld::ProviderMetadataBuilder to build a provider metadata blob. + - If you don't have a specific provider GUID already selected, use + tld::GetGuidForName to compute your provider GUID. + - Use tld::RegisterProvider to open the REGHANDLE. + - Use the REGHANDLE in calls to tld::WriteEvent. + - Use tld::UnregisterProvider to close the REGHANDLE. + + Low-level event management: + - Use tld::EventMetadataBuilder to build an event metadata blob. + - Use tld::Type values to define field types, or create non-standard + types using tld::MakeType, tld::InType, and tld::OutType. + - Use tld::EventDataBuilder to build an event data blob. + - Create an EVENT_DESCRIPTOR for your event. + - Optionally use the tld::EventDescriptor wrapper class to simplify + initialization of EVENT_DESCRIPTOR structures. + - Allocate an array of EVENT_DATA_DESCRIPTORs. Size should be two more + than you need for your event payload (the first two are reserved for the + provider and event metadata). + - Use EventDataDescCreate to fill in the data descriptor array (skipping + the first two) to reference your event payload data. + - Use tld::WriteEvent to write your event. + */ + + /* + class Provider (high-level API): + + Manages an ETW REGHANDLE and the provider metadata blob. + - Constructor creates the provider metadata blob and registers the provider. + Note that this includes configuring ETW callbacks. + - Destructor unregisters the provider and frees the metadata blob. + - IsEnabled APIs provide very efficient checks for whether an event needs + to be written. + - Write APIs call WriteEvent, automatically providing the correct REGHANDLE + and provider metadata blobs. + + Typical usage: + + tld::Provider provider("ProviderName", ...); + if (provider.IsEnabled(eventLevel, eventKeywords)) + { + (prepare event); + provider.Write(...); + } + + It is not a problem to call provider.Write without checking + provider.IsEnabled(...), but it is more efficient to check + provider.IsEnabled(...) so that you can skip the process of preparing the + event if the event is not enabled. + */ + class Provider; + + /* + class Event (high-level API): + + Manages the data and metadata for an event. You create an Event object, + set properties, add field definitions, add field values, and then Write + the event. + + ByteVectorTy will usually be std::vector<BYTE>, though other similar + types could be used instead. + + Typical usage: + + if (provider.IsEnabled(eventLevel, eventKeywords)) + { + Event event(provider, szEventName, eventLevel, eventKeywords); + (add fields definitions and values); + event.Write(); + } + + You can reuse an Event object by calling event.Reset(...). + */ + template<class ByteVectorTy> + class Event; + + /* + class EventBuilder (high-level API): + + This class exposes an interface for adding field metadata/data to an event. + The Event class inherits from EventBuilder so that it can expose this + interface. In addition, each call to eventBuilder.AddStruct(...) returns a + new EventBuilder that can be used to add fields to a nested structure. + You won't directly create instances of this type -- you'll use instances + returned by Event.AddStruct or by EventBuilder.AddStruct. + + You can use EventBuilder& as the type of a parameter to a function, and + then pass either an Event or an EventBuilder to that function (allowing + for recursive processing of event fields). + + Note that order is important but not checked when using multiple Event or + EventBuilder objects. Fields are always added to the end of the overall + event, regardless of which Event or EventBuilder you use to add the field. + The separate Event and EventBuilder objects are used for the sole purpose + of tracking the number of fields in each struct. + + Assume that you have obtained EventBuilder object b from object a, i.e. + EventBuilder b = a.AddStruct(...). You must complete all operations using + b before resuming any operations using a. If this rule is not followed, + fields may end up nesting in unexpected ways. + */ + template<class ByteVectorTy> + class EventBuilder; + + /* + enum Type (high-level API and low-level API): + + Types for event fields, corresponding to a combination of TDH_INTYPE and + TDH_OUTTYPE values. The Type enumeration contains predefined combinations + of InType and OutType that are known to work well and are recognized by + decoders such as TDH and xperf. + + Advanced use: It is possible to create other Type values by combining + values from the InType and OutType enumerations using the tld::MakeType + function. (Valid Type values consist of one InType combined with one + OutType.) However, custom combinations not already present in the Type + enumeration are unlikely to be recognized by xperf or other decoders, + and will usually end up being decoded as if they had used OutTypeDefault. + + When providing payload for a field, the payload data must match the InType. + Payload is always packed tightly with no alignment or padding. + + **** + + How to pack single values: + + Primitive values (int, GUID) are packed directly, with no padding or + alignment. Just reserve sizeof(value) bytes in the buffer, then memcpy the + value into the buffer. Note that there should be no alignment or padding + between items -- the payload is always tightly-packed. + + NUL-terminated strings are also simply memcpy'ed into the buffer. Be sure + to include the NUL termination when reserving space and copying the value. + There is no special encoding reserved for a NULL string. The encoding + helpers in this header simply treat a NULL string the same as an empty + string, i.e. AddString((char*)NULL) is the same as AppendString(""). + + Binary, CountedString, and CountedAnsiString scalars are all encoded as a + UINT16 byte-count followed by the string data. In this case, no NUL + termination should be included. Remember that the size is a byte count, + not a character count. + + **** + + How to pack arrays: + + Assume you have a function Pack(payloadVec, item) that correctly appends an + item to the end of payloadVec. + + Packing a variable-length array of N items is done by appending N (encoded + as UINT16) to payloadVec followed by calling Pack(...) N times. + + Packing a constant-length array of N items is done by calling Pack(...) N + times. The value of N is encoded in the metadata (it was provided in the + field's declaration) so it does not need to be provided in the payload. + + Note: while this header allows you to create arrays of anything, you + should avoid creating arrays of the following, as they might not decode + correctly: + - TypeNone (or anything based on InTypeNull). + - TypeBinary (or anything based on InTypeBinary). + + It is ok to create an array of nested structures where fields in the + structure have TypeNone or TypeBinary. + */ + enum Type : UINT16; + + /* + Used for composing Type values. + Normally you'll use a precomposed Type... value, but you can compose a + custom Type... value by combining a value from InType with a value from + OutType using MakeType. + + The InType tells the pipeline how to encode the field's payload (primarily + how to determine payload size). In addition, each InType has an implicit + default formatting behavior that will be used if not overridden by an + OutType. + + The comments for each InType value indicate the payload encoding rules and + the OutTypes that are most likely to be usable with this InType. + */ + enum InType : UINT8; + + /* + Used for composing Type values. + Normally you'll use a precomposed Type... value, but you can compose a + custom Type... value by combining a value from InType with a value from + OutType using MakeType. + + The OutType gives the pipeline a formatting hint that the trace consumer + may use to override the InType's default formatting behavior. If no + OutType is specified (i.e. if OutTypeDefault is used) or if the trace + consumer does not recognize the specified InType+OutType combination, the + trace consumer will perform default formatting based solely on InType. + + The comments for each OutType indicate the InTypes for which the OutType + might have valid semantics. However, most trace consumer only recognize a + small subset of the "valid" InType+OutType combinations, so even if the + semantics are valid, the OutType might still be ignored by the trace + consumer. The most commonly-supported combinations are the combinations + with corresponding precomposed Type... values. + */ + enum OutType : UINT8; + + /* + enum ProviderTraitType (low-level API): + + The type of a provider trait. Used when building up provider metadata. + */ + enum ProviderTraitType : UINT8; + + /* + macro TLD_HAVE_EVENT_SET_INFORMATION (low-level API): + + Configuration macro for controlling the behavior of SetInformation. + + Not all versions of Windows support the EventSetInformation API. The + tld::SetInformation wrapper provides default behavior that works well in + most cases, but may not meet the needs of all users. The configuration + macros can be used to control how the tld::SetInformation function invokes + the EventSetInformation API. (Note that tld::SetInformation is called + automatically by the Provider class's constructor to register the + provider's traits with ETW.) + + When TLD_HAVE_EVENT_SET_INFORMATION is not defined and WINVER < 0x0602: + SetInformation uses GetModuleHandleExW+GetProcAddress to find the + EventSetInformation function. If found, SetInformation calls it and + returns whatever EventSetInformation returns. Otherwise, SetInformation + returns an error. + + When TLD_HAVE_EVENT_SET_INFORMATION is not defined and WINVER >= 0x0602: + SetInformation directly calls the EventSetInformation(...) function and + returns whatever EventSetInformation returns. + + If you set TLD_HAVE_EVENT_SET_INFORMATION: + - If TLD_HAVE_EVENT_SET_INFORMATION == 0, SetInformation always returns an + error. + - If TLD_HAVE_EVENT_SET_INFORMATION == 1, SetInformation calls + EventSetInformation. + - If TLD_HAVE_EVENT_SET_INFORMATION == 2, SetInformation locates + EventSetInformation via GetProcAddress. + */ +#ifndef TLD_HAVE_EVENT_SET_INFORMATION + #if WINVER < 0x0602 // If targeting Windows before Windows 8 + #define TLD_HAVE_EVENT_SET_INFORMATION 2 // Find "EventSetInformation" via GetModuleHandleExW/GetProcAddress + #else + #define TLD_HAVE_EVENT_SET_INFORMATION 1 // Directly invoke EventSetInformation(...) + #endif +#endif // TLD_HAVE_EVENT_SET_INFORMATION + + /* + macro TLD_HAVE_EVENT_WRITE_EX (low-level API): + + Configuration macro for enabling/disabling WriteEventEx, WriteEx. + + If TLD_HAVE_EVENT_WRITE_EX is not defined and WINVER < 0x0601, or + if TLD_HAVE_EVENT_WRITE_EX is defined as 0, + then the WriteEventEx, WriteEx, and related APIs will not be available. + + If TLD_HAVE_EVENT_WRITE_EX is not defined and WINVER >= 0x0601, or + if TLD_HAVE_EVENT_WRITE_EX is defined as 1, + then the WriteEventEx, WriteEx, and related APIs will be available. + */ +#ifndef TLD_HAVE_EVENT_WRITE_EX + #if WINVER < 0x0601 // If targeting Windows before Windows 7 + #define TLD_HAVE_EVENT_WRITE_EX 0 + #else + #define TLD_HAVE_EVENT_WRITE_EX 1 + #endif +#endif // TLD_HAVE_EVENT_WRITE_EX + + + /* + class ProviderMetadataBuilder (low-level API): + + Helper for building the provider metadata blob. + The type provided for ByteBufferTy must behave like a std::vector<BYTE>. + + Example usage: + + std::vector<BYTE> byteVector; + tld::ProviderMetadataBuilder<std::vector<BYTE>> builder(byteVector); + builder.Begin(szProviderName); // Note: calls byteVector.clear(). + if (builder.End()) // Returns false if the metadata is too large. + { + // byteVector now contains a valid provider metadata blob. + } + */ + template<class ByteBufferTy> + class ProviderMetadataBuilder; + + /* + class EventMetadataBuilder (low-level API): + + Helper for building the event metadata blob, including field definitions. + The type provided for ByteBufferTy must behave like a std::vector<BYTE>. + + Example usage: + + std::vector<BYTE> byteVector; + tld::EventMetadataBuilder<std::vector<BYTE>> builder(byteVector); + builder.Begin(eventName); // Note: calls byteVector.clear(). + builder.AddField("Field1", TypeBOOL32); // top-level field + auto struct1 = builder.AddStruct("Struct1"); + struct1.AddField("Nested1", TypeINT32) // nested field + struct1.AddField("Nested2", TypeANSISTRING); // nested field + if (builder.End()) // Returns false if the metadata is too large. + { + // byteVector now contains a valid event metadata blob. + } + */ + template<class ByteBufferTy> + class EventMetadataBuilder; + + /* + class EventDataBuilder (low-level API): + + Helpers for adding event data to an event data blob. + The type provided for ByteBufferTy must behave like a std::vector<BYTE>. + */ + template<class ByteBufferTy> + class EventDataBuilder; + + /* + class EventDescriptor (low-level API): + + Derives from EVENT_DESCRIPTOR. Adds convenient constructor and Reset + members. Contains an event's Id/Version/Level/Opcode/Task/Keyword + settings. Use of this class is optional - you can use this class if you + want to use the constructor or Reset methods, or you can use + EVENT_DESCRIPTOR directly if you don't need the convenience methods. + */ + struct EventDescriptor; + + /* + class ByteArrayWrapper (low-level API): + + Adapter that allows a regular BYTE array (stack-allocated or + manually-allocated) to be used as the buffer in MetadataBuilder + operations. If the underlying BYTE array is too small, or if no array is + provided at all, this adapter will stop writing data beyond the end of the + array, but will continue to "accept" data (and will increment it size + member accordingly). This allows you to determine the size of the required + array so that the operation can be retried with a correctly-sized array. + + Example usage: + + // Create a wrapper with a default (0-length) buffer. + tld::ByteArrayWrapper wrapper; + + // Create a metadata builder attached to the wrapper. + tld::ProviderMetadataBuilder<tld::ByteArrayWrapper> builder(wrapper); + + // Determine the size needed for provider metadata. + builder.Begin(providerName); + builder.End(); + + // Allocate a real buffer and reset the wrapper to use it. + BYTE* pBuffer = new BYTE[wrapper.size()]; + wrapper.reset(pBuffer, wrapper.size()); + + // Redo the operation with the real buffer. + builder.Begin(providerName); + if (builder.End()) + { + // pBuffer now contains a valid provider metadata blob. + } + */ + class ByteArrayWrapper; + + /* + function RegisterProvider (low-level API): + + Calls EventRegister. If EventRegister succeeds, calls EventSetInformation + to register your provider traits with ETW. Use this instead of calling + EventRegister directly to ensure that your provider traits are properly + registered. + */ + inline HRESULT RegisterProvider( + _Out_ REGHANDLE* phProvider, + _In_ GUID const* pProviderId, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_opt_ PENABLECALLBACK pEnableCallback = 0, + _In_opt_ void* pEnableCallbackContext = 0); + + /* + function UnregisterProvider (low-level API): + + Calls EventUnregister. + */ + inline HRESULT UnregisterProvider( + REGHANDLE hProvider); + + /* + function WriteEvent (low-level API): + + Calls EventWriteTransfer. You must provide 2 more data descriptors than + are required for your data. The first two elements in pDataDescriptors + will be used for provider and event metadata. The actual payload (if any) + is expected to be in the remaining elements of pDataDescriptors. + + Example usage: + + tld::EventDescriptor eventDescriptor( + level, + opcode, + task, + keywords); + EVENT_DATA_DESCRIPTOR pDataDescriptors[cFields + 2]; + for (int i = 0; i < cFields; i++) + { + EventDataDescCreate(&pDataDescriptors[i + 2], ...); + } + tld::WriteEvent( + hProvider, + eventDescriptor, + pProviderMetadata, + pEventMetadata, + cFields + 2, + pDataDescriptors); + */ + inline HRESULT WriteEvent( + _In_ REGHANDLE hProvider, + _In_ EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + _In_opt_ GUID const* pActivityId = 0, + _In_opt_ GUID const* pRelatedActivityId = 0); + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + /* + function WriteEventEx (low-level API): + Calls EventWriteEx. Otherwise works like the WriteEvent function. + */ + inline HRESULT WriteEventEx( + _In_ REGHANDLE hProvider, + _In_ EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + ULONG64 filter = 0, + ULONG flags = 0, + _In_opt_ GUID const* pActivityId = 0, + _In_opt_ GUID const* pRelatedActivityId = 0); + +#endif // TLD_HAVE_EVENT_WRITE_EX + + /* + function SetInformation (low-level API): + + Wrapper for ETW API EventSetInformation. + If TraceLoggingDynamic.cpp was compiled to require Win8 or later (as + determined by WINVER), this directly calls EventSetInformation. Otherwise, + this attempts to dynamically load the EventSetInformation API via + GetModuleHandleExW. + + The behavior of this function (e.g. to override the WINVER check) can be + adjusted by setting the TLD_HAVE_EVENT_SET_INFORMATION macro as described + below. + */ + inline HRESULT SetInformation( + _In_ REGHANDLE hProvider, + EVENT_INFO_CLASS informationClass, + _In_reads_bytes_opt_(cbInformation) void* pbInformation, + ULONG cbInformation); + + /* + function GetGuidForName (low-level API): + + Hashes a provider name to generate a GUID. Uses the same GUID generation + algorithm as System.Diagnostics.Tracing.EventSource (from .NET) and + Windows.Foundation.Diagnostics.LoggingChannel (from Windows Runtime). + */ + inline GUID GetGuidForName( + _In_z_ char const* szUtf8Name); + + /* + function GetGuidForName (low-level API): + + Hashes a provider name to generate a GUID. Uses the same GUID generation + algorithm as .NET System.Diagnostics.Tracing.EventSource and Windows + Runtime Windows.Foundation.Diagnostics.LoggingChannel. + */ + inline GUID GetGuidForName( + _In_z_ wchar_t const* szUtf16Name); + + /* + function PushBackAsUtf8 (low-level API): + + Transcodes a NUL-terminated UTF-16LE string to a NUL-terminated UTF-8 + string. Intended for use when appending provider/event/field names to + metadata buffers. + */ + template<class ByteBufferTy> + void PushBackAsUtf8( + ByteBufferTy& buffer, + _In_z_ wchar_t const* szUtf16); + +#pragma endregion + + namespace detail + { +#pragma region Internal macros + +#define _tld_MAKE_TYPE(inType, outType) \ + static_cast<::tld::Type>((inType) | (static_cast<int>(outType) << 8)) + +#ifndef TLD_DEBUG + #if (DBG || defined(DEBUG) || defined(_DEBUG)) && !defined(NDEBUG) + #define TLD_DEBUG 1 + #else // DBG + #define TLD_DEBUG 0 + #endif // DBG +#endif // TLD_DEBUG + +#ifndef _tld_ASSERT + #if TLD_DEBUG + #define _tld_ASSERT(exp, str) ((void)(!(exp) ? (__annotation(L"Debug", L"AssertFail", L"TraceLogging: " L#exp L" : " L##str), DbgRaiseAssertionFailure(), 0) : 0)) + #else // TLD_DEBUG + #define _tld_ASSERT(exp, str) ((void)0) + #endif // TLD_DEBUG +#endif // _tld_ASSERT + +#pragma endregion + +#pragma region MetadataBuilderBase + + template<class ByteBufferTy> + class MetadataBuilderBase + { + ByteBufferTy& m_buffer; + + MetadataBuilderBase(MetadataBuilderBase const&); // = delete + void operator=(MetadataBuilderBase const&); // = delete + + protected: + + explicit MetadataBuilderBase(ByteBufferTy& buffer) + : m_buffer(buffer) + { + return; + } + + void BaseBegin() + { + m_buffer.clear(); + AddU16(0); // Metadata size filled in by BaseEnd + } + + bool BaseEnd() + { + auto size = m_buffer.size(); + _tld_ASSERT(2 <= size, "Begin was not called"); + SetU16(0, static_cast<UINT16>(size)); + return size < 32768; + } + + void AddBytes( + _In_reads_bytes_(cb) void const* p, + unsigned cb) + { + auto pb = static_cast<UINT8 const*>(p); + for (unsigned i = 0; i != cb; i++) + { + m_buffer.push_back(pb[i]); + } + } + + void AddString( + _In_z_ char const* szUtf8) + { + for (unsigned i = 0;; i++) + { + m_buffer.push_back(szUtf8[i]); + if (szUtf8[i] == 0) + { + break; + } + } + } + + void AddString( + _In_z_ wchar_t const* szUtf16) + { + PushBackAsUtf8(m_buffer, szUtf16); + } + + void AddU8( + UINT8 val) + { + m_buffer.push_back(val);; + } + + void AddU16( + UINT16 val) + { + m_buffer.push_back(static_cast<UINT8>(val)); + m_buffer.push_back(static_cast<UINT8>(val >> 8)); + } + + UINT32 GetBookmark() + { + return static_cast<UINT32>(m_buffer.size()); + } + + void SetU8( + UINT32 bookmark, + UINT8 val) + { + _tld_ASSERT(bookmark < m_buffer.size(), "bookmark out of range"); + m_buffer[bookmark] = val; + } + + void SetU16( + UINT32 bookmark, + UINT16 val) + { + _tld_ASSERT(1 <= bookmark + 1, "bookmark out of range"); + SetU8(bookmark + 0, static_cast<UINT8>(val)); + SetU8(bookmark + 1, static_cast<UINT8>(val >> 8)); + } + + void IncrementU7( + UINT32 bookmark) + { + _tld_ASSERT(bookmark < m_buffer.size(), "bookmark out of range"); + UINT8 result; + result = ++m_buffer[bookmark]; + _tld_ASSERT((result & 0x7f) != 0, "too many fields in struct"); + } + + public: + + ByteBufferTy& GetBuffer() + { + return m_buffer; + } + + ByteBufferTy const& GetBuffer() const + { + return m_buffer; + } + }; + +#pragma endregion + +#pragma region Sha1ForNonSecretPurposes + + /* + Implements the SHA1 hashing algorithm. Note that this implementation is + for hashing public information. Do not use this code to hash private data, + as this implementation does not take any steps to avoid information + disclosure (i.e. does not scrub its buffers). + */ + class Sha1ForNonSecretPurposes + { + Sha1ForNonSecretPurposes(Sha1ForNonSecretPurposes const&); // = delete + void operator=(Sha1ForNonSecretPurposes const&); // = delete + + UINT64 m_length; // Total message length in bits + unsigned m_pos; // Length of current chunk in bytes + UINT32 m_results[5]; + UINT32 m_w[80]; // Workspace + + public: + + Sha1ForNonSecretPurposes() + : m_length() + , m_pos() + { + m_results[0] = 0x67452301; + m_results[1] = 0xEFCDAB89; + m_results[2] = 0x98BADCFE; + m_results[3] = 0x10325476; + m_results[4] = 0xC3D2E1F0; + } + + void Append( + _In_reads_bytes_(cbInput) void const* pInput, + unsigned cbInput) + { + for (unsigned i = 0; i != cbInput; i++) + { + Append(static_cast<UINT8 const*>(pInput)[i]); + } + } + + void Append(UINT8 input) + { + reinterpret_cast<UINT8*>(m_w)[m_pos++] = input; + if (64 == m_pos) + { + Drain(); + } + } + + _Ret_bytecount_(20) UINT8* Finish() + { + UINT64 totalBitCount = m_length + 8 * m_pos; + Append(0x80); + while (m_pos != 56) + { + Append(0x00); + } + + *reinterpret_cast<UINT64*>(m_w + 14) = _byteswap_uint64(totalBitCount); + Drain(); + + for (unsigned i = 0; i != 5; i++) + { + m_results[i] = _byteswap_ulong(m_results[i]); + } + + return reinterpret_cast<UINT8*>(m_results); + } + + private: + + void Drain() + { + for (unsigned i = 0; i != 16; i++) + { + m_w[i] = _byteswap_ulong(m_w[i]); + } + + for (unsigned i = 16; i != 80; i++) + { + m_w[i] = _rotl((m_w[i - 3] ^ m_w[i - 8] ^ m_w[i - 14] ^ m_w[i - 16]), 1); + } + + UINT32 a = m_results[0]; + UINT32 b = m_results[1]; + UINT32 c = m_results[2]; + UINT32 d = m_results[3]; + UINT32 e = m_results[4]; + + for (unsigned i = 0; i != 20; i++) + { + UINT32 const k = 0x5A827999; + UINT32 f = (b & c) | ((~b) & d); + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + for (unsigned i = 20; i != 40; i++) + { + const UINT32 k = 0x6ED9EBA1; + UINT32 f = b ^ c ^ d; + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + for (unsigned i = 40; i != 60; i++) + { + const UINT32 k = 0x8F1BBCDC; + UINT32 f = (b & c) | (b & d) | (c & d); + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + for (unsigned i = 60; i != 80; i++) + { + const UINT32 k = 0xCA62C1D6; + UINT32 f = b ^ c ^ d; + UINT32 temp = _rotl(a, 5) + f + e + k + m_w[i]; e = d; d = c; c = _rotl(b, 30); b = a; a = temp; + } + + m_results[0] += a; + m_results[1] += b; + m_results[2] += c; + m_results[3] += d; + m_results[4] += e; + + m_length += 512; // 64 bytes == 512 bits + m_pos = 0; + } + }; + +#pragma endregion + +#pragma region CopyUtfCodePoint + + /* + Transcodes one code point from UTF-8 to UTF-16LE. + + Note that this function requires the input buffer to be nul-terminated. + This function may try to read several bytes from the input buffer, and the + nul-termination is required to ensure that it doesn't read off the end of + the valid buffer when decoding what appears to be a multi-byte code point. + + The UTF-8 validation is very permissive -- anything that is not valid + UTF-8 is treated as if it were ISO-8859-1 (i.e. the BYTE value is cast + to wchar_t and assumed to be a valid Unicode code point). This usually + works well - it has a reasonable probability of doing what the developer + expects even when the input is CP1252/Latin1 instead of UTF-8, and doesn't + fail too badly when it doesn't do what the developer expects. + + pchUtf16Output: + Buffer that receives a single code point, encoded in UTF-16-LE as one or + two 16-bit wchar_t values. + + pszUtf8Input: + Pointer into a nul-terminated UTF-8 byte sequence. On entry, this points + to the next char to be consumed. This function will advance the pointer + past the consumed data. + + returns: + The number of 16-bit wchar_t values added to pchUtf16Output (one or two). + */ + static unsigned CopyUtfCodePoint( + _Out_writes_to_(2, return) wchar_t* pchUtf16Output, + char const*& pszUtf8Input) + { + unsigned cchOutput = 1; + unsigned ch = static_cast<unsigned char>(*pszUtf8Input); + pszUtf8Input += 1; + if (ch <= 0xbf) + { + // 0x01..0x7f: ASCII - pass through. + // 0x80..0xbf: Invalid - pass through. + } + else if ((pszUtf8Input[0] & 0xc0) != 0x80) + { + // Invalid trail byte - pass through. + } + else if (ch <= 0xdf) + { + // 0xc0..0xdf: Two-byte encoding for 0x0080..0x07ff. + unsigned ch2 = ((ch & 0x1f) << 6) + | (pszUtf8Input[0] & 0x3f); + if (0x0080 <= ch2) + { + ch = ch2; + pszUtf8Input += 1; + } + } + else if ((pszUtf8Input[1] & 0xc0) != 0x80) + { + // Invalid trail byte - pass through. + } + else if (ch <= 0xef) + { + // 0xe0..0xef: Three-byte encoding for 0x0800..0xffff. + unsigned ch2 = ((ch & 0x0f) << 12) + | ((pszUtf8Input[0] & 0x3f) << 6) + | (pszUtf8Input[1] & 0x3f); + if (0x0800 <= ch2) + { + ch = ch2; + pszUtf8Input += 2; + } + } + else if ((pszUtf8Input[2] & 0xc0) != 0x80) + { + // Invalid trail byte - pass through. + } + else if (ch <= 0xf4) + { + // 0xf0..0xf4: Four-byte encoding for 0x010000..0x10ffff. + unsigned ch2 = ((ch & 0x07) << 18) + | ((pszUtf8Input[0] & 0x3f) << 12) + | ((pszUtf8Input[1] & 0x3f) << 6) + | (pszUtf8Input[2] & 0x3f); + ch2 -= 0x10000; + if (ch2 <= 0xfffff) + { + // Decode into surrogate pair (2 wchar_t units) + pchUtf16Output[1] = static_cast<wchar_t>(0xdc00 | (ch2 & 0x3ff)); + ch = 0xd800 | (ch2 >> 10); + pszUtf8Input += 3; + cchOutput = 2; + } + } + + pchUtf16Output[0] = static_cast<wchar_t>(ch); + return cchOutput; + } + + /* + Copies one code point of UTF-16LE data. + + Note that this function requires the input buffer to be nul-terminated. + This function may try to read several words from the input buffer, and the + nul-termination is required to ensure that it doesn't read off the end of + the valid buffer when copying what appears to be a surrogate pair. + + pchUtf16Output: + Buffer that receives a single code point, encoded in UTF-16-LE as one or + two 16-bit wchar_t values. + + pszUtf16Input: + Pointer into a nul-terminated UTF-16-LE wchar_t sequence. On entry, this + points to the next wchar_t to be consumed. This function will advance the + pointer past the consumed data. + + returns: + The number of 16-bit wchar_t values added to pchUtf16Output (one or two). + */ + static unsigned CopyUtfCodePoint( + _Out_writes_to_(2, return) wchar_t* pchUtf16Output, + wchar_t const*& pszUtf16Input) + { + unsigned cchOutput = 1; + wchar_t ch = *pszUtf16Input; + pszUtf16Input += 1; + if (0xd800 <= ch && ch < 0xdc00 && + 0xdc00 <= *pszUtf16Input && *pszUtf16Input < 0xe000) + { + pchUtf16Output[1] = *pszUtf16Input; + pszUtf16Input += 1; + cchOutput = 2; + } + pchUtf16Output[0] = ch; + return cchOutput; + } + +#pragma endregion + +#pragma region GetGuidForNameImpl + + __declspec(selectany) extern UINT32 const NamespaceValue[] = { + 0xB22D2C48, 0xC84790C3, 0x151AF887, 0xFB30C1Bf + }; + + template<class CharTy> + GUID GetGuidForNameImpl( + _In_z_ CharTy const* szUtfName) + { + /* + Algorithm: + szNameUpperBE = ByteSwap(ToUpper(szName)); + hash = SHA1(namespaceBytes + szNameUpperBE); // Does not include zero-termination in hash. + hash[7] = (hash[7] & 0x0F) | 0x50; + guid = First16Bytes(hash); + */ + + WCHAR buffer[16]; + unsigned const cchBuffer = ARRAYSIZE(buffer); + Sha1ForNonSecretPurposes sha1; + + sha1.Append(NamespaceValue, sizeof(NamespaceValue)); + if (szUtfName[0] != 0) + { + CharTy const* pszName = szUtfName; + unsigned iBuffer = 0; + for (;;) + { + // CopyUtfCodePoint may add up to 2 wchar_t units to buffer. + iBuffer += CopyUtfCodePoint(buffer + iBuffer, pszName); + _tld_ASSERT(iBuffer <= cchBuffer, "buffer overflow"); + + bool const bDone = *pszName == 0; + if (bDone || cchBuffer - 1 <= iBuffer) + { + #pragma warning(suppress: 26035) // string is counted, not nul-terminated. + LCMapStringEx( + LOCALE_NAME_INVARIANT, + LCMAP_UPPERCASE | LCMAP_BYTEREV, + buffer, + iBuffer, + buffer, + iBuffer, + NULL, + NULL, + 0); + sha1.Append(buffer, iBuffer * 2); + + if (bDone) + { + break; + } + + iBuffer = 0; + } + } + } + + UINT8* pSha1 = sha1.Finish(); + + // Set high 4 bits of octet 7 to 5, as per RFC 4122 + pSha1[7] = (pSha1[7] & 0x0f) | 0x50; + + return *reinterpret_cast<GUID*>(pSha1); + } + +#pragma endregion + } + // namespace detail + +#pragma region Enumerations + + enum InType : UINT8 + { + /* + A field with no value (0-length payload). + Arrays of InTypeNull are illegal. + */ + InTypeNull, + + /* + A nul-terminated CHAR16 string. + Useful OutTypes: Xml, Json. + */ + InTypeUnicodeString, + + /* + A nul-terminated CHAR8 string. + Useful OutTypes: Xml, Json, Utf8. + */ + InTypeAnsiString, + + /* + INT8. + */ + InTypeInt8, + + /* + UINT8. + Useful OutTypes: String, Boolean, Hex. + */ + InTypeUInt8, + + /* + INT16. + */ + InTypeInt16, + + /* + UINT16. + Useful OutTypes: String, Hex, Port. + */ + InTypeUInt16, + + /* + INT32. + Useful OutTypes: HResult. + */ + InTypeInt32, + + /* + UINT32. + Useful OutTypes: Pid, Tid, IPv4, Win32Error, NTStatus. + */ + InTypeUInt32, + + /* + INT64. + */ + InTypeInt64, + + /* + UINT64. + */ + InTypeUInt64, + + /* + FLOAT. + */ + InTypeFloat, + + /* + DOUBLE. + */ + InTypeDouble, + + /* + BOOL. + */ + InTypeBool32, + + /* + UINT16 byte-count followed by binary data. + Useful OutTypes: IPv6, SocketAddress. + Arrays of InTypeBinary are not supported. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + InTypeBinary, + + /* + GUID. + */ + InTypeGuid, + + /* + Not supported. Use InTypePointer. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + InTypePointer_PlatformSpecific, + + /* + FILETIME. + */ + InTypeFileTime, + + /* + SYSTEMTIME. + */ + InTypeSystemTime, + + /* + SID. Size is determined from the number of subauthorities. + */ + InTypeSid, + + /* + INT32. + */ + InTypeHexInt32, + + /* + INT64. + */ + InTypeHexInt64, + + /* + UINT16 byte-count followed by UTF-16 data. + */ + InTypeCountedString, + + /* + UINT16 byte-count followed by MBCS data. + */ + InTypeCountedAnsiString, + + /* + This field has no value by itself, but it causes the decoder to treat + the next N fields as part of this field. The value of N is taken from + the field's OutType. + + Note that it is valid to have an array of structs. The array length + will apply to the entire group of fields contained in the array. + + The Struct type is special. Do not use it directly. Use helper APIs to + create nested structures. + */ + InTypeStruct, + + /* + INT_PTR. + InTypeIntPtr is an alias for either InTypeInt32 or InTypeInt64. + */ + InTypeIntPtr = sizeof(void*) == 8 ? InTypeInt64 : InTypeInt32, + + /* + UINT_PTR. + InTypeUIntPtr is an alias for either InTypeUInt32 or InTypeUInt64. + */ + InTypeUIntPtr = sizeof(void*) == 8 ? InTypeUInt64 : InTypeUInt32, + + /* + LPVOID. + InTypePointer is an alias for either InTypeHexInt32 or InTypeHexInt64. + */ + InTypePointer = sizeof(void*) == 8 ? InTypeHexInt64 : InTypeHexInt32, + + /* + InType must fit in 5 bits. + */ + InTypeMask = 31 + }; + + enum OutType : UINT8 + { + OutTypeDefault = 0x00, + OutTypeNoPrint = 0x01, + OutTypeString = 0x02, // affects 8-bit and 16-bit integral types. + OutTypeBoolean = 0x03, // affects 8-bit and 32-bit integral types. + OutTypeHex = 0x04, // affects integral types. + OutTypePid = 0x05, // affects 32-bit integral types. + OutTypeTid = 0x06, // affects 32-bit integral types. + OutTypePort = 0x07, // affects 16-bit integral types. + OutTypeIPv4 = 0x08, // affects 32-bit integral types. + OutTypeIPv6 = 0x09, // affects arrays of 8-bit integral types, e.g. UInt8[]. + OutTypeSocketAddress = 0x0a, // affects arrays of 8-bit integral types, e.g. UInt8[]. + OutTypeXml = 0x0b, // affects strings; affects arrays of 8-bit and 16-bit integral types. + OutTypeJson = 0x0c, // affects strings; affects arrays of 8-bit and 16-bit integral types. + OutTypeWin32Error = 0x0d,// affects 32-bit integral types. + OutTypeNTStatus = 0x0e, // affects 32-bit integral types. + OutTypeHResult = 0x0f, // affects 32-bit integral types. + OutTypeFileTime = 0x10, // affects 64-bit integral types. + OutTypeSigned = 0x11, // affects integral types. + OutTypeUnsigned = 0x12, // affects integral types. + OutTypeDateTimeCultureInsensitive = 0x21, // affects FileTime, SystemTime. + OutTypeUtf8 = 0x23, // affects AnsiString types. + OutTypePkcs7WithTypeInfo = 0x24, // affects binary types. + OutTypeCodePointer = 0x25, // affects UInt32, UInt64, HexInt32, HexInt64. + OutTypeDateTimeUtc = 0x26, // affects FileTime, SystemTime. + OutTypeMask = 0x7f // OutType must fit into 7 bits. + }; + + enum Type : UINT16 + { + /* + Field with no data (empty). 0-length payload. + Can only be used on scalar fields. Illegal for arrays. + NOTE: Not well-supported by decoders. + */ + TypeNone = InTypeNull, + + /* + Encoding assumes 0-terminated CHAR16 string. + Formatting treats as UTF-16LE string. + */ + TypeUtf16String = InTypeUnicodeString, + + /* + Encoding assumes 0-terminated CHAR8 string. + Formatting treats as MBCS string. + */ + TypeMbcsString = InTypeAnsiString, + + /* + Encoding assumes nul-terminated CHAR8 string. + Formatting treats as UTF-8 string. + */ + TypeUtf8String = _tld_MAKE_TYPE(InTypeAnsiString, OutTypeUtf8), + + /* + Encoding assumes 1-byte value. + Formatting treats as signed integer. + */ + TypeInt8 = InTypeInt8, + + /* + Encoding assumes 1-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt8 = InTypeUInt8, + + /* + Encoding assumes 2-byte value. + Formatting treats as signed integer. + */ + TypeInt16 = InTypeInt16, + + /* + Encoding assumes 2-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt16 = InTypeUInt16, + + /* + Encoding assumes 4-byte value. + Formatting treats as signed integer. + */ + TypeInt32 = InTypeInt32, + + /* + Encoding assumes 4-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt32 = InTypeUInt32, + + /* + Encoding assumes 8-byte value. + Formatting treats as signed integer. + */ + TypeInt64 = InTypeInt64, + + /* + Encoding assumes 8-byte value. + Formatting treats as unsigned integer. + */ + TypeUInt64 = InTypeUInt64, + + /* + Encoding assumes 4-byte value. + Formatting treats as Float32. + */ + TypeFloat = InTypeFloat, + + /* + Encoding assumes 8-byte value. + Formatting treats as Float64. + */ + TypeDouble = InTypeDouble, + + /* + Encoding assumes 4-byte value. + Formatting treats as Boolean. + */ + TypeBool32 = InTypeBool32, + + /* + Encoding assumes 2-byte length followed by binary data. + Formatting treats as binary blob. + Arrays of TypeBinary are not supported. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + TypeBinary = InTypeBinary, + + /* + Encoding assumes 16-byte value. + Formatting treats as GUID. + */ + TypeGuid = InTypeGuid, + + /* + Encoding assumes 8-byte value. + Formatting treats as FILETIME. + */ + TypeFileTime = InTypeFileTime, + + /* + Encoding assumes 16-byte value. + Formatting treats as SYSTEMTIME. + */ + TypeSystemTime = InTypeSystemTime, + + /* + Encoding assumes 16-byte value. + Formatting treats as UTC SYSTEMTIME. + */ + TypeSystemTimeUtc = _tld_MAKE_TYPE(InTypeSystemTime, OutTypeDateTimeUtc), + + /* + Encoding assumes SID. Length is determined from number of subauthorities. + Formatting treats as a SID. + */ + TypeSid = InTypeSid, + + /* + Encoding assumes 4-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt32 = InTypeHexInt32, + + /* + Encoding assumes 8-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt64 = InTypeHexInt64, + + /* + Encoding assumes 2-byte bytecount followed by CHAR16 data. + Formatting treats as UTF-16LE string. + */ + TypeCountedUtf16String = InTypeCountedString, + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as MBCS string. + */ + TypeCountedMbcsString = InTypeCountedAnsiString, + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as UTF-8 string. + */ + TypeCountedUtf8String = _tld_MAKE_TYPE(InTypeCountedAnsiString, OutTypeUtf8), + + /* + Encoding assumes pointer-sized value. + Formatting treats as signed integer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is an alias for TypeInt32 when compiled for a 32-bit target. + This is an alias for TypeInt64 when compiled for a 64-bit target. + */ + TypeIntPtr = InTypeIntPtr, + + /* + Encoding assumes pointer-sized value. + Formatting treats as unsigned integer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is an alias for TypeUInt32 when compiled for a 32-bit target. + This is an alias for TypeUInt64 when compiled for a 64-bit target. + */ + TypeUIntPtr = InTypeUIntPtr, + + /* + Encoding assumes pointer-sized value. + Formatting treats as hexadecimal unsigned integer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is an alias for TypeHexInt32 when compiled for a 32-bit target. + This is an alias for TypeHexInt64 when compiled for a 64-bit target. + */ + TypePointer = InTypePointer, + + /* + Encoding assumes pointer-sized value. + Formatting treats as code pointer. + Note: the value of this enum is different on 32-bit and 64-bit systems. + This is a subtype of TypeHexInt32 when compiled for a 32-bit target. + This is a subtype of TypeHexInt64 when compiled for a 64-bit target. + */ + TypeCodePointer = _tld_MAKE_TYPE(InTypePointer, OutTypeCodePointer), + + /* + Encoding assumes 2-byte value. + Formatting treats as UTF-16LE character. + */ + TypeChar16 = _tld_MAKE_TYPE(InTypeUInt16, OutTypeString), + + /* + Encoding assumes 1-byte value. + Formatting treats as ANSI character. + */ + TypeChar8 = _tld_MAKE_TYPE(InTypeUInt8, OutTypeString), + + /* + Encoding assumes 1-byte value. + Formatting treats as Boolean. + */ + TypeBool8 = _tld_MAKE_TYPE(InTypeUInt8, OutTypeBoolean), + + /* + Encoding assumes 1-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt8 = _tld_MAKE_TYPE(InTypeUInt8, OutTypeHex), + + /* + Encoding assumes 2-byte value. + Formatting treats as hexadecimal unsigned integer. + */ + TypeHexInt16 = _tld_MAKE_TYPE(InTypeUInt16, OutTypeHex), + + /* + Encoding assumes 4-byte value. + Formatting treats as process identifier. + */ + TypePid = _tld_MAKE_TYPE(InTypeUInt32, OutTypePid), + + /* + Encoding assumes 4-byte value. + Formatting treats as thread identifier. + */ + TypeTid = _tld_MAKE_TYPE(InTypeUInt32, OutTypeTid), + + /* + Encoding assumes 2-byte value. + Formatting treats as big-endian IP port(s). + */ + TypePort = _tld_MAKE_TYPE(InTypeUInt16, OutTypePort), + + /* + Encoding assumes 4-byte value. + Formatting treats as IPv4 address. + */ + TypeIPv4 = _tld_MAKE_TYPE(InTypeUInt32, OutTypeIPv4), + + /* + Encoding assumes 2-byte length followed by binary data. + Formatting treats as IPv6 address. + Arrays of TypeIPv6 are not supported. (No error will be reported, + but decoding tools will probably not be able to decode them). + */ + TypeIPv6 = _tld_MAKE_TYPE(InTypeBinary, OutTypeIPv6), + + /* + Encoding assumes 2-byte length followed by binary data. + Formatting treats as SOCKADDR. + Arrays of TypeSocketAddress are not supported. (No error will be + reported, but decoding tools will probably not be able to decode them). + */ + TypeSocketAddress = _tld_MAKE_TYPE(InTypeBinary, OutTypeSocketAddress), + + /* + Encoding assumes nul-terminated CHAR16 string. + Formatting treats as UTF-16LE XML. + */ + TypeUtf16Xml = _tld_MAKE_TYPE(InTypeUnicodeString, OutTypeXml), + + /* + Encoding assumes nul-terminated CHAR8 string. + Formatting treats as UTF-8 XML. + */ + TypeMbcsXml = _tld_MAKE_TYPE(InTypeAnsiString, OutTypeXml), + + /* + Encoding assumes nul-terminated CHAR16 string. + Formatting treats as UTF-16LE JSON. + */ + TypeUtf16Json = _tld_MAKE_TYPE(InTypeUnicodeString, OutTypeJson), + + /* + Encoding assumes nul-terminated CHAR8 string. + Formatting treats as UTF-8 JSON. + */ + TypeMbcsJson = _tld_MAKE_TYPE(InTypeAnsiString, OutTypeJson), + + /* + Encoding assumes 2-byte bytecount followed by CHAR16 data. + Formatting treats as UTF-16LE XML. + */ + TypeCountedUtf16Xml = _tld_MAKE_TYPE(InTypeCountedString, OutTypeXml), + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as UTF-8 XML. + */ + TypeCountedMbcsXml = _tld_MAKE_TYPE(InTypeCountedAnsiString, OutTypeXml), + + /* + Encoding assumes 2-byte bytecount followed by CHAR16 data. + Formatting treats as UTF-16LE JSON. + */ + TypeCountedUtf16Json = _tld_MAKE_TYPE(InTypeCountedString, OutTypeJson), + + /* + Encoding assumes 2-byte bytecount followed by CHAR8 data. + Formatting treats as UTF-8 JSON. + */ + TypeCountedMbcsJson = _tld_MAKE_TYPE(InTypeCountedAnsiString, OutTypeJson), + + /* + Encoding assumes 4-byte value. + Formatting treats as Win32 error(s). + */ + TypeWin32Error = _tld_MAKE_TYPE(InTypeUInt32, OutTypeWin32Error), + + /* + Encoding assumes 4-byte value. + Formatting treats as NTSTATUS(s). + */ + TypeNTStatus = _tld_MAKE_TYPE(InTypeUInt32, OutTypeNTStatus), + + /* + Encoding assumes 4-byte value. + Formatting treats as HRESULT(s). + */ + TypeHResult = _tld_MAKE_TYPE(InTypeInt32, OutTypeHResult) + }; + + enum ProviderTraitType : UINT8 + { + ProviderTraitNone = 0, // Invalid value + ProviderTraitGroupGuid = 1 // Data is the group GUID. + }; + +#pragma endregion + +#pragma region EventDescriptor + + /* + Simple wrapper around the ETW EVENT_DESCRIPTOR structure. + Provides a constructor to make initialization easier. + Can be used anywhere an EVENT_DESCRIPTOR is required. + + Notes: + - Channel defaults to 11 (WINEVENT_CHANNEL_TRACELOGGING). Other channels + will only work if the provider is running on a TraceLogging-enabled OS + (Windows 10 or later, or Windows 7 sp1 with updates, or Windows 8.1 with + updates). If the provider is running on an earlier version of Windows and + you use a channel other than 11, the events will not decode correctly. + - If you are not assigning unique Ids for your events, set both Id and + Version to 0. + */ + struct EventDescriptor + : EVENT_DESCRIPTOR + { + explicit EventDescriptor( + UCHAR level = 5, // 5 = WINEVENT_LEVEL_VERBOSE + ULONGLONG keyword = 0, // 0 = no keywords + UCHAR opcode = 0, // 0 = WINEVENT_OPCODE_INFO + USHORT task = 0) // 0 = WINEVENT_TASK_NONE + { + Reset(level, keyword, opcode, task); + } + + void Reset( + UCHAR level = 5, // 5 = WINEVENT_LEVEL_VERBOSE + ULONGLONG keyword = 0, // 0 = no keywords + UCHAR opcode = 0, // 0 = WINEVENT_OPCODE_INFO + USHORT task = 0) // 0 = WINEVENT_TASK_NONE + { + Id = 0; + Version = 0; + Channel = 11; // WINEVENT_CHANNEL_TRACELOGGING + Level = level; + Opcode = opcode; + Task = task; + Keyword = keyword; + } + }; + +#pragma endregion + +#pragma region Support functions + +#pragma warning(push) +#pragma warning(disable: 26018) // PREfast: Potential read overflow. (Analysis is incorrect.) + + template<class ByteBufferTy> + void PushBackAsUtf8( + ByteBufferTy& buffer, + _In_z_ wchar_t const* szUtf16) + { + for (unsigned i = 0;; i++) + { + unsigned ch = szUtf16[i]; + if (ch < 0x80) + { + buffer.push_back(static_cast<UINT8>(ch)); + if (ch == 0) + { + break; + } + } + else if (ch < 0x800) + { + buffer.push_back(static_cast<UINT8>(((ch >> 6)) | 0xc0)); + buffer.push_back(static_cast<UINT8>(((ch)& 0x3f) | 0x80)); + } + else if ( + 0xd800 <= ch && ch < 0xdc00 && + 0xdc00 <= szUtf16[i + 1] && szUtf16[i + 1] < 0xe000) + { + // Decode surrogate pair. + ++i; + ch = 0x010000 + (((ch - 0xd800) << 10) | (szUtf16[i] - 0xdc00)); + buffer.push_back(static_cast<UINT8>(((ch >> 18)) | 0xf0)); + buffer.push_back(static_cast<UINT8>(((ch >> 12) & 0x3f) | 0x80)); + buffer.push_back(static_cast<UINT8>(((ch >> 6) & 0x3f) | 0x80)); + buffer.push_back(static_cast<UINT8>(((ch)& 0x3f) | 0x80)); + } + else + { + buffer.push_back(static_cast<UINT8>(((ch >> 12)) | 0xe0)); + buffer.push_back(static_cast<UINT8>(((ch >> 6) & 0x3f) | 0x80)); + buffer.push_back(static_cast<UINT8>(((ch)& 0x3f) | 0x80)); + } + } + } + +#pragma warning(pop) + + inline ::tld::Type MakeType( + ::tld::InType inType, + ::tld::OutType outType) + { + return _tld_MAKE_TYPE(inType & InTypeMask, outType & OutTypeMask); + } + + _Use_decl_annotations_ + inline GUID GetGuidForName( + wchar_t const* szUtf16Name) + { + return ::tld::detail::GetGuidForNameImpl(szUtf16Name); + } + + _Use_decl_annotations_ + inline GUID GetGuidForName( + char const* szUtf8Name) + { + return ::tld::detail::GetGuidForNameImpl(szUtf8Name); + } + + inline HRESULT RegisterProvider( + _Out_ REGHANDLE* phProvider, + _In_ GUID const* pProviderId, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata, + _In_opt_ PENABLECALLBACK pEnableCallback, + _In_opt_ void* pEnableCallbackContext) + { + HRESULT hr; + REGHANDLE hProvider = 0; + ULONG status = ::EventRegister( + pProviderId, + pEnableCallback, + pEnableCallbackContext, + &hProvider); + if (status == 0) + { + auto cbMetadata = *reinterpret_cast<UINT16 const*>(pProviderMetadata); + status = tld::SetInformation( + hProvider, + static_cast<EVENT_INFO_CLASS>(2), // EventProviderSetTraits + const_cast<UINT8*>(pProviderMetadata), + cbMetadata); + hr = S_OK; // Ignore any failures from SetInformation. + } + else + { + hr = HRESULT_FROM_WIN32(status); + } + + *phProvider = hProvider; + return hr; + } + + inline HRESULT UnregisterProvider( + REGHANDLE hProvider) + { + ULONG status = ::EventUnregister(hProvider); + return HRESULT_FROM_WIN32(status); + } + + _Use_decl_annotations_ + inline HRESULT SetInformation( + REGHANDLE hProvider, + EVENT_INFO_CLASS informationClass, + void* pvInformation, + ULONG cbInformation) + { + ULONG status; + +#if TLD_HAVE_EVENT_SET_INFORMATION == 1 + +#pragma warning(suppress: 6387) // It's ok for pvInformation to be null if cbInformation is 0. + status = ::EventSetInformation( + hProvider, + informationClass, + pvInformation, + cbInformation); + +#elif TLD_HAVE_EVENT_SET_INFORMATION == 2 + + HMODULE hEventing; + status = ERROR_NOT_SUPPORTED; + if (GetModuleHandleExW(0, L"api-ms-win-eventing-provider-l1-1-0", &hEventing) || + GetModuleHandleExW(0, L"advapi32.dll", &hEventing)) + { + typedef ULONG(WINAPI* PFEventSetInformation)( + _In_ REGHANDLE RegHandle, + _In_ EVENT_INFO_CLASS InformationClass, + _In_reads_bytes_opt_(InformationLength) PVOID EventInformation, + _In_ ULONG InformationLength); + PFEventSetInformation pfEventSetInformation = + (PFEventSetInformation)GetProcAddress(hEventing, "EventSetInformation"); + if (pfEventSetInformation) + { + status = pfEventSetInformation( + hProvider, + informationClass, + pvInformation, + cbInformation); + } + + FreeLibrary(hEventing); + } + +#else // TLD_HAVE_EVENT_SET_INFORMATION == 0 + + (void)hProvider; + (void)informationClass; + (void)pvInformation; + (void)cbInformation; + + status = ERROR_NOT_SUPPORTED; + +#endif // TLD_HAVE_EVENT_SET_INFORMATION + + return HRESULT_FROM_WIN32(status); + } + + _Use_decl_annotations_ + inline HRESULT WriteEvent( + REGHANDLE hProvider, + EVENT_DESCRIPTOR const& eventDescriptor, + UINT8 const* pProviderMetadata, + UINT8 const* pEventMetadata, + ULONG cDataDescriptors, + EVENT_DATA_DESCRIPTOR* pDataDescriptors, + LPCGUID pActivityId, + LPCGUID pRelatedActivityId) + { + _tld_ASSERT(3 <= *(UINT16*)pProviderMetadata, "Invalid provider metadata."); + _tld_ASSERT(4 <= *(UINT16*)pEventMetadata, "Invalid event metadata."); + pDataDescriptors[0].Ptr = (ULONG_PTR)pProviderMetadata; + pDataDescriptors[0].Size = *(UINT16*)pProviderMetadata; + pDataDescriptors[0].Reserved = 2; // Descriptor contains provider metadata. + pDataDescriptors[1].Ptr = (ULONG_PTR)pEventMetadata; + pDataDescriptors[1].Size = *(UINT16*)pEventMetadata; + pDataDescriptors[1].Reserved = 1; // Descriptor contains event metadata. + ULONG status = ::EventWriteTransfer( + hProvider, + &eventDescriptor, + pActivityId, + pRelatedActivityId, + cDataDescriptors, + pDataDescriptors); + return HRESULT_FROM_WIN32(status); + } + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + _Use_decl_annotations_ + inline HRESULT WriteEventEx( + REGHANDLE hProvider, + EVENT_DESCRIPTOR const& eventDescriptor, + UINT8 const* pProviderMetadata, + UINT8 const* pEventMetadata, + ULONG cDataDescriptors, + EVENT_DATA_DESCRIPTOR* pDataDescriptors, + ULONG64 filter, + ULONG flags, + GUID const* pActivityId, + GUID const* pRelatedActivityId) + { + _tld_ASSERT(3 <= *(UINT16*)pProviderMetadata, "Invalid provider metadata."); + _tld_ASSERT(4 <= *(UINT16*)pEventMetadata, "Invalid event metadata."); + pDataDescriptors[0].Ptr = (ULONG_PTR)pProviderMetadata; + pDataDescriptors[0].Size = *(UINT16*)pProviderMetadata; + pDataDescriptors[0].Reserved = 2; // Descriptor contains provider metadata. + pDataDescriptors[1].Ptr = (ULONG_PTR)pEventMetadata; + pDataDescriptors[1].Size = *(UINT16*)pEventMetadata; + pDataDescriptors[1].Reserved = 1; // Descriptor contains event metadata. + ULONG status = ::EventWriteEx( + hProvider, + &eventDescriptor, + filter, + flags, + pActivityId, + pRelatedActivityId, + cDataDescriptors, + pDataDescriptors); + return HRESULT_FROM_WIN32(status); + } + +#endif // TLD_HAVE_EVENT_WRITE_EX + +#pragma endregion + +#pragma region ByteArrayWrapper + + class ByteArrayWrapper + { + UINT8* m_pb; + UINT32 m_cbMax; + UINT32 m_cbCur; + + public: + + ByteArrayWrapper() + { + reset(); + } + + ByteArrayWrapper( + _Out_writes_(cbMax) UINT8* pb, + UINT32 cbMax) + { + reset(pb, cbMax); + } + + void reset() + { + m_pb = 0; + m_cbMax = 0; + m_cbCur = 0; + } + + void reset( + _Inout_updates_bytes_(cbMax) UINT8* pb, + UINT32 cbMax) + { + m_pb = pb; + m_cbMax = cbMax; + m_cbCur = 0; + } + + void clear() + { + m_cbCur = 0; + } + + UINT32 size() const + { + return m_cbCur; + } + + BYTE const& front() const + { + return *m_pb; + } + + BYTE const* data() const + { + return m_pb; + } + + UINT32 capacity() const + { + return m_cbMax; + } + + void push_back(UINT8 val) + { + if (m_cbCur < m_cbMax) + { + m_pb[m_cbCur] = val; + } + + m_cbCur += 1; + } + + UINT8& operator[](UINT32 index) + { + static UINT8 dummy; + return index < m_cbMax + ? m_pb[index] + : dummy; + } + }; + +#pragma endregion + +#pragma region ProviderMetadataBuilder + + template<class ByteBufferTy> + class ProviderMetadataBuilder + : public detail::MetadataBuilderBase<ByteBufferTy> + { + /* + ProviderMetadata = + Size : UINT16; // Size counts the whole data structure, including the Size field. + Name : Nul-terminated UTF-8. + Traits[]: ProviderTrait (parse until you have consumed Size bytes) + + ProviderTrait = + TraitSize: UINT16; + TraitType: UINT8; + TraitData: UINT8[TraitSize - 3]; + */ + + public: + + explicit ProviderMetadataBuilder(ByteBufferTy& buffer) + : detail::MetadataBuilderBase<ByteBufferTy>(buffer) + { + return; + } + + template<class CharTy> + void Begin( + _In_z_ CharTy const* szUtfProviderName) + { + this->BaseBegin(); + this->AddString(szUtfProviderName); + } + + void AddTrait( + ProviderTraitType type, + _In_reads_bytes_(cbData) void const* pData, + unsigned cbData) + { + this->AddU16(static_cast<UINT16>(cbData + 3)); + this->AddU8(static_cast<UINT8>(type)); + this->AddBytes(pData, cbData); + } + + bool End() + { + return this->BaseEnd(); + } + }; + +#pragma endregion + +#pragma region EventMetadataBuilder + + template<class ByteBufferTy> + class EventMetadataBuilder + : public detail::MetadataBuilderBase<ByteBufferTy> + { + /* + EventMetadata = + Size : UINT16; // Size counts the whole data structure, including the Size field. + Tags[] : UINT8 (read bytes until you encounter a byte with high bit unset); + Name : Nul-terminated UTF-8; + Fields[]: FieldMetadata (parse until you get to end of EventMetadata); + + FieldMetadata = + Name : Nul-terminated UTF-8. + InMeta : UINT8. + OutMeta : UINT8 (only present if InMeta & InMetaChain); + Tags[] : UINT8 (only present if OutMeta & OutMetaChain, read bytes until you encounter a byte with high bit unset); + Ccount : UINT16 (only present if InMeta & InMetaCountMask == InMetaCcount); + CbSchema: UINT16 (only present if InMeta & InMetaCountMask == InMetaCustom); + Schema : UINT8[CbSchema] + */ + + static const UINT8 InMetaScalar = 0; + static const UINT8 InMetaCcount = 32; + static const UINT8 InMetaVcount = 64; + static const UINT8 InMetaCustom = 96; + static const UINT8 InMetaCountMask = 96; + static const UINT8 InMetaChain = 128; + static const UINT8 OutMetaChain = 128; + + UINT32 const m_bookmark; + + EventMetadataBuilder( + ByteBufferTy& buffer, + UINT32 bookmark) + : detail::MetadataBuilderBase<ByteBufferTy>(buffer) + , m_bookmark(bookmark) + { + return; + } + + void AddTags(UINT32 tags) + { + _tld_ASSERT(0 == (tags & 0xf0000000), "Tags only supports 28-bit values."); + for (;;) + { + auto val = static_cast<UINT8>(tags >> 21); + if ((tags & 0x1fffff) == 0) + { + this->AddU8(val & 0x7f); + break; + } + + this->AddU8(val | 0x80); + tags = tags << 7; + } + } + + void AddFieldInfo(UINT8 arity, Type type, UINT32 tags) + { + _tld_ASSERT((type & InTypeMask) == (type & 0xff), "InType out of range"); + _tld_ASSERT((type & _tld_MAKE_TYPE(0, OutTypeMask)) == (Type)(type & 0xffffff00), "OutType out of range"); + + UINT8 inMeta = arity | static_cast<UINT8>(type); + UINT8 outMeta = static_cast<UINT8>(type >> 8); + if (tags != 0) + { + this->AddU8(static_cast<UINT8>(inMeta | InMetaChain)); + this->AddU8(static_cast<UINT8>(outMeta | OutMetaChain)); + AddTags(tags); + } + else if (outMeta != 0) + { + this->AddU8(static_cast<UINT8>(inMeta | InMetaChain)); + this->AddU8(static_cast<UINT8>(outMeta)); + } + else + { + this->AddU8(inMeta); + } + + if (m_bookmark != 0) + { + this->IncrementU7(m_bookmark); + } + } + + UINT32 AddStructInfo(UINT8 arity, UINT32 tags) + { + UINT8 inMeta = arity | InTypeStruct; + this->AddU8(static_cast<UINT8>(inMeta | InMetaChain)); + UINT32 bookmark = this->GetBookmark(); + if (tags == 0) + { + this->AddU8(0); + } + else + { + this->AddU8(OutMetaChain); + AddTags(tags); + } + + if (m_bookmark != 0) + { + this->IncrementU7(m_bookmark); + } + + return bookmark; + } + + public: + + explicit EventMetadataBuilder( + ByteBufferTy& buffer) + : detail::MetadataBuilderBase<ByteBufferTy>(buffer) + , m_bookmark(0) + { + return; + } + + EventMetadataBuilder(EventMetadataBuilder& other) + : detail::MetadataBuilderBase<ByteBufferTy>(other.GetBuffer()) + , m_bookmark(other.m_bookmark) + { + return; + } + + EventMetadataBuilder(EventMetadataBuilder&& other) + : detail::MetadataBuilderBase<ByteBufferTy>(other.GetBuffer()) + , m_bookmark(other.m_bookmark) + { + return; + } + + template<class CharTy> + void Begin( + _In_z_ CharTy const* szUtfEventName, + UINT32 eventTags = 0) + { + _tld_ASSERT(m_bookmark == 0, "Must not call Begin on a struct builder"); + this->BaseBegin(); + AddTags(eventTags); + this->AddString(szUtfEventName); + } + + bool End() + { + _tld_ASSERT(m_bookmark == 0, "Must not call End on a struct builder"); + return this->BaseEnd(); + } + + // Note: Do not create structs with 0 fields. + template<class CharTy> + EventMetadataBuilder AddStruct( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaScalar, fieldTags); + return EventMetadataBuilder(this->GetBuffer(), bookmark); + } + + // Note: Do not create structs with 0 fields. + template<class CharTy> + UINT32 AddStructRaw( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaScalar, fieldTags); + return bookmark; + } + + // Note: Do not create structs with 0 fields. + template<class CharTy> + EventMetadataBuilder AddStructArray( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaVcount, fieldTags); + return EventMetadataBuilder(this->GetBuffer(), bookmark); + } + + // Note: Do not use 0 for itemCount. + // Note: Do not create structs with 0 fields. + template<class CharTy> + EventMetadataBuilder AddStructFixedArray( + _In_z_ CharTy const* szUtfStructName, + UINT16 itemCount, + UINT32 fieldTags = 0) + { + this->AddString(szUtfStructName); + auto bookmark = AddStructInfo(InMetaCcount, fieldTags); + this->AddU16(itemCount); + return EventMetadataBuilder(this->GetBuffer(), bookmark); + } + + template<class CharTy> + void AddField( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaScalar, type, fieldTags); + } + + template<class CharTy> + void AddFieldArray( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaVcount, type, fieldTags); + } + + // Note: Do not use 0 for itemCount. + template<class CharTy> + void AddFieldFixedArray( + _In_z_ CharTy const* szUtfFieldName, + UINT16 itemCount, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaCcount, type, fieldTags); + this->AddU16(itemCount); + } + + template<class CharTy> + void AddFieldCustom( + _In_z_ CharTy const* szUtfFieldName, + _In_reads_bytes_(cbSchema) void const* pSchema, + UINT16 cbSchema, + Type type, + UINT32 fieldTags = 0) + { + this->AddString(szUtfFieldName); + AddFieldInfo(InMetaCustom, type, fieldTags); + this->AddU16(cbSchema); + auto pbSchema = static_cast<UINT8 const*>(pSchema); + for (UINT32 i = 0; i != cbSchema; i++) + { + this->AddU8(pbSchema[i]); + } + } + }; + +#pragma endregion + +#pragma region EventDataBuilder + + template<class ByteBufferTy> + class EventDataBuilder + { + void operator=(EventDataBuilder const&); // = delete + ByteBufferTy& m_buffer; + + public: + + explicit EventDataBuilder(ByteBufferTy& buffer) + : m_buffer(buffer) + { + return; + } + + EventDataBuilder(EventDataBuilder& other) + : m_buffer(other.m_buffer) + { + return; + } + + EventDataBuilder(EventDataBuilder&& other) + : m_buffer(other.m_buffer) + { + return; + } + + ByteBufferTy& Buffer() + { + return m_buffer; + } + + ByteBufferTy const& Buffer() const + { + return m_buffer; + } + + void Clear() + { + m_buffer.clear(); + } + + UINT32 Size() const + { + return static_cast<UINT32>(m_buffer.size()); + } + + UINT8& operator[](UINT32 index) + { + return m_buffer[index]; + } + + void AddArrayCount(UINT16 cItems) + { + AddBytes(&cItems, sizeof(cItems)); + } + + /* + Adds a value to the payload. + Note: should only be used for blittable POD types with no padding. + */ + template<class T> + void AddValue(T const& value) + { + AddBytes(&reinterpret_cast<const char&>(value), sizeof(T)); + } + + /* + Adds an array of values to the payload. + Note: should only be used for blittable POD types with no padding. + */ + template<class T> + void AddValues(_In_reads_(cValues) T const* pValues, unsigned cValues) + { + AddBytes(pValues, sizeof(T) * cValues); + } + + /* + Appends an InTypeBinary field to the payload. + Compatible with: TypeBinary, TypeIPv6, TypeSocketAddress. + */ + void AddBinary(_In_reads_bytes_(cbData) void const* pbData, UINT16 cbData) + { + AddBytes(&cbData, sizeof(cbData)); + AddBytes(pbData, cbData); + } + + /* + Appends an InTypeCountedAnsiString field to the payload. + Compatible with: TypeCountedMbcsString, TypeCountedMbcsXml, TypeCountedMbcsJson. + */ + void AddCountedString(_In_reads_(cch) char const* pch, UINT16 cch) + { + AddBinary(pch, cch * sizeof(*pch)); + } + + /* + Appends an InTypeCountedString field to the payload. + Compatible with: TypeCountedUtf16String, TypeCountedUtf16Xml, TypeCountedUtf16Json. + */ + void AddCountedString(_In_reads_(cch) wchar_t const* pch, UINT16 cch) + { + AddBinary(pch, cch * sizeof(*pch)); + } + + /* + Appends an InTypeAnsiString field to the payload. + Compatible with: TypeMbcsString, TypeMbcsXml, TypeMbcsJson. + */ + void AddString(_In_z_ char const* psz) + { + AddBytes(psz, static_cast<unsigned>(strlen(psz) + 1)); + } + + /* + Appends a InTypeUnicodeString field to the payload. + Compatible with: TypeUtf16String, TypeUtf16Xml, TypeUtf16Json + */ + void AddString(_In_z_ wchar_t const* psz) + { + AddBytes(psz, static_cast<unsigned>(2 * (wcslen(psz) + 1))); + } + + /* + Appends a raw byte to the payload. (Calls buffer.push_back) + */ + void AddByte(UINT8 val) + { + m_buffer.push_back(val); + } + + /* + Appends raw bytes to the payload. (Calls buffer.insert) + */ + void AddBytes(_In_reads_bytes_(cbData) void const* pbData, unsigned cbData) + { + auto pb = static_cast<UINT8 const*>(pbData); + m_buffer.insert(m_buffer.end(), pb + 0, pb + cbData); + } + }; + +#pragma endregion + +#pragma region Provider + + class Provider + { + Provider(Provider const&); // = delete + void operator=(Provider const&); // = delete + + UINT32 m_levelPlus1; + HRESULT m_hrInitialization; + ULONGLONG m_keywordsAny; + ULONGLONG m_keywordsAll; + REGHANDLE m_hProvider; + UINT8 const* m_pbMetadata; + PENABLECALLBACK m_pfCallback; + LPVOID m_pCallbackContext; + GUID m_id; + + /* + Shortest possible valid provider metadata blob: + Size = 0x0003, Name = "", no options. + */ + static _Ret_bytecount_(3) UINT8 const* NullMetadata() + { + return reinterpret_cast<UINT8 const*>("\03\0"); + } + + static VOID NTAPI EnableCallback( + _In_ LPCGUID pSourceId, + _In_ ULONG callbackType, + _In_ UCHAR level, + _In_ ULONGLONG keywordsAny, + _In_ ULONGLONG keywordsAll, + _In_opt_ PEVENT_FILTER_DESCRIPTOR pFilterData, + _Inout_opt_ PVOID pCallbackContext) + { + if (pCallbackContext) + { + Provider* pProvider = static_cast<Provider*>(pCallbackContext); + switch (callbackType) + { + case 0: // EVENT_CONTROL_CODE_DISABLE_PROVIDER + pProvider->m_levelPlus1 = 0; + break; + case 1: // EVENT_CONTROL_CODE_ENABLE_PROVIDER + pProvider->m_levelPlus1 = level != 0 ? (UINT32)level + 1u : 256u; + pProvider->m_keywordsAny = keywordsAny; + pProvider->m_keywordsAll = keywordsAll; + break; + } + + if (pProvider->m_pfCallback) + { + pProvider->m_pfCallback( + pSourceId, + callbackType, + level, + keywordsAny, + keywordsAll, + pFilterData, + pProvider->m_pCallbackContext); + } + } + } + + _Ret_opt_bytecount_(cbMetadata) + static UINT8* MetadataAlloc( + UINT32 cbMetadata) + { + return static_cast<UINT8*>(HeapAlloc(GetProcessHeap(), 0, cbMetadata)); + } + + static void MetadataFree( + _Post_invalid_ UINT8* pbMetadata) + { + if (pbMetadata) + { + HeapFree(GetProcessHeap(), 0, pbMetadata); + } + } + + void InitFail( + HRESULT hr) + { + _tld_ASSERT(m_pbMetadata != NullMetadata(), "InitFail called with m_pbMetadata == NullMetadata()"); + MetadataFree(const_cast<UINT8*>(m_pbMetadata)); + m_hrInitialization = hr; + m_pbMetadata = NullMetadata(); + } + + template<class CharTy> + void Init( + _In_z_ CharTy const* szName, + _In_opt_ GUID const* pGroupId) + { + // Already initialized: m_pfCallback, m_pCallbackContext, m_id. + m_levelPlus1 = 0; + m_hrInitialization = 0; + m_keywordsAny = 0; + m_keywordsAll = 0; + m_hProvider = 0; + m_pbMetadata = 0; + + ByteArrayWrapper wrapper(0, 0); + ProviderMetadataBuilder<ByteArrayWrapper> builder(wrapper); + builder.Begin(szName); + if (pGroupId) + { + builder.AddTrait(ProviderTraitGroupGuid, pGroupId, sizeof(GUID)); + } + if (!builder.End()) + { + InitFail(E_INVALIDARG); // Metadata > 64KB. + goto Done; + } + + { + UINT32 const cbMetadata = wrapper.size(); + UINT8* pbMetadata = MetadataAlloc(cbMetadata); + if (!pbMetadata) + { + InitFail(E_OUTOFMEMORY); + goto Done; + } + + m_pbMetadata = pbMetadata; + wrapper.reset(pbMetadata, cbMetadata); + builder.Begin(szName); + if (pGroupId) + { + builder.AddTrait(ProviderTraitGroupGuid, pGroupId, sizeof(GUID)); + } + if (!builder.End() || cbMetadata < wrapper.size()) + { + InitFail(0x8000000CL); // E_CHANGED_STATE + goto Done; + } + + { + HRESULT hr = ::tld::RegisterProvider(&m_hProvider, &m_id, m_pbMetadata, &EnableCallback, this); + if (FAILED(hr)) + { + InitFail(hr); + } + } + } + + Done: + + return; + } + + bool IsEnabledForKeywords(ULONGLONG keywords) const + { + return keywords == 0 || ( + (keywords & m_keywordsAny) != 0 && + (keywords & m_keywordsAll) == m_keywordsAll); + } + + public: + + ~Provider() + { + EventUnregister(m_hProvider); + if (m_pbMetadata != NullMetadata()) + { + MetadataFree(const_cast<UINT8*>(m_pbMetadata)); + } + } + + Provider( + _In_z_ wchar_t const* szUtf16Name, + GUID const& providerId, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(providerId) + { + Init(szUtf16Name, pGroupId); + } + + Provider( + _In_z_ char const* szUtf8Name, + GUID const& providerId, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(providerId) + { + Init(szUtf8Name, pGroupId); + } + + explicit Provider( + _In_z_ wchar_t const* szUtf16Name, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(GetGuidForName(szUtf16Name)) + { + Init(szUtf16Name, pGroupId); + } + + explicit Provider( + _In_z_ char const* szUtf8Name, + _In_opt_ GUID const* pGroupId = 0, + PENABLECALLBACK pfCallback = 0, + LPVOID pCallbackContext = 0) + : m_pfCallback(pfCallback) + , m_pCallbackContext(pCallbackContext) + , m_id(GetGuidForName(szUtf8Name)) + { + Init(szUtf8Name, pGroupId); + } + + HRESULT InitializationResult() const + { + return m_hrInitialization; + } + + _Ret_z_ char const* Name() const + { + return reinterpret_cast<char const*>(m_pbMetadata + 2); + } + + GUID const& Id() const + { + return m_id; + } + + _Ret_notnull_ UINT8 const* GetMetadata() const + { + return m_pbMetadata; + } + + UINT16 GetMetadataSize() const + { + return *reinterpret_cast<UINT16 const*>(m_pbMetadata); + } + + bool IsEnabled() const + { + return m_levelPlus1 != 0; + } + + bool IsEnabled(UCHAR level) const + { + return level < m_levelPlus1; + } + + bool IsEnabled(UCHAR level, ULONGLONG keywords) const + { + return level < m_levelPlus1 && IsEnabledForKeywords(keywords); + } + + HRESULT SetInformation( + EVENT_INFO_CLASS informationClass, + _In_reads_bytes_opt_(cbInformation) void* pbInformation, + ULONG cbInformation + ) const + { + return ::tld::SetInformation(m_hProvider, informationClass, pbInformation, cbInformation); + } + + HRESULT WriteGather( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + return ::tld::WriteEvent( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cDataDescriptors, + pDataDescriptors, + pActivityId, + pRelatedActivityId); + } + + HRESULT Write( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_reads_bytes_(cbData) LPCVOID pbData, + UINT32 cbData, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + EventDataDescCreate(dataDesc + 2, pbData, cbData); + return ::tld::WriteEvent( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cbData ? 3 : 2, + dataDesc, + pActivityId, + pRelatedActivityId); + } + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + HRESULT WriteGatherEx( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_range_(2, 128) ULONG cDataDescriptors, + _Inout_count_(cDataDescriptors) EVENT_DATA_DESCRIPTOR* pDataDescriptors, + ULONG64 filter, + ULONG flags, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + return ::tld::WriteEventEx( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cDataDescriptors, + pDataDescriptors, + filter, + flags, + pActivityId, + pRelatedActivityId); + } + + HRESULT WriteEx( + EVENT_DESCRIPTOR const& eventDescriptor, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pEventMetadata, + _In_reads_bytes_(cbData) LPCVOID pbData, + UINT32 cbData, + ULONG64 filter, + ULONG flags, + _In_opt_ LPCGUID pActivityId = 0, + _In_opt_ LPCGUID pRelatedActivityId = 0 + ) const + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + EventDataDescCreate(dataDesc + 2, pbData, cbData); + return ::tld::WriteEventEx( + m_hProvider, + eventDescriptor, + m_pbMetadata, + pEventMetadata, + cbData ? 3 : 2, + dataDesc, + filter, + flags, + pActivityId, + pRelatedActivityId); + } + +#endif // TLD_HAVE_EVENT_WRITE_EX + }; + +#pragma endregion + +#pragma region EventBuilder + + enum EventState + { + EventStateOpen, + EventStateClosed, + EventStateError + }; + + template<class ByteBufferTy> + class EventBuilder + { + void operator=(EventBuilder const&); // = delete + + EventMetadataBuilder<ByteBufferTy> m_metaBuilder; + EventDataBuilder<ByteBufferTy> m_dataBuilder; + EventState& m_eventState; + + EventBuilder(EventBuilder& other, EventMetadataBuilder<ByteBufferTy>&& metaBuilder) + : m_metaBuilder(metaBuilder) + , m_dataBuilder(other.m_dataBuilder) + , m_eventState(other.m_eventState) + { + return; + } + + protected: + + EventBuilder( + ByteBufferTy& metaBuffer, + ByteBufferTy& dataBuffer, + EventState& eventState) + : m_metaBuilder(metaBuffer) + , m_dataBuilder(dataBuffer) + , m_eventState(eventState) + { + return; + } + + public: + + EventBuilder(EventBuilder& other) + : m_metaBuilder(other.m_metaBuilder) + , m_dataBuilder(other.m_dataBuilder) + , m_eventState(other.m_eventState) + { + return; + } + + EventBuilder(EventBuilder&& other) + : m_metaBuilder(other.m_metaBuilder) + , m_dataBuilder(other.m_dataBuilder) + , m_eventState(other.m_eventState) + { + return; + } + + /* + Returns a const reference to the event metadata's underlying buffer. + */ + ByteBufferTy const& MetaBuffer() const + { + return m_metaBuilder.GetBuffer(); + } + + /* + Returns a const reference to the event payload data's underlying buffer. + */ + ByteBufferTy const& DataBuffer() const + { + return m_dataBuilder.Buffer(); + } + + /* + Adds a nested struct to the event's metadata. + Use the returned EventBuilder to add data and metadata for the fields + of the nested struct. + Note: do not call any Add methods on this builder object until you are + done calling Add methods on the nested builder object. + Note: Do not create structs with 0 fields. + */ + template<class CharTy> + EventBuilder AddStruct( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + return EventBuilder(*this, m_metaBuilder.AddStruct(szUtfStructName, fieldTags)); + } + + /* + Adds a nested array of struct to the event's metadata. + Use the returned EventBuilder to add data and metadata for the fields + of the nested struct. + Note: do not call any Add methods on this builder object until you are + done calling Add methods on the nested builder object. + Note: Do not create structs with 0 fields. + */ + template<class CharTy> + EventBuilder AddStructArray( + _In_z_ CharTy const* szUtfStructName, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + return EventBuilder(*this, m_metaBuilder.AddStructArray(szUtfStructName, fieldTags)); + } + + /* + Adds a nested array of struct to the event's metadata. + Use the returned EventBuilder to add data and metadata for the fields + of the nested struct. + Note: do not call any Add methods on this builder object until you are + done calling Add methods on the nested builder object. + Note: Do not use 0 for itemCount. + Note: Do not create structs with 0 fields. + */ + template<class CharTy> + EventBuilder AddStructFixedArray( + _In_z_ CharTy const* szUtfStructName, + UINT16 itemCount, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + return EventBuilder(*this, m_metaBuilder.AddStructFixedArray(szUtfStructName, itemCount, fieldTags)); + } + + /* + Adds a scalar field to the event's metadata. + */ + template<class CharTy> + void AddField( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddField(szUtfFieldName, type, fieldTags); + } + + /* + Adds a variable-length array field to the event's metadata. + The length (item count) must be added to the event's payload + immediately before the array item values. + */ + template<class CharTy> + void AddFieldArray( + _In_z_ CharTy const* szUtfFieldName, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddFieldArray(szUtfFieldName, type, fieldTags); + } + + /* + Adds a fixed-length array field to the event's metadata. + The length (item count) is encoded in the metadata, so it does not + need to be included in the event's payload. + Note: Do not use 0 for itemCount. + */ + template<class CharTy> + void AddFieldFixedArray( + _In_z_ CharTy const* szUtfFieldName, + UINT16 itemCount, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddFieldFixedArray(szUtfFieldName, itemCount, type, fieldTags); + } + + /* + Adds a custom-encoded field to the event's metadata. + Custom-encoded means that you are using some other serialization + system (e.g. Bond, Protocol Buffers) to produce the schema and data. + The payload is encoded as a UINT16 byte count followed by the data. + */ + template<class CharTy> + void AddFieldCustom( + _In_z_ CharTy const* szUtfFieldName, + _In_reads_bytes_(cbSchema) void const* pSchema, + UINT16 cbSchema, + Type type, + UINT32 fieldTags = 0) + { + _tld_ASSERT(m_eventState == EventStateOpen, "Metadata already prepared."); + m_metaBuilder.AddFieldCustom(szUtfFieldName, pSchema, cbSchema, type, fieldTags); + } + + /* + Advanced use: adds raw metadata bytes. + */ + void AddRawMetadata( + _In_reads_bytes_(cbMetadata) void const* pMetadata, + UINT32 cbMetadata) + { + auto& buf = m_metaBuilder.GetBuffer(); + auto pb = static_cast<BYTE const*>(pMetadata); + buf.insert(buf.end(), pb + 0u, pb + cbMetadata); + } + + /* + Adds an array length (item count) to the payload. This is for + variable-length arrays only (not fixed-length), and must occur + immediately before the array item values are written to the payload. + */ + void AddArrayCount(UINT16 cItems) + { + m_dataBuilder.AddArrayCount(cItems); + } + + /* + Adds a value to the payload. + Note: should only be used for blittable POD types with no padding, + e.g. INT32, FILETIME, GUID, not for strings or structs. + */ + template<class T> + void AddValue(T const& value) + { + m_dataBuilder.AddValue(value); + } + + /* + Adds an array of values to the payload. + Note: should only be used for blittable POD types with no padding, + e.g. INT32, FILETIME, GUID, not for strings or structs. + */ + template<class T> + void AddValues(_In_reads_(cValues) T const* pValues, unsigned cValues) + { + m_dataBuilder.AddValues(pValues, cValues); + } + + /* + Appends an InTypeBinary field to the payload. + Compatible with: TypeBinary, TypeIPv6, TypeSocketAddress. + */ + void AddBinary(_In_reads_bytes_(cbData) void const* pbData, UINT16 cbData) + { + m_dataBuilder.AddBinary(pbData, cbData); + } + + /* + Appends an InTypeCountedAnsiString field to the payload. + Compatible with: TypeCountedMbcsString, TypeCountedMbcsXml, TypeCountedMbcsJson. + */ + void AddCountedString(_In_reads_(cch) char const* pch, UINT16 cch) + { + m_dataBuilder.AddCountedString(pch, cch); + } + + /* + Appends an InTypeCountedString field to the payload. + Compatible with: TypeCountedUtf16String, TypeCountedUtf16Xml, TypeCountedUtf16Json. + */ + void AddCountedString(_In_reads_(cch) wchar_t const* pch, UINT16 cch) + { + m_dataBuilder.AddCountedString(pch, cch); + } + + /* + Appends an InTypeAnsiString field to the payload. + Compatible with: TypeMbcsString, TypeMbcsXml, TypeMbcsJson. + */ + void AddString(_In_z_ char const* psz) + { + m_dataBuilder.AddString(psz); + } + + /* + Appends a InTypeUnicodeString field to the payload. + Compatible with: TypeUtf16String, TypeUtf16Xml, TypeUtf16Json + */ + void AddString(_In_z_ wchar_t const* psz) + { + m_dataBuilder.AddString(psz); + } + + /* + Appends a raw byte to the payload. + */ + void AddByte(UINT8 val) + { + m_dataBuilder.AddByte(val); + } + + /* + Appends raw bytes to the payload. + */ + void AddBytes(_In_reads_bytes_(cbData) void const* pbData, unsigned cbData) + { + m_dataBuilder.AddBytes(pbData, cbData); + } + }; + +#pragma endregion + +#pragma region Event + + template<class ByteBufferTy> + class Event + : public EventBuilder<ByteBufferTy> + { + Event(Event const&); // = delete + void operator=(Event const&); // = delete + + ByteBufferTy m_metaBuffer; + ByteBufferTy m_dataBuffer; + EventDescriptor m_descriptor; + GUID const* m_pActivityId; + GUID const* m_pRelatedActivityId; + EventState m_state; + + public: + + Event( + _In_z_ char const* szUtf8Name, + UCHAR level = 5, + ULONGLONG keywords = 0, + UINT32 tags = 0, + UCHAR opcode = 0, + USHORT task = 0, + GUID const* pActivityId = 0, + GUID const* pRelatedActivityId = 0) + : EventBuilder<ByteBufferTy>(m_metaBuffer, m_dataBuffer, m_state) + , m_descriptor(level, keywords, opcode, task) + , m_pActivityId(pActivityId) + , m_pRelatedActivityId(pRelatedActivityId) + , m_state(EventStateOpen) + { + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).Begin(szUtf8Name, tags); + } + + Event( + _In_z_ wchar_t const* szUtf16Name, + UCHAR level = 5, + ULONGLONG keywords = 0, + UINT32 tags = 0, + UCHAR opcode = 0, + USHORT task = 0, + GUID const* pActivityId = 0, + GUID const* pRelatedActivityId = 0) + : EventBuilder<ByteBufferTy>(m_metaBuffer, m_dataBuffer, m_state) + , m_descriptor(level, keywords, opcode, task) + , m_pActivityId(pActivityId) + , m_pRelatedActivityId(pRelatedActivityId) + , m_state(EventStateOpen) + { + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).Begin(szUtf16Name, tags); + } + + /* + Clears the metadata and the payload. + */ + template<class CharTy> + void Reset( + _In_z_ CharTy const* szUtfName, + UCHAR level = 5, + ULONGLONG keywords = 0, + UINT32 tags = 0, + UCHAR opcode = 0, + USHORT task = 0, + GUID* pActivityId = 0, + GUID* pRelatedActivityId = 0) + { + m_metaBuffer.clear(); + m_dataBuffer.clear(); + m_descriptor.Reset(level, keywords, opcode, task); + m_pActivityId = pActivityId; + m_pRelatedActivityId = pRelatedActivityId; + m_state = EventStateOpen; + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).Begin(szUtfName, tags); + } + + /* + Clears the payload without clearing the metadata. + */ + void ResetData() + { + m_dataBuffer.clear(); + } + + /* + Sends the event to ETW using EventWriteTransfer. + */ + HRESULT Write(Provider const& provider) + { + HRESULT hr = E_INVALIDARG; + if (PrepareEvent()) + { + hr = provider.Write( + m_descriptor, + &m_metaBuffer.front(), + m_dataBuffer.empty() ? NULL : &m_dataBuffer.front(), + static_cast<UINT32>(m_dataBuffer.size()), + m_pActivityId, + m_pRelatedActivityId); + } + return hr; + } + + /* + Sends the event to ETW using EventWriteTransfer. + */ + HRESULT Write( + REGHANDLE hProvider, + _In_count_x_((UINT16*)pProviderMetadata) UINT8 const* pProviderMetadata) + { + HRESULT hr = E_INVALIDARG; + + if (PrepareEvent()) + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + ULONG cDataDesc = 2; + if (!m_dataBuffer.empty()) + { + EventDataDescCreate( + &dataDesc[cDataDesc++], + &m_dataBuffer.front(), + static_cast<ULONG>(m_dataBuffer.size())); + } + + hr = ::tld::WriteEvent( + hProvider, + m_descriptor, + pProviderMetadata, + &m_metaBuffer.front(), + cDataDesc, + dataDesc, + m_pActivityId, + m_pRelatedActivityId); + } + + return hr; + } + +#if TLD_HAVE_EVENT_WRITE_EX == 1 + + /* + Sends the event to ETW using EventWriteEx. + */ + HRESULT WriteEx( + Provider const& provider, + ULONG64 filter, + ULONG flags) + { + HRESULT hr = E_INVALIDARG; + if (PrepareEvent()) + { + hr = provider.WriteEx( + m_descriptor, + &m_metaBuffer.front(), + m_dataBuffer.empty() ? NULL : &m_dataBuffer.front(), + static_cast<UINT32>(m_dataBuffer.size()), + filter, + flags, + m_pActivityId, + m_pRelatedActivityId); + } + return hr; + } + + /* + Sends the event to ETW using EventWriteEx. + */ + HRESULT WriteEx( + REGHANDLE hProvider, + _In_count_x_((UINT16*)pEventMetadata) UINT8 const* pProviderMetadata, + ULONG64 filter, + ULONG flags) + { + HRESULT hr = E_INVALIDARG; + + if (PrepareEvent()) + { + EVENT_DATA_DESCRIPTOR dataDesc[3]; + ULONG cDataDesc = 2; + if (!m_dataBuffer.empty()) + { + EventDataDescCreate( + &dataDesc[cDataDesc++], + &m_dataBuffer.front(), + static_cast<ULONG>(m_dataBuffer.size())); + } + + hr = ::tld::WriteEventEx( + hProvider, + m_descriptor, + pProviderMetadata, + &m_metaBuffer.front(), + cDataDesc, + dataDesc, + filter, + flags, + m_pActivityId, + m_pRelatedActivityId); + } + + return hr; + } + +#endif // TLD_HAVE_EVENT_WRITE_EX + +#pragma region Basic properties + + UCHAR Channel() const + { + return m_descriptor.Channel; + } + + /* + Note: the default channel is 11 (WINEVENT_CHANNEL_TRACELOGGING). + Other channels are only supported if the provider is running on + Windows 10 or later. If a provider is running on an earlier version + of Windows and it uses a channel other than 11, TDH will not be able + to decode the events. + */ + void Channel(UCHAR channel) + { + m_descriptor.Channel = channel; + } + + UCHAR Level() const + { + return m_descriptor.Level; + } + + void Level(UCHAR value) + { + m_descriptor.Level = value; + } + + ULONGLONG Keywords() const + { + return m_descriptor.Keyword; + } + + void Keywords(ULONGLONG value) + { + m_descriptor.Keyword = value; + } + + UCHAR Opcode() const + { + return m_descriptor.Opcode; + } + + void Opcode(UCHAR value) + { + m_descriptor.Opcode = value; + } + + USHORT Task() const + { + return m_descriptor.Task; + } + + void Task(USHORT value) + { + m_descriptor.Task = value; + } + + const GUID* ActivityId() const + { + return m_pActivityId; + } + + void ActivityId(GUID* pValue) + { + m_pActivityId = pValue; + } + + const GUID* RelatedActivityId() const + { + return m_pRelatedActivityId; + } + + void RelatedActivityId(GUID* pValue) + { + m_pRelatedActivityId = pValue; + } + + bool IsEnabledFor(Provider const& provider) const + { + return provider.IsEnabled(m_descriptor.Level, m_descriptor.Keyword); + } + +#pragma endregion + + private: + + bool PrepareEvent() + { + if (m_state == EventStateOpen) + { + PrepareEventImpl(); + } + return m_state == EventStateClosed; + } + + void PrepareEventImpl() + { + m_state = + EventMetadataBuilder<ByteBufferTy>(m_metaBuffer).End() + ? EventStateClosed + : EventStateError; + } + }; + +#pragma endregion +} +// namespace tld diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h new file mode 100644 index 000000000..60cac48a5 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_config.h @@ -0,0 +1,186 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#include <map> + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/span_id.h" + +#include "opentelemetry/exporters/etw/etw_provider.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ +/** + * @brief TelemetryProvider Options passed via SDK API. + */ +using TelemetryProviderOptions = + std::map<std::string, nostd::variant<std::string, uint64_t, float, bool>>; + +/** + * @brief TelemetryProvider runtime configuration class. Internal representation + * of TelemetryProviderOptions used by various components of SDK. + */ +typedef struct +{ + bool enableTraceId; // Set `TraceId` on ETW events + bool enableSpanId; // Set `SpanId` on ETW events + bool enableActivityId; // Assign `SpanId` to `ActivityId` + bool enableActivityTracking; // Emit TraceLogging events for Span/Start and Span/Stop Not used + // for Logs + bool enableRelatedActivityId; // Assign parent `SpanId` to `RelatedActivityId` + bool enableAutoParent; // Start new spans as children of current active span, Not used for Logs + ETWProvider::EventFormat + encoding; // Event encoding to use for this provider (TLD, MsgPack, XML, etc.). +} TelemetryProviderConfiguration; + +/** + * @brief Helper template to convert a variant value from TelemetryProviderOptions to + * LoggerProviderConfiguration + * + * @param options TelemetryProviderOptions passed on API surface + * @param key Option name + * @param value Reference to destination value + * @param defaultValue Default value if option is not supplied + */ +template <typename T> +static inline void GetOption(const TelemetryProviderOptions &options, + const char *key, + T &value, + T defaultValue) +{ + auto it = options.find(key); + if (it != options.end()) + { + auto val = it->second; + value = nostd::get<T>(val); + } + else + { + value = defaultValue; + } +} + +/** + * @brief Helper template to convert encoding config option to EventFormat. + * Configuration option passed as `options["encoding"] = "MsgPack"`. + * Default encoding is TraceLogging Dynamic Manifest (TLD). + * + * Valid encoding names listed below. + * + * For MessagePack encoding: + * - "MSGPACK" + * - "MsgPack" + * - "MessagePack" + * + * For XML encoding: + * - "XML" + * - "xml" + * + * For TraceLogging Dynamic encoding: + * - "TLD" + * - "tld" + * + */ +static inline ETWProvider::EventFormat GetEncoding(const TelemetryProviderOptions &options) +{ + ETWProvider::EventFormat evtFmt = ETWProvider::EventFormat::ETW_MANIFEST; + + auto it = options.find("encoding"); + if (it != options.end()) + { + auto varValue = it->second; + std::string val = nostd::get<std::string>(varValue); + +#pragma warning(push) +#pragma warning(disable : 4307) /* Integral constant overflow - OK while computing hash */ + auto h = utils::hashCode(val.c_str()); + switch (h) + { + case CONST_HASHCODE(MSGPACK): + // nobrk + case CONST_HASHCODE(MsgPack): + // nobrk + case CONST_HASHCODE(MessagePack): + evtFmt = ETWProvider::EventFormat::ETW_MSGPACK; + break; + + case CONST_HASHCODE(XML): + // nobrk + case CONST_HASHCODE(xml): + evtFmt = ETWProvider::EventFormat::ETW_XML; + break; + + case CONST_HASHCODE(TLD): + // nobrk + case CONST_HASHCODE(tld): + // nobrk + evtFmt = ETWProvider::EventFormat::ETW_MANIFEST; + break; + + default: + break; + } +#pragma warning(pop) + } + + return evtFmt; +} + +/** + * @brief Utility template to obtain etw::TracerProvider._config or etw::LoggerProvider._config + * + * @tparam T etw::TracerProvider + * @param t etw::TracerProvider ref + * @return TelemetryProviderConfiguration ref + */ +template <class T> +TelemetryProviderConfiguration &GetConfiguration(T &t) +{ + return t.config_; +} + +/** + * @brief Utility template to convert SpanId or TraceId to hex. + * @param id - value of SpanId or TraceId + * @return Hexadecimal representation of Id as string. + */ +template <class T> +static inline std::string ToLowerBase16(const T &id) +{ + char buf[2 * T::kSize] = {0}; + id.ToLowerBase16(buf); + return std::string(buf, sizeof(buf)); +} + +/** + * @brief Utility method to convert span_id (8 byte) to ActivityId GUID (16 bytes) + * @param span OpenTelemetry Span Id object + * @return GUID struct containing 8-bytes of SpanId + 8 NUL bytes. + */ +static inline bool CopySpanIdToActivityId(const opentelemetry::trace::SpanId &span_id, + GUID &outGuid) +{ + memset(&outGuid, 0, sizeof(outGuid)); + if (!span_id.IsValid()) + { + return false; + } + auto spanId = span_id.Id().data(); + uint8_t *guidPtr = reinterpret_cast<uint8_t *>(&outGuid); + for (size_t i = 0; i < 8; i++) + { + guidPtr[i] = spanId[i]; + } + return true; +} + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h new file mode 100644 index 000000000..a2fd6c727 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_fields.h @@ -0,0 +1,142 @@ +/* // Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#pragma once + +#include <unordered_map> + +#include "opentelemetry/exporters/etw/utils.h" + +/* clang-format off */ +#ifdef CUSTOM_ETW_FIELDS_H +/* Customers may redefine the default field names by including CUSTOM_ETW_FIELDS_H header */ +# include CUSTOM_ETW_FIELDS_H +#else + +/** + + List of configurable Field Name constants: + + Version - Schema version (optional for ETW exporter). + _name - Built-in ETW name at envelope level (dedicated ETW field). + _time - Built-in ETW time at envelope level (dedicated ETW field). + SpanId - OT SpanId + TraceId - OT TraceId + StartTime - OT Span start time + Kind - OT Span kind + Name - OT Span name in ETW 'Payload["Name"]' + ParentId - OT Span parentId + Links - OT Span links array + + Other standard fields (reserved names) that may be appended by ETW channel: + + Level - a 1-byte integer that enables filtering based on the severity or verbosity of events + ProviderGuid - ETW Provider Guid + ProviderName - ETW Provider Name + OpcodeName - Name of Opcode (e.g. Start, Stop) + KeywordName - Name of Keyword + TaskName - TaskName, could be handled as an alias to Payload['name'] + ChannelName - ETW Channel Name + EventMessage - ETW Event Message string for unstructured events + ActivityId - ActivityId for EventSource parenting (current event) + RelatedActivityId - RelatedActivityId for EventSource parenting (parent event) + Pid - Process Id + Tid - Thread Id + + Example "Span" as shown in Visual Studio "Diagnostic Events" view. EventName="Span": + + { + "Timestamp": "2021-04-01T00:33:25.5876605-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 20, + "Message": null, + "ProcessId": 10424, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "56f2366b-5475-496f-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 0, + "Name": "B.max", + "ParentId": "8ad900d0587fad4a", + "SpanId": "6b36f25675546f49", + "StartTime": "2021-04-01T07:33:25.587Z", + "TraceId": "8f8ac710c37c5a419f0fe574f335e986" + } + } + + Example named Event on Span. Note that EventName="MyEvent2" in this case: + + { + "Timestamp": "2021-04-01T00:33:22.5848789-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 15, + "Message": null, + "ProcessId": 10424, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "MyEvent2", + "ActivityID": null, + "RelatedActivityID": null, + "Payload": { + "SpanId": "0da9f6bf7524a449", + "TraceId": "7715c9d490f54f44a5d0c6b62570f1b2", + "strKey": "anotherValue", + "uint32Key": 9876, + "uint64Key": 987654321 + } + } + + */ +# define ETW_FIELD_VERSION "Version" /* Event version */ +# define ETW_FIELD_TYPE "Type" /* Event type */ +# define ETW_FIELD_NAME "_name" /* Event name */ +# define ETW_FIELD_TIME "_time" /* Event time */ +# define ETW_FIELD_OPCODE "OpCode" /* OpCode for TraceLogging */ + +# define ETW_FIELD_TRACE_ID "TraceId" /* Trace Id */ +# define ETW_FIELD_SPAN_ID "SpanId" /* Span Id */ +# define ETW_FIELD_SPAN_PARENTID "ParentId" /* Span ParentId */ +# define ETW_FIELD_SPAN_KIND "Kind" /* Span Kind */ +# define ETW_FIELD_SPAN_LINKS "Links" /* Span Links array */ + +# define ETW_FIELD_PAYLOAD_NAME "Name" /* ETW Payload["Name"] */ + +/* Span option constants */ +# define ETW_FIELD_STARTTIME "StartTime" /* Operation start time */ +# define ETW_FIELD_DURATION "Duration" /* Operation duration */ +# define ETW_FIELD_STATUSCODE "StatusCode" /* Span status code */ +# define ETW_FIELD_STATUSMESSAGE "StatusMessage" /* Span status message */ +# define ETW_FIELD_SUCCESS "Success" /* Span success */ +# define ETW_FIELD_TIMESTAMP "Timestamp" /* Log timestamp */ + +/* Value constants */ +# define ETW_VALUE_SPAN "Span" /* ETW event name for Span */ +# define ETW_VALUE_LOG "Log" /* ETW event name for Log */ + +# define ETW_VALUE_SPAN_START "SpanStart" /* ETW for Span Start */ +# define ETW_VALUE_SPAN_END "SpanEnd" /* ETW for Span Start */ + + +/* Log specific */ +# define ETW_FIELD_LOG_BODY "body" /* Log body */ +# define ETW_FIELD_LOG_SEVERITY_TEXT "severityText" /* Sev text */ +# define ETW_FIELD_LOG_SEVERITY_NUM "severityNumber" /* Sev num */ + + +#endif + +/* clang-format on */ diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h new file mode 100644 index 000000000..bd2478224 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h @@ -0,0 +1,297 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +# include <algorithm> + +# include <cstdint> +# include <cstdio> +# include <cstdlib> +# include <sstream> +# include <type_traits> + +# include <fstream> + +# include <map> + +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/nostd/string_view.h" +# include "opentelemetry/nostd/unique_ptr.h" +# include "opentelemetry/nostd/variant.h" + +# include "opentelemetry/common/key_value_iterable_view.h" + +# include "opentelemetry/logs/logger_provider.h" +# include "opentelemetry/trace/span_id.h" +# include "opentelemetry/trace/trace_id.h" + +# include "opentelemetry/exporters/etw/etw_config.h" +# include "opentelemetry/exporters/etw/etw_fields.h" +# include "opentelemetry/exporters/etw/etw_properties.h" +# include "opentelemetry/exporters/etw/etw_provider.h" +# include "opentelemetry/exporters/etw/utils.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ + +class LoggerProvider; + +/** + * @brief Logger class that allows to send logs to ETW Provider. + */ +class Logger : public opentelemetry::logs::Logger +{ + + /** + * @brief Parent provider of this Tracer + */ + etw::LoggerProvider &loggerProvider_; + + /** + * @brief ProviderId (Name or GUID) + */ + std::string provId; + + /** + * @brief Encoding (Manifest, MessagePack or XML) + */ + ETWProvider::EventFormat encoding; + + /** + * @brief Provider Handle + */ + ETWProvider::Handle &provHandle; + + /** + * @brief ETWProvider is a singleton that aggregates all ETW writes. + * @return + */ + static ETWProvider &etwProvider() + { + static ETWProvider instance; // C++11 magic static + return instance; + } + + /** + * @brief Init a reference to etw::ProviderHandle + * @return Provider Handle + */ + ETWProvider::Handle &initProvHandle() { return etwProvider().open(provId, encoding); } + +public: + /** + * @brief Tracer constructor + * @param parent Parent LoggerProvider + * @param providerId ProviderId - Name or GUID + * @param encoding ETW encoding format to use. + */ + Logger(etw::LoggerProvider &parent, + nostd::string_view providerId = "", + ETWProvider::EventFormat encoding = ETWProvider::EventFormat::ETW_MANIFEST) + : opentelemetry::logs::Logger(), + loggerProvider_(parent), + provId(providerId.data(), providerId.size()), + encoding(encoding), + provHandle(initProvHandle()) + {} + + void Log(opentelemetry::logs::Severity severity, + nostd::string_view body, + const common::KeyValueIterable &attributes, + opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::TraceFlags trace_flags, + common::SystemTimestamp timestamp) noexcept override + { + +# ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + // Properties *res = dynamic_cast<Properties *>(&resr); + + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return Log(severity, provId, body, *evt, trace_id, span_id, trace_flags, timestamp); + } +# endif + Properties evtCopy = attributes; + return Log(severity, provId, body, evtCopy, trace_id, span_id, trace_flags, timestamp); + } + + void Log(opentelemetry::logs::Severity severity, + nostd::string_view name, + nostd::string_view body, + const common::KeyValueIterable &attributes, + opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::TraceFlags trace_flags, + common::SystemTimestamp timestamp) noexcept override + { + +# ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + // Properties *res = dynamic_cast<Properties *>(&resr); + + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return Log(severity, name, body, *evt, trace_id, span_id, trace_flags, timestamp); + } +# endif + Properties evtCopy = attributes; + return Log(severity, name, body, evtCopy, trace_id, span_id, trace_flags, timestamp); + } + + virtual void Log(opentelemetry::logs::Severity severity, + nostd::string_view name, + nostd::string_view body, + Properties &evt, + opentelemetry::trace::TraceId trace_id, + opentelemetry::trace::SpanId span_id, + opentelemetry::trace::TraceFlags trace_flags, + common::SystemTimestamp timestamp) noexcept + { + // Populate Etw.EventName attribute at envelope level + evt[ETW_FIELD_NAME] = ETW_VALUE_LOG; + +# ifdef HAVE_FIELD_TIME + { + auto timeNow = std::chrono::system_clock::now().time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(timeNow).count(); + evt[ETW_FIELD_TIME] = utils::formatUtcTimestampMsAsISO8601(millis); + } +# endif + const auto &cfg = GetConfiguration(loggerProvider_); + if (cfg.enableSpanId) + { + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(span_id); + } + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(trace_id); + } + // Populate ActivityId if enabled + GUID ActivityId; + LPGUID ActivityIdPtr = nullptr; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(span_id, ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + evt[ETW_FIELD_PAYLOAD_NAME] = std::string(name.data(), name.size()); + std::chrono::system_clock::time_point ts = timestamp; + int64_t tsMs = + std::chrono::duration_cast<std::chrono::milliseconds>(ts.time_since_epoch()).count(); + evt[ETW_FIELD_TIMESTAMP] = utils::formatUtcTimestampMsAsISO8601(tsMs); + int severity_index = static_cast<int>(severity); + if (severity_index < 0 || + severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value) + { + std::stringstream sout; + sout << "Invalid severity(" << severity_index << ")"; + evt[ETW_FIELD_LOG_SEVERITY_TEXT] = sout.str(); + } + else + { + evt[ETW_FIELD_LOG_SEVERITY_TEXT] = + opentelemetry::logs::SeverityNumToText[severity_index].data(); + } + evt[ETW_FIELD_LOG_SEVERITY_NUM] = static_cast<uint32_t>(severity); + evt[ETW_FIELD_LOG_BODY] = std::string(body.data(), body.length()); + etwProvider().write(provHandle, evt, nullptr, nullptr, 0, encoding); + } + + const nostd::string_view GetName() noexcept override { return std::string(); } + // TODO : Flush and Shutdown method in main Logger API + ~Logger() { etwProvider().close(provHandle); } +}; + +/** + * @brief ETW LoggerProvider + */ +class LoggerProvider : public opentelemetry::logs::LoggerProvider +{ +public: + /** + * @brief LoggerProvider options supplied during initialization. + */ + TelemetryProviderConfiguration config_; + + /** + * @brief Construct instance of LoggerProvider with given options + * @param options Configuration options + */ + LoggerProvider(TelemetryProviderOptions options) : opentelemetry::logs::LoggerProvider() + { + GetOption(options, "enableTraceId", config_.enableTraceId, true); + GetOption(options, "enableSpanId", config_.enableSpanId, true); + GetOption(options, "enableActivityId", config_.enableActivityId, false); + + // Determines what encoding to use for ETW events: TraceLogging Dynamic, MsgPack, XML, etc. + config_.encoding = GetEncoding(options); + } + + LoggerProvider() : opentelemetry::logs::LoggerProvider() + { + config_.encoding = ETWProvider::EventFormat::ETW_MANIFEST; + } + + nostd::shared_ptr<opentelemetry::logs::Logger> GetLogger( + nostd::string_view logger_name, + nostd::string_view options, + nostd::string_view library_name, + nostd::string_view version = "", + nostd::string_view schema_url = "") override + { + UNREFERENCED_PARAMETER(options); + UNREFERENCED_PARAMETER(library_name); + UNREFERENCED_PARAMETER(version); + UNREFERENCED_PARAMETER(schema_url); + ETWProvider::EventFormat evtFmt = config_.encoding; + return nostd::shared_ptr<opentelemetry::logs::Logger>{ + new (std::nothrow) etw::Logger(*this, logger_name, evtFmt)}; + } + + /** + * @brief Obtain ETW Tracer. + * @param name ProviderId (instrumentation name) - Name or GUID + * @param args Additional arguments that controls `codec` of the provider. + * Possible values are: + * - "ETW" - 'classic' Trace Logging Dynamic manifest ETW events. + * - "MSGPACK" - MessagePack-encoded binary payload ETW events. + * - "XML" - XML events (reserved for future use) + * @param library_name Library name + * @param version Library version + * @param schema_url schema URL + * @return + */ + nostd::shared_ptr<opentelemetry::logs::Logger> GetLogger( + nostd::string_view logger_name, + nostd::span<nostd::string_view> args, + nostd::string_view library_name, + nostd::string_view version = "", + nostd::string_view schema_url = "") override + { + UNREFERENCED_PARAMETER(args); + UNREFERENCED_PARAMETER(library_name); + UNREFERENCED_PARAMETER(version); + UNREFERENCED_PARAMETER(schema_url); + ETWProvider::EventFormat evtFmt = config_.encoding; + return nostd::shared_ptr<opentelemetry::logs::Logger>{ + new (std::nothrow) etw::Logger(*this, logger_name, evtFmt)}; + } +}; + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif // ENABLE_LOGS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger_exporter.h new file mode 100644 index 000000000..9b96cb27a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_logger_exporter.h @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef ENABLE_LOGS_PREVIEW +# include <cstdint> +# include <cstdio> +# include <cstdlib> + +# include <mutex> + +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/nostd/string_view.h" +# include "opentelemetry/nostd/unique_ptr.h" + +# include "opentelemetry/common/key_value_iterable_view.h" + +# include "opentelemetry/logs/logger_provider.h" +# include "opentelemetry/trace/span_id.h" +# include "opentelemetry/trace/trace_id.h" + +# include "opentelemetry/sdk/logs/exporter.h" + +# include "opentelemetry/exporters/etw/etw_config.h" +# include "opentelemetry/exporters/etw/etw_logger.h" +# include "opentelemetry/exporters/etw/etw_provider.h" + +# include "opentelemetry/exporters/etw/utils.h" + +#endif // ENABLE_LOGS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_properties.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_properties.h new file mode 100644 index 000000000..3cf365c8a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_properties.h @@ -0,0 +1,456 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "opentelemetry/version.h" + +#include "opentelemetry/common/key_value_iterable_view.h" + +#include <opentelemetry/nostd/span.h> +#include <map> +#include <string> +#include <vector> + +#ifdef _WIN32 +# include <Windows.h> +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ + +/** + * @brief PropertyVariant provides: + * - a constructor to initialize from initializer lists + * - an owning wrapper around `common::AttributeValue` + */ +using PropertyVariant = + nostd::variant<bool, + int32_t, + int64_t, + uint32_t, + uint64_t, + double, + std::string, + const char *, + // 8-bit byte arrays / binary blobs are not part of OT spec yet! + // Ref: https://github.com/open-telemetry/opentelemetry-specification/issues/780 + std::vector<uint8_t>, + std::vector<bool>, + std::vector<int32_t>, + std::vector<int64_t>, + std::vector<uint32_t>, + std::vector<uint64_t>, + std::vector<double>, + std::vector<std::string>>; + +enum PropertyType +{ + kTypeBool, + kTypeInt, + kTypeInt64, + kTypeUInt, + kTypeUInt64, + kTypeDouble, + kTypeString, + kTypeCString, + kTypeSpanByte, + kTypeSpanBool, + kTypeSpanInt, + kTypeSpanInt64, + kTypeSpanUInt, + kTypeSpanUInt64, + kTypeSpanDouble, + kTypeSpanString +}; + +/** + * @brief PropertyValue class that holds PropertyVariant and + * provides converter for non-owning common::AttributeValue + */ +class PropertyValue : public PropertyVariant +{ + + /** + * @brief Convert span<T> to vector<T> + * @tparam T + * @param source + * @return + */ + template <typename T> + static std::vector<T> to_vector(const nostd::span<const T, nostd::dynamic_extent> &source) + { + return std::vector<T>(source.begin(), source.end()); + } + + /** + * @brief Convert span<string_view> to vector<string> + * @param source Span of non-owning string views. + * @return Vector of owned strings. + */ + std::vector<std::string> static to_vector(const nostd::span<const nostd::string_view> &source) + { + std::vector<std::string> result(source.size()); + for (const auto &item : source) + { + result.push_back(std::string(item.data())); + } + return result; + } + + /** + * @brief Convert vector<INTEGRAL> to span<INTEGRAL>. + * @tparam T Integral type + * @param vec Vector of integral type primitives to convert to span. + * @return Span of integral type primitives. + */ + template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true> + static nostd::span<const T> to_span(const std::vector<T> &vec) + { + nostd::span<const T> result(vec.data(), vec.size()); + return result; + } + + /** + * @brief Convert vector<FLOAT> to span<const FLOAT>. + * @tparam T Float type + * @param vec Vector of float type primitives to convert to span. + * @return Span of float type primitives. + */ + template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> + static nostd::span<const T> to_span(const std::vector<T> &vec) + { + nostd::span<const T> result(vec.data(), vec.size()); + return result; + } + +public: + /** + * @brief PropertyValue from bool + * @param v + * @return + */ + PropertyValue(bool value) : PropertyVariant(value) {} + + /** + * @brief PropertyValue from integral. + * @param v + * @return + */ + template <typename TInteger, std::enable_if_t<std::is_integral<TInteger>::value, bool> = true> + PropertyValue(TInteger number) : PropertyVariant(number) + {} + + /** + * @brief PropertyValue from floating point. + * @param v + * @return + */ + template <typename TFloat, std::enable_if_t<std::is_floating_point<TFloat>::value, bool> = true> + PropertyValue(TFloat number) : PropertyVariant(double(number)) + {} + + /** + * @brief Default PropertyValue (int32_t=0) + * @param v + * @return + */ + PropertyValue() : PropertyVariant(int32_t(0)) {} + + /** + * @brief PropertyValue from array of characters as string. + * + * @param v + * @return + */ + PropertyValue(char value[]) : PropertyVariant(std::string(value)) {} + + /** + * @brief PropertyValue from array of characters as string. + * + * @param v + * @return + */ + PropertyValue(const char *value) : PropertyVariant(std::string(value)) {} + + /** + * @brief PropertyValue from string. + * + * @param v + * @return + */ + PropertyValue(const std::string &value) : PropertyVariant(value) {} + + /** + * @brief PropertyValue from vector as array. + * @return + */ + template <typename T> + PropertyValue(std::vector<T> value) : PropertyVariant(value) + {} + + /** + * @brief Convert non-owning common::AttributeValue to owning PropertyValue. + * @return + */ + PropertyValue &FromAttributeValue(const common::AttributeValue &v) + { + switch (v.index()) + { + case common::AttributeType::kTypeBool: + PropertyVariant::operator=(nostd::get<bool>(v)); + break; + case common::AttributeType::kTypeInt: + PropertyVariant::operator=(nostd::get<int32_t>(v)); + break; + case common::AttributeType::kTypeInt64: + PropertyVariant::operator=(nostd::get<int64_t>(v)); + break; + case common::AttributeType::kTypeUInt: + PropertyVariant::operator=(nostd::get<uint32_t>(v)); + break; + case common::AttributeType::kTypeUInt64: + PropertyVariant::operator=(nostd::get<uint64_t>(v)); + break; + case common::AttributeType::kTypeDouble: + PropertyVariant::operator=(nostd::get<double>(v)); + break; + case common::AttributeType::kTypeCString: { + PropertyVariant::operator=(nostd::get<const char *>(v)); + break; + } + case common::AttributeType::kTypeString: { + PropertyVariant::operator= + (std::string{nostd::string_view(nostd::get<nostd::string_view>(v)).data()}); + break; + } + + case common::AttributeType::kTypeSpanByte: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const uint8_t>>(v))); + break; + + case common::AttributeType::kTypeSpanBool: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const bool>>(v))); + break; + + case common::AttributeType::kTypeSpanInt: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const int32_t>>(v))); + break; + + case common::AttributeType::kTypeSpanInt64: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const int64_t>>(v))); + break; + + case common::AttributeType::kTypeSpanUInt: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const uint32_t>>(v))); + break; + + case common::AttributeType::kTypeSpanUInt64: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const uint64_t>>(v))); + break; + + case common::AttributeType::kTypeSpanDouble: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const double>>(v))); + break; + + case common::AttributeType::kTypeSpanString: + PropertyVariant::operator=(to_vector(nostd::get<nostd::span<const nostd::string_view>>(v))); + break; + + default: + break; + } + return (*this); + } + + /** + * @brief Convert owning PropertyValue to non-owning common::AttributeValue + * @param other + */ + common::AttributeValue ToAttributeValue() const + { + common::AttributeValue value; + + switch (this->index()) + { + case PropertyType::kTypeBool: + value = nostd::get<bool>(*this); + break; + case PropertyType::kTypeInt: + value = nostd::get<int32_t>(*this); + break; + case PropertyType::kTypeInt64: + value = nostd::get<int64_t>(*this); + break; + case PropertyType::kTypeUInt: + value = nostd::get<uint32_t>(*this); + break; + case PropertyType::kTypeUInt64: + value = nostd::get<uint64_t>(*this); + break; + case PropertyType::kTypeDouble: + value = nostd::get<double>(*this); + break; + case PropertyType::kTypeString: { + const std::string &str = nostd::get<std::string>(*this); + return nostd::string_view(str.data(), str.size()); + break; + } + case PropertyType::kTypeCString: { + const char *data = nostd::get<const char *>(*this); + return nostd::string_view(data, (data) ? strlen(data) : 0); + break; + } + case PropertyType::kTypeSpanByte: { + value = to_span(nostd::get<std::vector<uint8_t>>(*this)); + break; + } + case PropertyType::kTypeSpanBool: { + const auto &vec = nostd::get<std::vector<bool>>(*this); + // FIXME: sort out how to remap from vector<bool> to span<bool> + UNREFERENCED_PARAMETER(vec); + break; + } + case PropertyType::kTypeSpanInt: + value = to_span(nostd::get<std::vector<int32_t>>(*this)); + break; + + case PropertyType::kTypeSpanInt64: + value = to_span(nostd::get<std::vector<int64_t>>(*this)); + break; + + case PropertyType::kTypeSpanUInt: + value = to_span(nostd::get<std::vector<uint32_t>>(*this)); + break; + + case PropertyType::kTypeSpanUInt64: + value = to_span(nostd::get<std::vector<uint64_t>>(*this)); + break; + + case PropertyType::kTypeSpanDouble: + value = to_span(nostd::get<std::vector<double>>(*this)); + break; + + case PropertyType::kTypeSpanString: + // FIXME: sort out how to remap from vector<string> to span<string_view> + // value = to_span(nostd::get<std::vector<std::string>>(self)); + break; + + default: + break; + } + return value; + } +}; + +/** + * @brief Map of PropertyValue + */ +using PropertyValueMap = std::map<std::string, PropertyValue>; + +/** + * @brief Map of PropertyValue with common::KeyValueIterable interface. + */ +class Properties : public common::KeyValueIterable, public PropertyValueMap +{ + + /** + * @brief Helper tyoe for map constructor. + */ + using PropertyValueType = std::pair<const std::string, PropertyValue>; + +public: + /** + * @brief PropertyValueMap constructor. + */ + Properties() : PropertyValueMap() {} + + /** + * @brief PropertyValueMap constructor from initializer list. + */ + Properties(const std::initializer_list<PropertyValueType> properties) : PropertyValueMap() + { + (*this) = (properties); + } + + /** + * @brief PropertyValueMap assignment operator from initializer list. + */ + Properties &operator=(std::initializer_list<PropertyValueType> properties) + { + PropertyValueMap::operator=(properties); + return (*this); + } + + /** + * @brief PropertyValueMap constructor from map. + */ + Properties(const PropertyValueMap &properties) : PropertyValueMap() { (*this) = properties; } + + /** + * @brief PropertyValueMap assignment operator from map. + */ + Properties &operator=(const PropertyValueMap &properties) + { + PropertyValueMap::operator=(properties); + return (*this); + } + + /** + * @brief PropertyValueMap constructor from KeyValueIterable + * allows to convert non-Owning KeyValueIterable to owning + * container. + * + */ + Properties(const common::KeyValueIterable &other) { (*this) = other; } + + /** + * @brief PropertyValueMap assignment operator. + */ + Properties &operator=(const common::KeyValueIterable &other) + { + clear(); + other.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + std::string k(key.data(), key.length()); + (*this)[k].FromAttributeValue(value); + return true; + }); + return (*this); + } + + /** + * @brief PropertyValueMap property accessor. + */ + PropertyValue &operator[](const std::string &k) { return PropertyValueMap::operator[](k); } + + /** + * Iterate over key-value pairs + * @param callback a callback to invoke for each key-value. If the callback returns false, + * the iteration is aborted. + * @return true if every key-value pair was iterated over + */ + bool ForEachKeyValue(nostd::function_ref<bool(nostd::string_view, common::AttributeValue)> + callback) const noexcept override + { + for (const auto &kv : (*this)) + { + const common::AttributeValue &value = kv.second.ToAttributeValue(); + if (!callback(nostd::string_view{kv.first}, value)) + { + return false; + } + } + return true; + } + + /** + * @return the number of key-value pairs + */ + size_t size() const noexcept override { return PropertyValueMap::size(); } +}; + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_provider.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_provider.h new file mode 100644 index 000000000..51cca03a6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_provider.h @@ -0,0 +1,629 @@ +// // Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4459) +# pragma warning(disable : 4018) +# pragma warning(disable : 5054) +#endif + +#include "opentelemetry/exporters/etw/etw_properties.h" +#include "opentelemetry/exporters/etw/uuid.h" +#include "opentelemetry/version.h" + +#include "opentelemetry/exporters/etw/etw_fields.h" +#include "opentelemetry/exporters/etw/utils.h" + +#ifdef HAVE_MSGPACK +# include "nlohmann/json.hpp" +#endif + +#include "opentelemetry/exporters/etw/etw_traceloggingdynamic.h" + +#include <map> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#ifdef HAVE_KRABS_TESTS +// krabs.hpp requires this definition of min macro from Windows.h +# ifndef min +# define min(a, b) (((a) < (b)) ? (a) : (b)) +# endif +#endif + +#define MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE 0x01000000 + +using namespace OPENTELEMETRY_NAMESPACE::exporter::etw; + +OPENTELEMETRY_BEGIN_NAMESPACE + +class ETWProvider +{ + +public: + const unsigned long STATUS_OK = 0; + const unsigned long STATUS_ERROR = ULONG_MAX; + const unsigned long STATUS_EFBIG = ULONG_MAX - 1; + + enum EventFormat + { + ETW_MANIFEST = 0, + ETW_MSGPACK = 1, + ETW_XML = 2 + }; + + /// <summary> + /// Entry that contains Provider Handle, Provider MetaData and Provider GUID + /// </summary> + struct Handle + { + uint64_t refCount; + REGHANDLE providerHandle; + std::vector<BYTE> providerMetaVector; + GUID providerGuid; + }; + + /// <summary> + /// Check if given provider is registered. + /// </summary> + /// <param name="providerId"></param> + /// <returns></returns> + bool is_registered(const std::string &providerId) + { + std::lock_guard<std::mutex> lock(m_providerMapLock); + auto it = providers().find(providerId); + if (it != providers().end()) + { + if (it->second.providerHandle != INVALID_HANDLE) + { + return true; + } + } + return false; + } + + /// <summary> + /// Get Provider by Name or string representation of GUID + /// </summary> + /// <param name="providerId"></param> + /// <returns></returns> + Handle &open(const std::string &providerId, EventFormat format = EventFormat::ETW_MSGPACK) + { + std::lock_guard<std::mutex> lock(m_providerMapLock); + + // Check and return if provider is already registered + auto it = providers().find(providerId); + if (it != providers().end()) + { + if (it->second.providerHandle != INVALID_HANDLE) + { + it->second.refCount++; + return it->second; + } + } + + // Register provider if necessary + auto &data = providers()[providerId]; + data.providerMetaVector.clear(); + + utils::UUID guid = (providerId.rfind("{", 0) == 0) ? utils::UUID(providerId.c_str()) + : // It's a ProviderGUID + utils::GetProviderGuid(providerId.c_str()); // It's a ProviderName + + data.providerGuid = guid.to_GUID(); + + // TODO: currently we do not allow to specify a custom group GUID + GUID providerGroupGuid = NULL_GUID; + + switch (format) + { + // Register with TraceLoggingDynamic facility - dynamic manifest ETW events. + case EventFormat::ETW_MANIFEST: { + tld::ProviderMetadataBuilder<std::vector<BYTE>> providerMetaBuilder( + data.providerMetaVector); + + // Use Tenant ID as provider Name + providerMetaBuilder.Begin(providerId.c_str()); + providerMetaBuilder.AddTrait(tld::ProviderTraitType::ProviderTraitGroupGuid, + (void *)&providerGroupGuid, sizeof(GUID)); + providerMetaBuilder.End(); + + REGHANDLE hProvider = 0; + if (0 != + tld::RegisterProvider(&hProvider, &data.providerGuid, data.providerMetaVector.data())) + { + // There was an error registering the ETW provider + data.refCount = 0; + data.providerHandle = INVALID_HANDLE; + } + else + { + data.refCount = 1; + data.providerHandle = hProvider; + } + } + break; + +#ifdef HAVE_MSGPACK + // Register for MsgPack payload ETW events. + case EventFormat::ETW_MSGPACK: { + REGHANDLE hProvider = 0; + if (EventRegister(&data.providerGuid, NULL, NULL, &hProvider) != ERROR_SUCCESS) + { + // There was an error registering the ETW provider + data.refCount = 0; + data.providerHandle = INVALID_HANDLE; + } + else + { + data.refCount = 1; + data.providerHandle = hProvider; + } + } + break; +#endif + + default: + // TODO: other protocols, e.g. XML events - not supported yet + break; + } + + // We always return an entry even if we failed to register. + // Caller should check whether the hProvider handle is valid. + return data; + } + + /** + * @brief Unregister provider + * @param data Provider Handle + * @return status code + */ + unsigned long close(Handle handle) + { + std::lock_guard<std::mutex> lock(m_providerMapLock); + + // use reference to provider list, NOT it' copy. + auto &m = providers(); + auto it = m.begin(); + while (it != m.end()) + { + if (it->second.providerHandle == handle.providerHandle) + { + // reference to item in the map of open provider handles + auto &data = it->second; + unsigned long result = STATUS_OK; + + data.refCount--; + if (data.refCount == 0) + { + if (data.providerMetaVector.size()) + { + // ETW/TraceLoggingDynamic provider + result = tld::UnregisterProvider(data.providerHandle); + } + else + { + // Other provider types, e.g. ETW/MsgPack + result = EventUnregister(data.providerHandle); + } + + it->second.providerHandle = INVALID_HANDLE; + if (result == STATUS_OK) + { + m.erase(it); + } + } + return result; + } + } + return STATUS_ERROR; + } + + unsigned long writeMsgPack(Handle &providerData, + exporter::etw::Properties &eventData, + LPCGUID ActivityId = nullptr, + LPCGUID RelatedActivityId = nullptr, + uint8_t Opcode = 0) + { +#ifdef HAVE_MSGPACK + // Events can only be sent if provider is registered + if (providerData.providerHandle == INVALID_HANDLE) + { + // Provider not registered! + return STATUS_ERROR; + } + + std::string eventName = "NoName"; + auto nameField = eventData[ETW_FIELD_NAME]; + +# ifdef HAVE_FIELD_TIME + // Event time is appended by ETW layer itself by default. This code allows + // to override the timestamp by millisecond timestamp, in case if it has + // not been already provided by the upper layer. + if (!eventData.count(ETW_FIELD_TIME)) + { + // TODO: if nanoseconds resolution is required, then we can populate it in 96-bit MsgPack + // spec. auto nanos = + // std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count(); + eventData[ETW_FIELD_TIME] = opentelemetry::utils::getUtcSystemTimeMs(); + } +# endif + + switch (nameField.index()) + { + case PropertyType::kTypeString: + eventName = (char *)(nostd::get<std::string>(nameField).data()); // must be 0-terminated! + break; + case PropertyType::kTypeCString: + eventName = (char *)(nostd::get<const char *>(nameField)); + break; + default: + // If invalid event name is supplied, then we replace it with 'NoName' + break; + } + + /* clang-format off */ + nlohmann::json jObj = + { + { ETW_FIELD_NAME, eventName }, + { ETW_FIELD_OPCODE, Opcode } + }; + /* clang-format on */ + + std::string eventFieldName(ETW_FIELD_NAME); + for (auto &kv : eventData) + { + const char *name = kv.first.data(); + + // Don't include event name field in the Payload section + if (eventFieldName == name) + continue; + + auto &value = kv.second; + switch (value.index()) + { + case PropertyType::kTypeBool: { + UINT8 temp = static_cast<UINT8>(nostd::get<bool>(value)); + jObj[name] = temp; + break; + } + case PropertyType::kTypeInt: { + auto temp = nostd::get<int32_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeInt64: { + auto temp = nostd::get<int64_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeUInt: { + auto temp = nostd::get<uint32_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeUInt64: { + auto temp = nostd::get<uint64_t>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeDouble: { + auto temp = nostd::get<double>(value); + jObj[name] = temp; + break; + } + case PropertyType::kTypeString: { + jObj[name] = nostd::get<std::string>(value); + break; + } + case PropertyType::kTypeCString: { + auto temp = nostd::get<const char *>(value); + jObj[name] = temp; + break; + } +# if HAVE_TYPE_GUID + // TODO: consider adding UUID/GUID to spec + case common::AttributeType::TYPE_GUID: { + auto temp = nostd::get<GUID>(value); + // TODO: add transform from GUID type to string? + jObj[name] = temp; + break; + } +# endif + + // TODO: arrays are not supported yet + case PropertyType::kTypeSpanByte: + case PropertyType::kTypeSpanBool: + case PropertyType::kTypeSpanInt: + case PropertyType::kTypeSpanInt64: + case PropertyType::kTypeSpanUInt: + case PropertyType::kTypeSpanUInt64: + case PropertyType::kTypeSpanDouble: + case PropertyType::kTypeSpanString: + default: + // TODO: unsupported type + break; + } + } + + std::vector<uint8_t> v = nlohmann::json::to_msgpack(jObj); + + EVENT_DESCRIPTOR evtDescriptor; + // TODO: event descriptor may be populated with additional values as follows: + // Id - if 0, auto-incremented sequence number that uniquely identifies event in a trace + // Version - event version + // Channel - 11 for TraceLogging + // Level - verbosity level + // Task - TaskId + // Opcode - described in evntprov.h:259 : 0 - info, 1 - activity start, 2 - activity stop. + EventDescCreate(&evtDescriptor, 0, 0x1, 0, 0, 0, Opcode, 0); + EVENT_DATA_DESCRIPTOR evtData[1]; + EventDataDescCreate(&evtData[0], v.data(), static_cast<ULONG>(v.size())); + ULONG writeResponse = 0; + if ((ActivityId != nullptr) || (RelatedActivityId != nullptr)) + { + writeResponse = EventWriteTransfer(providerData.providerHandle, &evtDescriptor, ActivityId, + RelatedActivityId, 1, evtData); + } + else + { + writeResponse = EventWrite(providerData.providerHandle, &evtDescriptor, 1, evtData); + } + + switch (writeResponse) + { + case ERROR_INVALID_PARAMETER: + break; + case ERROR_INVALID_HANDLE: + break; + case ERROR_ARITHMETIC_OVERFLOW: + break; + case ERROR_MORE_DATA: + break; + case ERROR_NOT_ENOUGH_MEMORY: + break; + default: + break; + } + + if (writeResponse == ERROR_ARITHMETIC_OVERFLOW) + { + return STATUS_EFBIG; + } + return (unsigned long)(writeResponse); +#else + UNREFERENCED_PARAMETER(providerData); + UNREFERENCED_PARAMETER(eventData); + UNREFERENCED_PARAMETER(ActivityId); + UNREFERENCED_PARAMETER(RelatedActivityId); + UNREFERENCED_PARAMETER(Opcode); + return STATUS_ERROR; +#endif + } + + /// <summary> + /// Send event to Provider Id + /// </summary> + /// <param name="providerId"></param> + /// <param name="eventData"></param> + /// <returns></returns> + unsigned long writeTld(Handle &providerData, + Properties &eventData, + LPCGUID ActivityId = nullptr, + LPCGUID RelatedActivityId = nullptr, + uint8_t Opcode = 0 /* Information */) + { + // Make sure you stop sending event before register unregistering providerData + if (providerData.providerHandle == INVALID_HANDLE) + { + // Provider not registered! + return STATUS_ERROR; + } + + UINT32 eventTags = MICROSOFT_EVENTTAG_NORMAL_PERSISTENCE; + + std::vector<BYTE> byteVector; + std::vector<BYTE> byteDataVector; + tld::EventMetadataBuilder<std::vector<BYTE>> builder(byteVector); + tld::EventDataBuilder<std::vector<BYTE>> dbuilder(byteDataVector); + + const std::string EVENT_NAME = ETW_FIELD_NAME; + std::string eventName = "NoName"; + auto nameField = eventData[EVENT_NAME]; + switch (nameField.index()) + { + case PropertyType::kTypeString: + eventName = (char *)(nostd::get<std::string>(nameField).data()); + break; + case PropertyType::kTypeCString: + eventName = (char *)(nostd::get<const char *>(nameField)); + break; + default: + // This is user error. Invalid event name! + // We supply default 'NoName' event name in this case. + break; + } + + builder.Begin(eventName.c_str(), eventTags); + + for (auto &kv : eventData) + { + const char *name = kv.first.data(); + auto &value = kv.second; + switch (value.index()) + { + case PropertyType::kTypeBool: { + builder.AddField(name, tld::TypeBool8); + UINT8 temp = static_cast<UINT8>(nostd::get<bool>(value)); + dbuilder.AddByte(temp); + break; + } + case PropertyType::kTypeInt: { + builder.AddField(name, tld::TypeInt32); + auto temp = nostd::get<int32_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeInt64: { + builder.AddField(name, tld::TypeInt64); + auto temp = nostd::get<int64_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeUInt: { + builder.AddField(name, tld::TypeUInt32); + auto temp = nostd::get<uint32_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeUInt64: { + builder.AddField(name, tld::TypeUInt64); + auto temp = nostd::get<uint64_t>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeDouble: { + builder.AddField(name, tld::TypeDouble); + auto temp = nostd::get<double>(value); + dbuilder.AddValue(temp); + break; + } + case PropertyType::kTypeString: { + builder.AddField(name, tld::TypeUtf8String); + dbuilder.AddString(nostd::get<std::string>(value).data()); + break; + } + case PropertyType::kTypeCString: { + builder.AddField(name, tld::TypeUtf8String); + auto temp = nostd::get<const char *>(value); + dbuilder.AddString(temp); + break; + } +#if HAVE_TYPE_GUID + // TODO: consider adding UUID/GUID to spec + case PropertyType::kGUID: { + builder.AddField(name.c_str(), TypeGuid); + auto temp = nostd::get<GUID>(value); + dbuilder.AddBytes(&temp, sizeof(GUID)); + break; + } +#endif + + // TODO: arrays are not supported + case PropertyType::kTypeSpanByte: + case PropertyType::kTypeSpanBool: + case PropertyType::kTypeSpanInt: + case PropertyType::kTypeSpanInt64: + case PropertyType::kTypeSpanUInt: + case PropertyType::kTypeSpanUInt64: + case PropertyType::kTypeSpanDouble: + case PropertyType::kTypeSpanString: + default: + // TODO: unsupported type + break; + } + } + + if (!builder.End()) // Returns false if the metadata is too large. + { + return STATUS_EFBIG; // if event is too big for UTC to handle + } + + tld::EventDescriptor eventDescriptor; + eventDescriptor.Opcode = Opcode; + eventDescriptor.Level = 0; /* FIXME: Always on for now */ + + EVENT_DATA_DESCRIPTOR pDataDescriptors[3]; + EventDataDescCreate(&pDataDescriptors[2], byteDataVector.data(), + static_cast<ULONG>(byteDataVector.size())); + + HRESULT writeResponse = 0; + if ((ActivityId != nullptr) || (RelatedActivityId != nullptr)) + { + writeResponse = tld::WriteEvent(providerData.providerHandle, eventDescriptor, + providerData.providerMetaVector.data(), byteVector.data(), 3, + pDataDescriptors, ActivityId, RelatedActivityId); + } + else + { + writeResponse = tld::WriteEvent(providerData.providerHandle, eventDescriptor, + providerData.providerMetaVector.data(), byteVector.data(), 3, + pDataDescriptors); + } + + // Event is larger than ETW max sized of 64KB + if (writeResponse == HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW)) + { + return STATUS_EFBIG; + } + + return (unsigned long)(writeResponse); + } + + unsigned long write(Handle &providerData, + Properties &eventData, + LPCGUID ActivityId, + LPCGUID RelatedActivityId, + uint8_t Opcode, + ETWProvider::EventFormat format) + { + if (format == ETWProvider::EventFormat::ETW_MANIFEST) + { + return writeTld(providerData, eventData, ActivityId, RelatedActivityId, Opcode); + } + if (format == ETWProvider::EventFormat::ETW_MSGPACK) + { + return writeMsgPack(providerData, eventData, ActivityId, RelatedActivityId, Opcode); + } + if (format == ETWProvider::EventFormat::ETW_XML) + { + // TODO: not implemented + return STATUS_ERROR; + } + return STATUS_ERROR; + } + + static const REGHANDLE INVALID_HANDLE = _UI64_MAX; + +protected: + const unsigned int LargeEventSizeKB = 62; + + const GUID NULL_GUID = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}}; + + mutable std::mutex m_providerMapLock; + + using ProviderMap = std::map<std::string, Handle>; + + ProviderMap &providers() + { + static std::map<std::string, Handle> providers; + return providers; + } +}; + +OPENTELEMETRY_END_NAMESPACE + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_traceloggingdynamic.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_traceloggingdynamic.h new file mode 100644 index 000000000..a6243c46d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_traceloggingdynamic.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __has_include +# if __has_include("TraceLoggingDynamic.h") +# include "TraceLoggingDynamic.h" +# ifndef HAVE_TLD +# define HAVE_TLD +# endif +# endif +#else +# ifndef HAVE_TLD +# define HAVE_TLD +# endif +# include "TraceLoggingDynamic.h" +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h new file mode 100644 index 000000000..5db31a7a7 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h @@ -0,0 +1,995 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <algorithm> +#include <atomic> + +#include <cstdint> +#include <cstdio> +#include <cstdlib> + +#include <fstream> +#include <iomanip> +#include <iostream> +#include <map> +#include <memory> +#include <mutex> +#include <sstream> +#include <vector> + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/nostd/variant.h" + +#include "opentelemetry/common/key_value_iterable_view.h" + +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/exporters/etw/etw_config.h" +#include "opentelemetry/exporters/etw/etw_fields.h" +#include "opentelemetry/exporters/etw/etw_properties.h" +#include "opentelemetry/exporters/etw/etw_provider.h" +#include "opentelemetry/exporters/etw/utils.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace etw +{ + +class Span; + +/** + * @brief Template that allows to instantiate new Span object for header-only forward-declared + * etw::Span type + * + * @tparam SpanType Expected to be etw::Span + * @tparam TracerType expected to be etw::Tracer + * @param objPtr Pointer to parent + * @param name Span Name + * @param options Span Options + * @return Span instance + */ +template <class SpanType, class TracerType> +SpanType *new_span(TracerType *objPtr, + nostd::string_view name, + const opentelemetry::trace::StartSpanOptions &options) +{ + return new (std::nothrow) SpanType{*objPtr, name, options}; +} + +/** + * @brief Template that allows to convert etw::Span pointer to smart shared pointer to + * `opentelemetry::trace::Span` + * @tparam SpanType Expected to be etw::Span + * @param ptr Pointer to etw::Span + * @return Smart shared pointer to `opentelemetry::trace::Span` + */ +template <class SpanType> +nostd::shared_ptr<opentelemetry::trace::Span> to_span_ptr(SpanType *ptr) +{ + return nostd::shared_ptr<opentelemetry::trace::Span>{ptr}; +} + +class TracerProvider; + +/** + * @brief Utility template for obtaining Span Name + * @tparam T etw::Span + * @param t instance of etw::Span + * @return Span Name + */ +template <class T> +std::string GetName(T &t) +{ + auto sV = t.GetName(); + return std::string(sV.data(), sV.length()); +} + +/** + * @brief Utility template to obtain Span start time + * @tparam T etw::Span + * @param t instance of etw::Span + * @return Span Start timestamp + */ +template <class T> +common::SystemTimestamp GetStartTime(T &t) +{ + return t.GetStartTime(); +} + +/** + * @brief Utility template to obtain Span end time + * @tparam T etw::Span + * @param t instance of etw::Span + * @return Span Stop timestamp + */ +template <class T> +common::SystemTimestamp GetEndTime(T &t) +{ + return t.GetEndTime(); +} + +class Properties; + +/** + * @brief Utility template to store Attributes on Span + * @tparam T etw::Span + * @param instance instance of etw::Span + * @param t Properties to store as Attributes + */ +template <class T> +void SetSpanAttributes(T &instance, Properties &t) +{ + instance.SetAttributes(t); +} + +/** + * @brief Utility template to obtain Span Attributes + * @tparam T etw::Span + * @param instance instance of etw::Span + * @return ref to Span Attributes + */ +template <class T> +Properties &GetSpanAttributes(T &instance) +{ + return instance.GetAttributes(); +} + +template <class T> +void UpdateStatus(T &t, Properties &props) +{ + t.UpdateStatus(props); +} + +/** + * @brief Tracer class that allows to send spans to ETW Provider. + */ +class Tracer : public opentelemetry::trace::Tracer +{ + + /** + * @brief Parent provider of this Tracer + */ + etw::TracerProvider &tracerProvider_; + + /** + * @brief ProviderId (Name or GUID) + */ + std::string provId; + + /** + * @brief Encoding (Manifest, MessagePack or XML) + */ + ETWProvider::EventFormat encoding; + + /** + * @brief Provider Handle + */ + ETWProvider::Handle &provHandle; + + opentelemetry::trace::TraceId traceId_; + + std::atomic<bool> isClosed_{true}; + + /** + * @brief ETWProvider is a singleton that aggregates all ETW writes. + * @return + */ + static ETWProvider &etwProvider() + { + static ETWProvider instance; // C++11 magic static + return instance; + } + + /** + * @brief Internal method that allows to populate Links to other Spans. + * Span links are in hexadecimal representation, comma-separated in their + * order of appearance. + * + * @param attributes + * @param links + */ + virtual void DecorateLinks(Properties &attributes, + const opentelemetry::trace::SpanContextKeyValueIterable &links) const + { + // Add `SpanLinks` attribute if the list is not empty + if (links.size()) + { + size_t idx = 0; + std::string linksValue; + links.ForEachKeyValue( + [&](opentelemetry::trace::SpanContext ctx, const common::KeyValueIterable &) { + if (!linksValue.empty()) + { + linksValue += ','; + linksValue += ToLowerBase16(ctx.span_id()); + } + idx++; + return true; + }); + attributes[ETW_FIELD_SPAN_LINKS] = linksValue; + } + } + + /** + * @brief Allow our friendly etw::Span to end itself on Tracer. + * @param span + * @param + */ + virtual void EndSpan(const Span &span, + const opentelemetry::trace::Span *parentSpan = nullptr, + const opentelemetry::trace::EndSpanOptions & = {}) + { + const auto &cfg = GetConfiguration(tracerProvider_); + const opentelemetry::trace::Span &spanBase = + reinterpret_cast<const opentelemetry::trace::Span &>(span); + auto spanContext = spanBase.GetContext(); + + // Populate Span with presaved attributes + Span ¤tSpan = const_cast<Span &>(span); + Properties evt = GetSpanAttributes(currentSpan); + evt[ETW_FIELD_NAME] = GetName(span); + + if (cfg.enableSpanId) + { + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(spanContext.span_id()); + } + + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(spanContext.trace_id()); + } + + // Populate ActivityId if enabled + GUID ActivityId; + LPGUID ActivityIdPtr = nullptr; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(spanBase.GetContext().span_id(), ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + + // Populate RelatedActivityId if enabled + GUID RelatedActivityId; + LPGUID RelatedActivityIdPtr = nullptr; + if (cfg.enableRelatedActivityId) + { + if (parentSpan != nullptr) + { + if (CopySpanIdToActivityId(parentSpan->GetContext().span_id(), RelatedActivityId)) + { + RelatedActivityIdPtr = &RelatedActivityId; + } + } + } + + if (cfg.enableActivityTracking) + { + // TODO: check what EndSpanOptions should be supported for this exporter. + // The only option available currently (end_steady_time) does not apply. + // + // This event on Span Stop enables generation of "non-transactional" + // OpCode=Stop in alignment with TraceLogging Activity "EventSource" + // spec. + etwProvider().write(provHandle, evt, ActivityIdPtr, RelatedActivityIdPtr, 2, encoding); + } + + { + // Now since the span has ended, we need to emit the "Span" event that + // contains the entire span information, attributes, time, etc. on it. + evt[ETW_FIELD_NAME] = ETW_VALUE_SPAN; + evt[ETW_FIELD_PAYLOAD_NAME] = GetName(span); + + // Add timing details in ISO8601 format, which adequately represents + // the actual time, taking Timezone into consideration. This is NOT + // local time, but rather UTC time (Z=0). + std::chrono::system_clock::time_point startTime = GetStartTime(currentSpan); + std::chrono::system_clock::time_point endTime = GetEndTime(currentSpan); + int64_t startTimeMs = + std::chrono::duration_cast<std::chrono::milliseconds>(startTime.time_since_epoch()) + .count(); + int64_t endTimeMs = + std::chrono::duration_cast<std::chrono::milliseconds>(endTime.time_since_epoch()).count(); + + // It may be more optimal to enable passing timestamps as UTC milliseconds + // since Unix epoch instead of string, but that implies additional tooling + // is needed to convert it, rendering it NOT human-readable. + evt[ETW_FIELD_STARTTIME] = utils::formatUtcTimestampMsAsISO8601(startTimeMs); +#ifdef ETW_FIELD_ENDTTIME + // ETW has its own precise timestamp at envelope layer for every event. + // However, in some scenarios it is easier to deal with ISO8601 strings. + // In that case we convert the app-created timestamp and place it into + // Payload[$ETW_FIELD_TIME] field. The option configurable at compile-time. + evt[ETW_FIELD_ENDTTIME] = utils::formatUtcTimestampMsAsISO8601(endTimeMs); +#endif + // Duration of Span in milliseconds + evt[ETW_FIELD_DURATION] = endTimeMs - startTimeMs; + // Presently we assume that all spans are server spans + evt[ETW_FIELD_SPAN_KIND] = uint32_t(opentelemetry::trace::SpanKind::kServer); + UpdateStatus(currentSpan, evt); + etwProvider().write(provHandle, evt, ActivityIdPtr, RelatedActivityIdPtr, 0, encoding); + } + } + + const opentelemetry::trace::TraceId &trace_id() { return traceId_; } + + friend class Span; + + /** + * @brief Init a reference to etw::ProviderHandle + * @return Provider Handle + */ + ETWProvider::Handle &initProvHandle() + { + isClosed_ = false; + return etwProvider().open(provId, encoding); + } + +public: + /** + * @brief Tracer constructor + * @param parent Parent TraceProvider + * @param providerId ProviderId - Name or GUID + * @param encoding ETW encoding format to use. + */ + Tracer(etw::TracerProvider &parent, + nostd::string_view providerId = "", + ETWProvider::EventFormat encoding = ETWProvider::EventFormat::ETW_MANIFEST) + : opentelemetry::trace::Tracer(), + tracerProvider_(parent), + provId(providerId.data(), providerId.size()), + encoding(encoding), + provHandle(initProvHandle()) + { + // Generate random GUID + GUID trace_id; + CoCreateGuid(&trace_id); + // Populate TraceId of the Tracer with the above GUID + const auto *traceIdPtr = reinterpret_cast<const uint8_t *>(std::addressof(trace_id)); + nostd::span<const uint8_t, opentelemetry::trace::TraceId::kSize> traceIdBytes( + traceIdPtr, traceIdPtr + opentelemetry::trace::TraceId::kSize); + traceId_ = opentelemetry::trace::TraceId(traceIdBytes); + } + + /** + * @brief Start Span + * @param name Span name + * @param attributes Span attributes + * @param links Span links + * @param options Span options + * @return + */ + nostd::shared_ptr<opentelemetry::trace::Span> StartSpan( + nostd::string_view name, + const common::KeyValueIterable &attributes, + const opentelemetry::trace::SpanContextKeyValueIterable &links, + const opentelemetry::trace::StartSpanOptions &options = {}) noexcept override + { +#ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return StartSpan(name, *evt, links, options); + } +#endif + Properties evtCopy = attributes; + return StartSpan(name, evtCopy, links, options); + } + + /** + * @brief Start Span + * @param name Span name + * @param attributes Span attributes + * @param links Span links + * @param options Span options + * @return + */ + virtual nostd::shared_ptr<opentelemetry::trace::Span> StartSpan( + nostd::string_view name, + Properties &evt, + const opentelemetry::trace::SpanContextKeyValueIterable &links, + const opentelemetry::trace::StartSpanOptions &options = {}) noexcept + { + const auto &cfg = GetConfiguration(tracerProvider_); + + // Parent Context: + // - either use current span + // - or attach to parent SpanContext specified in options + opentelemetry::trace::SpanContext parentContext = GetCurrentSpan()->GetContext(); + if (nostd::holds_alternative<opentelemetry::trace::SpanContext>(options.parent)) + { + auto span_context = nostd::get<opentelemetry::trace::SpanContext>(options.parent); + if (span_context.IsValid()) + { + parentContext = span_context; + } + } + + // Populate Etw.RelatedActivityId at envelope level if enabled + GUID RelatedActivityId; + LPCGUID RelatedActivityIdPtr = nullptr; + if (cfg.enableAutoParent) + { + if (cfg.enableRelatedActivityId) + { + if (CopySpanIdToActivityId(parentContext.span_id(), RelatedActivityId)) + { + RelatedActivityIdPtr = &RelatedActivityId; + } + } + } + + // This template pattern allows us to forward-declare the etw::Span, + // create an instance of it, then assign it to tracer::Span result. + auto currentSpan = new_span<Span, Tracer>(this, name, options); + nostd::shared_ptr<opentelemetry::trace::Span> result = to_span_ptr<Span>(currentSpan); + + auto spanContext = result->GetContext(); + + // Decorate with additional standard fields + std::string eventName = name.data(); + + // Populate Etw.EventName attribute at envelope level + evt[ETW_FIELD_NAME] = eventName; + + // Populate Payload["SpanId"] attribute + // Populate Payload["ParentSpanId"] attribute if parent Span is valid + if (cfg.enableSpanId) + { + if (parentContext.IsValid()) + { + evt[ETW_FIELD_SPAN_PARENTID] = ToLowerBase16(parentContext.span_id()); + } + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(spanContext.span_id()); + } + + // Populate Etw.Payload["TraceId"] attribute + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(spanContext.trace_id()); + } + + // Populate Etw.ActivityId at envelope level if enabled + GUID ActivityId; + LPCGUID ActivityIdPtr = nullptr; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(result.get()->GetContext().span_id(), ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + + // Links + DecorateLinks(evt, links); + + // Remember Span attributes to be passed down to ETW on Span end + SetSpanAttributes(*currentSpan, evt); + + if (cfg.enableActivityTracking) + { + // TODO: add support for options that are presently ignored : + // - options.kind + // - options.start_steady_time + // - options.start_system_time + etwProvider().write(provHandle, evt, ActivityIdPtr, RelatedActivityIdPtr, 1, encoding); + } + + return result; + } + + /** + * @brief Force flush data to Tracer, spending up to given amount of microseconds to flush. + * NOTE: this method has no effect for the realtime streaming Tracer. + * + * @param timeout Allow Tracer to drop data if timeout is reached + * @return + */ + void ForceFlushWithMicroseconds(uint64_t) noexcept override {} + + /** + * @brief Close tracer, spending up to given amount of microseconds to flush and close. + * NOTE: This method decrements the reference count on current ETW Provider Handle and + * closes it if reference count on that provider handle is zero. + * + * @param timeout Allow Tracer to drop data if timeout is reached. + * @return + */ + void CloseWithMicroseconds(uint64_t) noexcept override + { + // Close once only + if (!isClosed_.exchange(true)) + { + etwProvider().close(provHandle); + } + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Parent span. + * @param name Event name. + * @param timestamp Event timestamp. + * @param attributes Event attributes. + * @return + */ + void AddEvent(opentelemetry::trace::Span &span, + nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept + { +#ifdef OPENTELEMETRY_RTTI_ENABLED + common::KeyValueIterable &attribs = const_cast<common::KeyValueIterable &>(attributes); + Properties *evt = dynamic_cast<Properties *>(&attribs); + if (evt != nullptr) + { + // Pass as a reference to original modifyable collection without creating a copy + return AddEvent(span, name, timestamp, *evt); + } +#endif + // Pass a copy converted to Properties object on stack + Properties evtCopy = attributes; + return AddEvent(span, name, timestamp, evtCopy); + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Parent span. + * @param name Event name. + * @param timestamp Event timestamp. + * @param attributes Event attributes. + * @return + */ + void AddEvent(opentelemetry::trace::Span &span, + nostd::string_view name, + common::SystemTimestamp timestamp, + Properties &evt) noexcept + { + // TODO: respect originating timestamp. Do we need to reserve + // a special 'Timestamp' field or is it an overkill? The delta + // between when `AddEvent` API is called and when ETW layer + // timestamp is appended is nanos- to micros-, thus handling + // the explicitly provided timestamp is only necessary in case + // if a process wants to submit back-dated or future-dated + // timestamp. Unless there is a strong ask from any ETW customer + // to have it, this feature (custom timestamp) remains unimplemented. + (void)timestamp; + + const auto &cfg = GetConfiguration(tracerProvider_); + + evt[ETW_FIELD_NAME] = name.data(); + + const auto &spanContext = span.GetContext(); + if (cfg.enableSpanId) + { + evt[ETW_FIELD_SPAN_ID] = ToLowerBase16(spanContext.span_id()); + } + + if (cfg.enableTraceId) + { + evt[ETW_FIELD_TRACE_ID] = ToLowerBase16(spanContext.trace_id()); + } + + LPGUID ActivityIdPtr = nullptr; + GUID ActivityId; + if (cfg.enableActivityId) + { + if (CopySpanIdToActivityId(spanContext.span_id(), ActivityId)) + { + ActivityIdPtr = &ActivityId; + } + } + +#ifdef HAVE_FIELD_TIME + { + auto timeNow = std::chrono::system_clock::now().time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(timeNow).count(); + evt[ETW_FIELD_TIME] = utils::formatUtcTimestampMsAsISO8601(millis); + } +#endif + + etwProvider().write(provHandle, evt, ActivityIdPtr, nullptr, 0, encoding); + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Span. + * @param name Event name. + * @param timestamp Event timestamp. + * @return + */ + void AddEvent(opentelemetry::trace::Span &span, + nostd::string_view name, + common::SystemTimestamp timestamp) noexcept + { + AddEvent(span, name, timestamp, sdk::GetEmptyAttributes()); + } + + /** + * @brief Add event data to span associated with tracer. + * @param span Spab. + * @param name Event name. + */ + void AddEvent(opentelemetry::trace::Span &span, nostd::string_view name) + { + AddEvent(span, name, std::chrono::system_clock::now(), sdk::GetEmptyAttributes()); + } + + /** + * @brief Tracer destructor. + */ + virtual ~Tracer() { CloseWithMicroseconds(0); } +}; + +/** + * @brief etw::Span allows to send event data to ETW listener. + */ +class Span : public opentelemetry::trace::Span +{ +protected: + friend class Tracer; + + /** + * @brief Span properties are attached on "Span" event on end of Span. + */ + Properties attributes_; + + common::SystemTimestamp start_time_; + common::SystemTimestamp end_time_; + + opentelemetry::trace::StatusCode status_code_{opentelemetry::trace::StatusCode::kUnset}; + std::string status_description_; + + /** + * @brief Owner Tracer of this Span + */ + Tracer &owner_; + + /** + * @brief Span name. + */ + nostd::string_view name_; + + /** + * @brief Attribute indicating that the span has ended. + */ + std::atomic<bool> has_ended_{false}; + + /** + * @brief Attribute indicating that the span has started. + */ + std::atomic<bool> has_started_{false}; + + /** + * @brief Parent Span of this nested Span (optional) + */ + Span *parent_{nullptr}; + + /** + * @brief Get Parent Span of this nested Span. + * @return Pointer to Parent or nullptr if no Parent. + */ + Span *GetParent() const { return parent_; } + + opentelemetry::trace::SpanContext context_; + + const opentelemetry::trace::SpanContext CreateContext() + { + GUID activity_id; + // Generate random GUID + CoCreateGuid(&activity_id); + const auto *activityIdPtr = reinterpret_cast<const uint8_t *>(std::addressof(activity_id)); + + // Populate SpanId with that GUID + nostd::span<const uint8_t, opentelemetry::trace::SpanId::kSize> spanIdBytes( + activityIdPtr, activityIdPtr + opentelemetry::trace::SpanId::kSize); + const opentelemetry::trace::SpanId spanId(spanIdBytes); + + // Inherit trace_id from Tracer + const opentelemetry::trace::TraceId traceId{owner_.trace_id()}; + // TODO: TraceFlags are not supported by ETW exporter. + const opentelemetry::trace::TraceFlags flags{0}; + // TODO: Remote parent is not supported by ETW exporter. + const bool hasRemoteParent = false; + return opentelemetry::trace::SpanContext{traceId, spanId, flags, hasRemoteParent}; + } + +public: + /** + * @brief Update Properties object with current Span status + * @param evt + */ + void UpdateStatus(Properties &evt) + { + /* Should we avoid populating this extra field if status is unset? */ + if ((status_code_ == opentelemetry::trace::StatusCode::kUnset) || + (status_code_ == opentelemetry::trace::StatusCode::kOk)) + { + evt[ETW_FIELD_SUCCESS] = "True"; + evt[ETW_FIELD_STATUSCODE] = uint32_t(status_code_); + evt[ETW_FIELD_STATUSMESSAGE] = status_description_; + } + else + { + evt[ETW_FIELD_SUCCESS] = "False"; + evt[ETW_FIELD_STATUSCODE] = uint32_t(status_code_); + evt[ETW_FIELD_STATUSMESSAGE] = status_description_; + } + } + + /** + * @brief Get start time of this Span. + * @return + */ + common::SystemTimestamp GetStartTime() { return start_time_; } + + /** + * @brief Get end time of this Span. + * @return + */ + common::SystemTimestamp GetEndTime() { return end_time_; } + + /** + * @brief Get Span Name. + * @return Span Name. + */ + nostd::string_view GetName() const { return name_; } + + /** + * @brief Span constructor + * @param owner Owner Tracer + * @param name Span name + * @param options Span options + * @param parent Parent Span (optional) + * @return + */ + Span(Tracer &owner, + nostd::string_view name, + const opentelemetry::trace::StartSpanOptions &options, + Span *parent = nullptr) noexcept + : opentelemetry::trace::Span(), + start_time_(std::chrono::system_clock::now()), + owner_(owner), + parent_(parent), + context_(CreateContext()) + { + name_ = name; + UNREFERENCED_PARAMETER(options); + } + + /** + * @brief Span Destructor + */ + ~Span() { End(); } + + /** + * @brief Add named event with no attributes. + * @param name Event name. + * @return + */ + void AddEvent(nostd::string_view name) noexcept override { owner_.AddEvent(*this, name); } + + /** + * @brief Add named event with custom timestamp. + * @param name + * @param timestamp + * @return + */ + void AddEvent(nostd::string_view name, common::SystemTimestamp timestamp) noexcept override + { + owner_.AddEvent(*this, name, timestamp); + } + + /** + * @brief Add named event with custom timestamp and attributes. + * @param name Event name. + * @param timestamp Event timestamp. + * @param attributes Event attributes. + * @return + */ + void AddEvent(nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept override + { + owner_.AddEvent(*this, name, timestamp, attributes); + } + + /** + * @brief Set Span status + * @param code Span status code. + * @param description Span description. + * @return + */ + void SetStatus(opentelemetry::trace::StatusCode code, + nostd::string_view description) noexcept override + { + status_code_ = code; + status_description_ = description.data(); + } + + void SetAttributes(Properties attributes) { attributes_ = attributes; } + + /** + * @brief Obtain span attributes specified at Span start. + * NOTE: please consider that this method is NOT thread-safe. + * + * @return ref to Properties collection + */ + Properties &GetAttributes() { return attributes_; } + + /** + * @brief Sets an attribute on the Span. If the Span previously contained a mapping + * for the key, the old value is replaced. + * + * @param key + * @param value + * @return + */ + void SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept override + { + // don't override fields propagated from span data. + if (key == ETW_FIELD_NAME || key == ETW_FIELD_SPAN_ID || key == ETW_FIELD_TRACE_ID) + { + return; + } + attributes_[std::string{key}].FromAttributeValue(value); + } + + /** + * @brief Update Span name. + * + * NOTE: this method is a no-op for streaming implementation. + * We cannot change the Span name after it started streaming. + * + * @param name + * @return + */ + void UpdateName(nostd::string_view) noexcept override + { + // We can't do that! + // name_ = name; + } + + /** + * @brief End Span. + * @param EndSpanOptions + * @return + */ + void End(const opentelemetry::trace::EndSpanOptions &options = {}) noexcept override + { + end_time_ = std::chrono::system_clock::now(); + + if (!has_ended_.exchange(true)) + { + owner_.EndSpan(*this, parent_, options); + } + } + + /** + * @brief Obtain SpanContext + * @return + */ + opentelemetry::trace::SpanContext GetContext() const noexcept override { return context_; } + + /** + * @brief Check if Span is recording data. + * @return + */ + bool IsRecording() const noexcept override + { + // For streaming implementation this should return the state of ETW Listener. + // In certain unprivileged environments, ex. containers, it is impossible + // to determine if a listener is registered. Thus, we always return true. + return true; + } + + virtual void SetToken(nostd::unique_ptr<context::Token> &&token) noexcept + { + // TODO: not implemented + UNREFERENCED_PARAMETER(token); + } + + /// <summary> + /// Get Owner tracer of this Span + /// </summary> + /// <returns></returns> + opentelemetry::trace::Tracer &tracer() const noexcept { return this->owner_; } +}; + +/** + * @brief ETW TracerProvider + */ +class TracerProvider : public opentelemetry::trace::TracerProvider +{ +public: + /** + * @brief TracerProvider options supplied during initialization. + */ + TelemetryProviderConfiguration config_; + + /** + * @brief Construct instance of TracerProvider with given options + * @param options Configuration options + */ + TracerProvider(TelemetryProviderOptions options) : opentelemetry::trace::TracerProvider() + { + // By default we ensure that all events carry their with TraceId and SpanId + GetOption(options, "enableTraceId", config_.enableTraceId, true); + GetOption(options, "enableSpanId", config_.enableSpanId, true); + + // Backwards-compatibility option that allows to reuse ETW-specific parenting described here: + // https://docs.microsoft.com/en-us/uwp/api/windows.foundation.diagnostics.loggingoptions.relatedactivityid + // https://docs.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventwritetransfer + + // Emit separate events compatible with TraceLogging Activity/Start and Activity/Stop + // format for every Span emitted. + GetOption(options, "enableActivityTracking", config_.enableActivityTracking, false); + + // Map current `SpanId` to ActivityId - GUID that uniquely identifies this activity. If NULL, + // ETW gets the identifier from the thread local storage. For details on getting this + // identifier, see EventActivityIdControl. + GetOption(options, "enableActivityId", config_.enableActivityId, false); + + // Map parent `SpanId` to RelatedActivityId - Activity identifier from the previous + // component. Use this parameter to link your component's events to the previous component's + // events. + GetOption(options, "enableRelatedActivityId", config_.enableRelatedActivityId, false); + + // When a new Span is started, the current span automatically becomes its parent. + GetOption(options, "enableAutoParent", config_.enableAutoParent, false); + + // Determines what encoding to use for ETW events: TraceLogging Dynamic, MsgPack, XML, etc. + config_.encoding = GetEncoding(options); + } + + TracerProvider() : opentelemetry::trace::TracerProvider() + { + config_.enableTraceId = true; + config_.enableSpanId = true; + config_.enableActivityId = false; + config_.enableActivityTracking = false; + config_.enableRelatedActivityId = false; + config_.enableAutoParent = false; + config_.encoding = ETWProvider::EventFormat::ETW_MANIFEST; + } + + /** + * @brief Obtain ETW Tracer. + * @param name ProviderId (instrumentation name) - Name or GUID + * + * @param args Additional arguments that controls `codec` of the provider. + * Possible values are: + * - "ETW" - 'classic' Trace Logging Dynamic manifest ETW events. + * - "MSGPACK" - MessagePack-encoded binary payload ETW events. + * - "XML" - XML events (reserved for future use) + * @return + */ + nostd::shared_ptr<opentelemetry::trace::Tracer> GetTracer( + nostd::string_view name, + nostd::string_view args = "", + nostd::string_view schema_url = "") noexcept override + { + UNREFERENCED_PARAMETER(args); + UNREFERENCED_PARAMETER(schema_url); + ETWProvider::EventFormat evtFmt = config_.encoding; + return nostd::shared_ptr<opentelemetry::trace::Tracer>{new (std::nothrow) + Tracer(*this, name, evtFmt)}; + } +}; + +} // namespace etw +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h new file mode 100644 index 000000000..b28f1a021 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer_exporter.h @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include <cstdint> +#include <cstdio> +#include <cstdlib> + +#include <mutex> + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" + +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/exporters/etw/etw_config.h" +#include "opentelemetry/exporters/etw/etw_provider.h" +#include "opentelemetry/exporters/etw/etw_tracer.h" + +#include "opentelemetry/exporters/etw/utils.h" diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/utils.h new file mode 100644 index 000000000..4d38c1fb6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/utils.h @@ -0,0 +1,274 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <algorithm> +#include <chrono> +#include <ctime> +#include <iomanip> +#include <locale> +#include <sstream> +#include <string> + +#include "opentelemetry/common/macros.h" +#include "opentelemetry/exporters/etw/uuid.h" +#include "opentelemetry/version.h" + +#ifdef _WIN32 +# include <Windows.h> +# include <evntprov.h> +# include <wincrypt.h> +# pragma comment(lib, "Advapi32.lib") +# pragma comment(lib, "Rpcrt4.lib") +# include <Objbase.h> +# pragma comment(lib, "Ole32.Lib") +#else +# include <codecvt> +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace utils +{ + +/// <summary> +/// Compile-time constexpr djb2 hash function for strings +/// </summary> +static constexpr uint32_t hashCode(const char *str, uint32_t h = 0) +{ + return (uint32_t)(!str[h] ? 5381 : ((uint32_t)hashCode(str, h + 1) * (uint32_t)33) ^ str[h]); +} + +#define CONST_UINT32_T(x) std::integral_constant<uint32_t, (uint32_t)x>::value + +#define CONST_HASHCODE(name) CONST_UINT32_T(OPENTELEMETRY_NAMESPACE::utils::hashCode(#name)) + +#ifdef _WIN32 + +/// <summary> +/// Compute SHA-1 hash of input buffer and save to output +/// </summary> +/// <param name="pData">Input buffer</param> +/// <param name="nData">Input buffer size</param> +/// <param name="pHashedData">Output buffer</param> +/// <param name="nHashedData">Output buffer size</param> +/// <returns></returns> +static inline bool sha1(const BYTE *pData, DWORD nData, BYTE *pHashedData, DWORD &nHashedData) +{ + bool bRet = false; + HCRYPTPROV hProv = NULL; + HCRYPTHASH hHash = NULL; + + if (!CryptAcquireContext(&hProv, // handle of the CSP + NULL, // key container name + NULL, // CSP name + PROV_RSA_FULL, // provider type + CRYPT_VERIFYCONTEXT)) // no key access is requested + { + bRet = false; + goto CleanUp; + } + + if (!CryptCreateHash(hProv, // handle of the CSP + CALG_SHA1, // hash algorithm to use + 0, // hash key + 0, // reserved + &hHash)) // + { + bRet = false; + goto CleanUp; + } + + if (!CryptHashData(hHash, // handle of the HMAC hash object + pData, // message to hash + nData, // number of bytes of data to add + 0)) // flags + { + bRet = false; + goto CleanUp; + } + + if (!CryptGetHashParam(hHash, // handle of the HMAC hash object + HP_HASHVAL, // query on the hash value + pHashedData, // filled on second call + &nHashedData, // length, in bytes,of the hash + 0)) + { + bRet = false; + goto CleanUp; + } + + bRet = true; + +CleanUp: + + if (hHash) + { + CryptDestroyHash(hHash); + } + + if (hProv) + { + CryptReleaseContext(hProv, 0); + } + return bRet; +} + +/// <summary> +/// Convert UTF-8 string to UTF-16 wide string. +/// +/// </summary> +/// <param name="in"></param> +/// <returns></returns> +static inline std::wstring to_utf16_string(const std::string &in) +{ +# ifdef _WIN32 + int in_length = static_cast<int>(in.size()); + int out_length = MultiByteToWideChar(CP_UTF8, 0, &in[0], in_length, NULL, 0); + std::wstring result(out_length, '\0'); + MultiByteToWideChar(CP_UTF8, 0, &in[0], in_length, &result[0], out_length); + return result; +# else + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; + return converter.from_bytes(in); +# endif +} + +/// <summary> +/// Transform ETW provider name to provider GUID as described here: +/// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ +/// </summary> +/// <param name="providerName"></param> +/// <returns></returns> +static inline GUID GetProviderGuid(const char *providerName) +{ + std::string name(providerName); + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c) { return (char)::toupper(c); }); + + size_t len = name.length() * 2 + 0x10; + uint8_t *buffer = new uint8_t[len]; + uint32_t num = 0x482c2db2; + uint32_t num2 = 0xc39047c8; + uint32_t num3 = 0x87f81a15; + uint32_t num4 = 0xbfc130fb; + + for (int i = 3; i >= 0; i--) + { + buffer[i] = (uint8_t)num; + num = num >> 8; + buffer[i + 4] = (uint8_t)num2; + num2 = num2 >> 8; + buffer[i + 8] = (uint8_t)num3; + num3 = num3 >> 8; + buffer[i + 12] = (uint8_t)num4; + num4 = num4 >> 8; + } + + for (size_t j = 0; j < name.length(); j++) + { + buffer[((2 * j) + 0x10) + 1] = (uint8_t)name[j]; + buffer[(2 * j) + 0x10] = (uint8_t)(name[j] >> 8); + } + + const size_t sha1_hash_size = 21; + uint8_t *buffer2 = new uint8_t[sha1_hash_size]; + DWORD len2 = sha1_hash_size; + sha1((const BYTE *)buffer, (DWORD)len, (BYTE *)buffer2, len2); + + unsigned long a = (((((buffer2[3] << 8) + buffer2[2]) << 8) + buffer2[1]) << 8) + buffer2[0]; + unsigned short b = (unsigned short)((buffer2[5] << 8) + buffer2[4]); + unsigned short num9 = (unsigned short)((buffer2[7] << 8) + buffer2[6]); + + GUID guid; + guid.Data1 = a; + guid.Data2 = b; + guid.Data3 = (unsigned short)((num9 & 0xfff) | 0x5000); + guid.Data4[0] = buffer2[8]; + guid.Data4[1] = buffer2[9]; + guid.Data4[2] = buffer2[10]; + guid.Data4[3] = buffer2[11]; + guid.Data4[4] = buffer2[12]; + guid.Data4[5] = buffer2[13]; + guid.Data4[6] = buffer2[14]; + guid.Data4[7] = buffer2[15]; + + delete[] buffer; + delete[] buffer2; + + return guid; +} +#endif + +static inline int64_t getUtcSystemTimeMs() +{ +#ifdef _WIN32 + ULARGE_INTEGER now; + ::GetSystemTimeAsFileTime(reinterpret_cast<FILETIME *>(&now)); + return (now.QuadPart - 116444736000000000ull) / 10000; +#else + return std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1); +#endif +} + +static inline int64_t getUtcSystemTimeinTicks() +{ +#ifdef _WIN32 + FILETIME tocks; + ::GetSystemTimeAsFileTime(&tocks); + ULONGLONG ticks = (ULONGLONG(tocks.dwHighDateTime) << 32) | tocks.dwLowDateTime; + // number of days from beginning to 1601 multiplied by ticks per day + return ticks + 0x701ce1722770000ULL; +#else + // On Un*x systems system_clock de-facto contains UTC time. Ref: + // https://en.cppreference.com/w/cpp/chrono/system_clock + // This UTC epoch contract has been signed in blood since C++20 + std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); + uint64_t ticks = millis; + ticks *= 10000; // convert millis to ticks (1 tick = 100ns) + ticks += 0x89F7FF5F7B58000ULL; // UTC time 0 in .NET ticks + return ticks; +#endif +} + +/** + * @brief Convert local system milliseconds time to ISO8601 string UTC time + * + * @param timestampMs Milliseconds since epoch in system time + * + * @return ISO8601 UTC string with microseconds part set to 000 + */ +static inline std::string formatUtcTimestampMsAsISO8601(int64_t timestampMs) +{ + char buf[sizeof("YYYY-MM-DDTHH:MM:SS.ssssssZ") + 1] = {0}; +#ifdef _WIN32 + __time64_t seconds = static_cast<__time64_t>(timestampMs / 1000); + int milliseconds = static_cast<int>(timestampMs % 1000); + tm tm; + if (::_gmtime64_s(&tm, &seconds) != 0) + { + memset(&tm, 0, sizeof(tm)); + } + ::_snprintf_s(buf, _TRUNCATE, "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ", 1900 + tm.tm_year, + 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, 1000 * milliseconds); +#else + time_t seconds = static_cast<time_t>(timestampMs / 1000); + int milliseconds = static_cast<int>(timestampMs % 1000); + tm tm; + bool valid = (gmtime_r(&seconds, &tm) != NULL); + if (!valid) + { + memset(&tm, 0, sizeof(tm)); + } + (void)snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ", 1900 + tm.tm_year, + 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, 1000 * milliseconds); +#endif + return buf; +} + +}; // namespace utils + +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/uuid.h b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/uuid.h new file mode 100644 index 000000000..a004aec9e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/include/opentelemetry/exporters/etw/uuid.h @@ -0,0 +1,412 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/version.h" + +#include <cstddef> +#include <cstring> +#include <functional> +#include <initializer_list> +#include <string> +#include <vector> + +#ifdef _WIN32 +# include "Windows.h" +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace utils +{ + +/// <summary> +/// The UUID structure represents the portable cross-platform implementation of a GUID (Globally +/// Unique ID). +/// </summary> +/// <remarks> +/// UUIDs identify objects such as interfaces, manager entry-point vectors (EPVs), and class +/// objects. A UUID is a 128-bit value consisting of one group of eight hexadecimal digits, followed +/// by three groups of four hexadecimal digits, each followed by one group of 12 hexadecimal digits. +/// </remarks> +#pragma pack(push) /* push current alignment to stack */ +#pragma pack(1) /* set alignment to 1 byte boundary */ +struct UUID +{ + /// <summary> + /// Specifies the first eight hexadecimal digits of the GUID. + /// </summary> + uint32_t Data1; + + /// <summary> + /// Specifies the first group of four hexadecimal digits. + ///</summary> + uint16_t Data2; + + /// <summary> + /// Specifies the second group of four hexadecimal digits. + /// </summary> + uint16_t Data3; + + /// <summary> + /// An array of eight bytes. + /// The first two bytes contain the third group of four hexadecimal digits. + /// The remaining six bytes contain the final 12 hexadecimal digits. + /// </summary> + uint8_t Data4[8]; + + /// <summary> + /// The default UUID constructor. + /// Creates a null instance of the UUID object (initialized to all zeros). + /// {00000000-0000-0000-0000-000000000000}. + /// </summary> + UUID() : Data1(0), Data2(0), Data3(0) + { + for (size_t i = 0; i < 8; i++) + { + Data4[i] = 0; + } + } + + /// <summary> + /// A constructor that creates a UUID object from a hyphenated string as defined by + /// https://tools.ietf.org/html/rfc4122#page-4 + /// </summary> + /// <param name="uuid_string">A hyphenated string that contains the UUID (curly braces + /// optional).</param> + UUID(const char *uuid_string) + { + const char *str = uuid_string; + // Skip curly brace + if (str[0] == '{') + { + str++; + } + // Convert to set of integer values + unsigned long p0; + unsigned int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + if ( + // Parse input with dashes + (11 == sscanf_s(str, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, + &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10)) || + // Parse input without dashes + (11 == sscanf_s(str, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, &p3, + &p4, &p5, &p6, &p7, &p8, &p9, &p10))) + { + Data1 = static_cast<uint32_t>(p0); + Data2 = static_cast<uint16_t>(p1); + Data3 = static_cast<uint16_t>(p2); + Data4[0] = static_cast<uint8_t>(p3); + Data4[1] = static_cast<uint8_t>(p4); + Data4[2] = static_cast<uint8_t>(p5); + Data4[3] = static_cast<uint8_t>(p6); + Data4[4] = static_cast<uint8_t>(p7); + Data4[5] = static_cast<uint8_t>(p8); + Data4[6] = static_cast<uint8_t>(p9); + Data4[7] = static_cast<uint8_t>(p10); + } + else // Invalid input--use a safe default value + { + Data1 = 0; + Data2 = 0; + Data3 = 0; + Data4[0] = 0; + Data4[1] = 0; + Data4[2] = 0; + Data4[3] = 0; + Data4[4] = 0; + Data4[5] = 0; + Data4[6] = 0; + Data4[7] = 0; + } + } + + /// <summary> + /// A constructor that creates a UUID object from a byte array. + /// </summary> + /// <param name="guid_bytes">A byte array.</param> + /// <param name="bigEndian"> + /// A boolean value that specifies the byte order.<br> + /// A value of <i>true</i> specifies the more natural human-readable order.<br> + /// A value of <i>false</i> (the default) specifies the same order as the .NET GUID constructor. + /// </param> + UUID(const uint8_t guid_bytes[16], bool bigEndian = false) + { + if (bigEndian) + { + /* Use big endian - human-readable */ + // Part 1 + Data1 = guid_bytes[3]; + Data1 |= ((uint32_t)(guid_bytes[2])) << 8; + Data1 |= ((uint32_t)(guid_bytes[1])) << 16; + Data1 |= ((uint32_t)(guid_bytes[0])) << 24; + // Part 2 + Data2 = guid_bytes[5]; + Data2 |= ((uint16_t)(guid_bytes[4])) << 8; + // Part 3 + Data3 = guid_bytes[7]; + Data3 |= ((uint16_t)(guid_bytes[6])) << 8; + } + else + { + /* Use little endian - the same order as .NET C# Guid() class uses */ + // Part 1 + Data1 = guid_bytes[0]; + Data1 |= ((uint32_t)(guid_bytes[1])) << 8; + Data1 |= ((uint32_t)(guid_bytes[2])) << 16; + Data1 |= ((uint32_t)(guid_bytes[3])) << 24; + // Part 2 + Data2 = guid_bytes[4]; + Data2 |= ((uint16_t)(guid_bytes[5])) << 8; + // Part 3 + Data3 = guid_bytes[6]; + Data3 |= ((uint16_t)(guid_bytes[7])) << 8; + } + // Part 4 + for (size_t i = 0; i < 8; i++) + { + Data4[i] = guid_bytes[8 + i]; + } + } + + /// <summary> + /// A constructor that creates a UUID object from three integers and a byte array. + /// </summary> + /// <param name="d1">An integer that specifies the first eight hexadecimal digits of the + /// UUID.</param> <param name="d2">An integer that specifies the first group of four hexadecimal + /// digits.</param> <param name="d3">An integer that specifies the second group of four + /// hexadecimal digits.</param> <param name="v">A reference to an array of eight bytes. The first + /// two bytes contain the third group of four hexadecimal digits. The remaining six bytes contain + /// the final 12 hexadecimal digits.</param> + UUID(int d1, int d2, int d3, const std::initializer_list<uint8_t> &v) + : Data1((uint32_t)d1), Data2((uint16_t)d2), Data3((uint16_t)d3) + { + size_t i = 0; + for (auto val : v) + { + Data4[i] = val; + i++; + } + } + + /// <summary> + /// The UUID copy constructor. + /// </summary> + /// <param name="uuid">A UUID object.</param> + UUID(const UUID &uuid) + { + this->Data1 = uuid.Data1; + this->Data2 = uuid.Data2; + this->Data3 = uuid.Data3; + memcpy(&(this->Data4[0]), &(uuid.Data4[0]), sizeof(uuid.Data4)); + } + +#ifdef _WIN32 + + /// <summary> + /// A constructor that creates a UUID object from a Windows GUID object. + /// </summary> + /// <param name="guid">A Windows GUID object.</param> + UUID(GUID guid) + { + this->Data1 = guid.Data1; + this->Data2 = guid.Data2; + this->Data3 = guid.Data3; + std::memcpy(&(this->Data4[0]), &(guid.Data4[0]), sizeof(guid.Data4)); + } + + /// <summary> + /// Converts a standard vector of bytes into a Windows GUID object. + /// </summary> + /// <param name="bytes">A standard vector of bytes.</param> + /// <returns>A GUID.</returns> + static GUID to_GUID(std::vector<uint8_t> const &bytes) + { + UUID temp_t = UUID(bytes.data()); + GUID temp; + temp.Data1 = temp_t.Data1; + temp.Data2 = temp_t.Data2; + temp.Data3 = temp_t.Data3; + for (size_t i = 0; i < 8; i++) + { + temp.Data4[i] = temp_t.Data4[i]; + } + return temp; + } + + GUID to_GUID() + { + GUID temp; + temp.Data1 = Data1; + temp.Data2 = Data2; + temp.Data3 = Data3; + for (size_t i = 0; i < 8; i++) + { + temp.Data4[i] = Data4[i]; + } + return temp; + } + +#endif + + /// <summary> + /// Converts this UUID to an array of bytes. + /// </summary> + /// <param name="guid_bytes">A uint8_t array of 16 bytes.</param> + void to_bytes(uint8_t (&guid_bytes)[16]) const + { + // Part 1 + guid_bytes[0] = (uint8_t)((Data1)&0xFF); + guid_bytes[1] = (uint8_t)((Data1 >> 8) & 0xFF); + guid_bytes[2] = (uint8_t)((Data1 >> 16) & 0xFF); + guid_bytes[3] = (uint8_t)((Data1 >> 24) & 0xFF); + // Part 2 + guid_bytes[4] = (uint8_t)((Data2)&0xFF); + guid_bytes[5] = (uint8_t)((Data2 >> 8) & 0xFF); + // Part 3 + guid_bytes[6] = (uint8_t)((Data3)&0xFF); + guid_bytes[7] = (uint8_t)((Data3 >> 8) & 0xFF); + // Part 4 + for (size_t i = 0; i < 8; i++) + { + guid_bytes[8 + i] = Data4[i]; + } + } + + /// <summary> + /// Convert this UUID object to a string. + /// </summary> + /// <returns>This UUID object in a string.</returns> + std::string to_string() const + { + static char inttoHex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + const unsigned buffSize = 36 + 1; // 36 + null-terminator + char buf[buffSize] = {0}; + + int test = (Data1 >> 28 & 0x0000000F); + buf[0] = inttoHex[test]; + test = (int)(Data1 >> 24 & 0x0000000F); + buf[1] = inttoHex[test]; + test = (int)(Data1 >> 20 & 0x0000000F); + buf[2] = inttoHex[test]; + test = (int)(Data1 >> 16 & 0x0000000F); + buf[3] = inttoHex[test]; + test = (int)(Data1 >> 12 & 0x0000000F); + buf[4] = inttoHex[test]; + test = (int)(Data1 >> 8 & 0x0000000F); + buf[5] = inttoHex[test]; + test = (int)(Data1 >> 4 & 0x0000000F); + buf[6] = inttoHex[test]; + test = (int)(Data1 & 0x0000000F); + buf[7] = inttoHex[test]; + buf[8] = '-'; + test = (int)(Data2 >> 12 & 0x000F); + buf[9] = inttoHex[test]; + test = (int)(Data2 >> 8 & 0x000F); + buf[10] = inttoHex[test]; + test = (int)(Data2 >> 4 & 0x000F); + buf[11] = inttoHex[test]; + test = (int)(Data2 & 0x000F); + buf[12] = inttoHex[test]; + buf[13] = '-'; + test = (int)(Data3 >> 12 & 0x000F); + buf[14] = inttoHex[test]; + test = (int)(Data3 >> 8 & 0x000F); + buf[15] = inttoHex[test]; + test = (int)(Data3 >> 4 & 0x000F); + buf[16] = inttoHex[test]; + test = (int)(Data3 & 0x000F); + buf[17] = inttoHex[test]; + buf[18] = '-'; + test = (int)(Data4[0] >> 4 & 0x0F); + buf[19] = inttoHex[test]; + test = (int)(Data4[0] & 0x0F); + buf[20] = inttoHex[test]; + test = (int)(Data4[1] >> 4 & 0x0F); + buf[21] = inttoHex[test]; + test = (int)(Data4[1] & 0x0F); + buf[22] = inttoHex[test]; + buf[23] = '-'; + test = (int)(Data4[2] >> 4 & 0x0F); + buf[24] = inttoHex[test]; + test = (int)(Data4[2] & 0x0F); + buf[25] = inttoHex[test]; + test = (int)(Data4[3] >> 4 & 0x0F); + buf[26] = inttoHex[test]; + test = (int)(Data4[3] & 0x0F); + buf[27] = inttoHex[test]; + test = (int)(Data4[4] >> 4 & 0x0F); + buf[28] = inttoHex[test]; + test = (int)(Data4[4] & 0x0F); + buf[29] = inttoHex[test]; + test = (int)(Data4[5] >> 4 & 0x0F); + buf[30] = inttoHex[test]; + test = (int)(Data4[5] & 0x0F); + buf[31] = inttoHex[test]; + test = (int)(Data4[6] >> 4 & 0x0F); + buf[32] = inttoHex[test]; + test = (int)(Data4[6] & 0x0F); + buf[33] = inttoHex[test]; + test = (int)(Data4[7] >> 4 & 0x0F); + buf[34] = inttoHex[test]; + test = (int)(Data4[7] & 0x0F); + buf[35] = inttoHex[test]; + buf[36] = 0; + + return std::string(buf); + } + + /// <summary> + /// Calculates the size of this UUID object. + /// The output from this method is compatible with std::unordered_map. + /// </summary> + /// <returns>The size of the UUID object in bytes.</returns> + size_t Hash() const + { + // Compute individual hash values for Data1, Data2, Data3, and parts of Data4 + size_t res = 17; + res = res * 31 + Data1; + res = res * 31 + Data2; + res = res * 31 + Data3; + res = res * 31 + (Data4[0] << 24 | Data4[1] << 16 | Data4[6] << 8 | Data4[7]); + return res; + } + + /// <summary> + /// Tests to determine whether two UUID objects are equivalent (needed for maps). + /// </summary> + /// <returns>A boolean value that indicates success or failure.</returns> + bool operator==(UUID const &other) const + { + return Data1 == other.Data1 && Data2 == other.Data2 && Data3 == other.Data3 && + (0 == memcmp(Data4, other.Data4, sizeof(Data4))); + } + + /// <summary> + /// Tests to determine how to sort 2 UUID objects + /// </summary> + /// <returns>A boolean value that indicates success or failure.</returns> + bool operator<(UUID const &other) const + { + return Data1 < other.Data1 || Data2 < other.Data2 || Data3 == other.Data3 || + (memcmp(Data4, other.Data4, sizeof(Data4)) < 0); + } +}; +#pragma pack(pop) /* restore original alignment from stack */ + +/// <summary> +/// Declare UUIDComparer as the Comparer when using UUID as a key in a map or set +/// </summary> +struct UUIDComparer : std::less<UUID> +{ + inline size_t operator()(UUID const &key) const { return key.Hash(); } + + inline bool operator()(UUID const &lhs, UUID const &rhs) const { return lhs.Hash() < rhs.Hash(); } +}; + +} // namespace utils + +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_logger_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_logger_test.cc new file mode 100644 index 000000000..4e2210118 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_logger_test.cc @@ -0,0 +1,101 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW +# ifdef _WIN32 + +# include <gtest/gtest.h> +# include <map> +# include <string> + +# include "opentelemetry/exporters/etw/etw_logger_exporter.h" +# include "opentelemetry/sdk/trace/simple_processor.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +using namespace opentelemetry::exporter::etw; + +const char *kGlobalProviderName = "OpenTelemetry-ETW-TLD"; + +/** + * @brief Logger test with name and unstructured text + * { + * "Timestamp": "2021-09-30T16:40:40.0820563-07:00", + * "ProviderName": "OpenTelemetry-ETW-TLD", + * "Id": 2, + * "Message": null, + * "ProcessId": 23180, + * "Level": "Always", + * "Keywords": "0x0000000000000000", + * "EventName": "Log", + * "ActivityID": null, + * "RelatedActivityID": null, + * "Payload": { + * "Name": "test", + * "SpanId": "0000000000000000", + * "Timestamp": "2021-09-30T23:40:40.081000Z", + * "TraceId": "00000000000000000000000000000000", + * "_name": "Log", + * "body": "This is test message", + * "severityNumber": 5, + * "severityText": "DEBUG" + * } + * } + */ + +TEST(ETWLogger, LoggerCheckWithBody) +{ + std::string providerName = kGlobalProviderName; // supply unique instrumentation name here + exporter::etw::LoggerProvider lp; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = lp.GetLogger(providerName, "", schema_url); + Properties attribs = {{"attrib1", 1}, {"attrib2", 2}}; + EXPECT_NO_THROW(logger->Log(opentelemetry::logs::Severity::kDebug, "This is test log body")); +} + +/** + * @brief Logger Test with structured attributes + * + * Example Event for below test: + * { + * "Timestamp": "2021-09-30T15:04:15.4227815-07:00", + * "ProviderName": "OpenTelemetry-ETW-TLD", + * "Id": 1, + * "Message": null, + * "ProcessId": 33544, + * "Level": "Always", + * "Keywords": "0x0000000000000000", + * "EventName": "Log", + * "ActivityID": null, + * "RelatedActivityID": null, + * "Payload": { + * "Name": "test", + * "SpanId": "0000000000000000", + * "Timestamp": "2021-09-30T22:04:15.422000Z", + * "TraceId": "00000000000000000000000000000000", + * "_name": "Log", + * "attrib1": 1, + * "attrib2": 2, + * "body": "", + * "severityNumber": 5, + * "severityText": "DEBUG" + * } + * } + * + */ + +TEST(ETWLogger, LoggerCheckWithAttributes) +{ + std::string providerName = kGlobalProviderName; // supply unique instrumentation name here + exporter::etw::LoggerProvider lp; + + const std::string schema_url{"https://opentelemetry.io/schemas/1.2.0"}; + auto logger = lp.GetLogger(providerName, "", schema_url); + // Log attributes + Properties attribs = {{"attrib1", 1}, {"attrib2", 2}}; + EXPECT_NO_THROW(logger->Log(opentelemetry::logs::Severity::kDebug, attribs)); +} + +# endif // _WIN32 +#endif // ENABLE_LOGS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_perf_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_perf_test.cc new file mode 100644 index 000000000..79052464e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_perf_test.cc @@ -0,0 +1,190 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef _WIN32 + +# include <benchmark/benchmark.h> +# include <gtest/gtest.h> +# include <map> +# include <string> +# include <unordered_map> + +# include "opentelemetry/exporters/etw/etw_tracer_exporter.h" +# include "opentelemetry/sdk/trace/simple_processor.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +using namespace opentelemetry::exporter::etw; + +namespace +{ +static constexpr const char *providerName = "OpenTelemetry-ETW-StressTest"; + +static exporter::etw::TelemetryProviderOptions providerOptions = { + {"enableTraceId", false}, + {"enableSpanId", false}, + {"enableActivityId", false}, + {"enableRelatedActivityId", false}, + {"enableAutoParent", false}}; + +class ETWProviderStressTest +{ + exporter::etw::TracerProvider provider_; + std::string mode_; + nostd::shared_ptr<trace::Tracer> tracer_; + nostd::shared_ptr<trace::Span> span_; + +public: + /** + * @brief Construct ETW Provider stress test object + * @param mode Operational mode: "TLD" or "MsgPack" + */ + ETWProviderStressTest(std::string mode = "TLD") : mode_(mode), provider_(providerOptions) {} + + /** + * @brief Initializer tracer and start a Span + */ + void Initialize() + { + tracer_ = provider_.GetTracer(providerName, mode_); + span_ = tracer_->StartSpan("Span"); + } + + /** + * @brief Obtain the tracer + */ + nostd::shared_ptr<trace::Tracer> GetTracer() { return tracer_; } + + /** + * @brief Add event using Properties container + */ + bool AddProperties() + { + std::string eventName = "MyEvent"; + Properties event = {{"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}; + span_->AddEvent(eventName, event); + return true; + } + + /** + * @brief Add event using static preallocated "reusable" Properties container. + * This approach works well for single-threaded flows, but may not be safe in + * some multithreaded scenarios in case if reusable `Properties` get concurrently + * modified by different threads (writes to Properties are not thread-safe). + */ + bool AddPropertiesStatic() + { + std::string eventName = "MyEvent"; + static Properties event = {{"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}; + span_->AddEvent(eventName, event); + return true; + } + + /** + * @brief Add event using initializer list + */ + bool AddInitList() + { + std::string eventName = "MyEvent"; + span_->AddEvent(eventName, {{"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}); + return true; + } + + /** + * @brief Add event using unordered_map + */ + bool AddMap() + { + std::string eventName = "MyEvent"; + std::unordered_map<const char *, common::AttributeValue> m = { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"}}; + span_->AddEvent(eventName, m); + return true; + } + + /** + * @brief End Span and close tracer. + */ + void Teardown() + { + span_->End(); + tracer_->CloseWithMicroseconds(0); + } +}; + +ETWProviderStressTest provider; + +/** + * @brief Create Properties and AddEvent(Properties) to Tracer + * @param state Benchmark state. + */ +void BM_AddPropertiesToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddProperties()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddPropertiesToTracer); + +/** + * @brief Create static Properties and AddEvent(Properties) to Tracer + * @param state Benchmark state. + */ +void BM_AddPropertiesStaticToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddPropertiesStatic()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddPropertiesStaticToTracer); + +/** + * @brief Create event via initializer list and AddEvent({...}) to Tracer + * @param state Benchmark state. + */ +void BM_AddInitListToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddInitList()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddInitListToTracer); + +/** + * @brief Create event as `std::map<std::string, common::AttributeValue>` + * and AddEvent(event) to Tracer. + * @param state Benchmark state. + */ +void BM_AddMapToTracer(benchmark::State &state) +{ + provider.Initialize(); + while (state.KeepRunning()) + { + benchmark::DoNotOptimize(provider.AddMap()); + } + provider.Teardown(); +} +BENCHMARK(BM_AddMapToTracer); + +} // namespace + +BENCHMARK_MAIN(); + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_provider_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_provider_test.cc new file mode 100644 index 000000000..d5ebbcad4 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_provider_test.cc @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef _WIN32 + +# include <gtest/gtest.h> +# include <string> + +# include "opentelemetry/exporters/etw/etw_provider.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +TEST(ETWProvider, ProviderIsRegisteredSuccessfully) +{ + std::string providerName = "OpenTelemetry-ETW-Provider"; + static ETWProvider etw; + auto handle = etw.open(providerName.c_str()); + + bool registered = etw.is_registered(providerName); + ASSERT_TRUE(registered); + etw.close(handle); +} + +TEST(ETWProvider, ProviderIsNotRegisteredSuccessfully) +{ + std::string providerName = "OpenTelemetry-ETW-Provider-NULL"; + static ETWProvider etw; + + bool registered = etw.is_registered(providerName); + ASSERT_FALSE(registered); +} + +TEST(ETWProvider, CheckOpenGUIDDataSuccessfully) +{ + std::string providerName = "OpenTelemetry-ETW-Provider"; + + // get GUID from the handle returned + static ETWProvider etw; + auto handle = etw.open(providerName.c_str()); + + utils::UUID uuid_handle(handle.providerGuid); + auto guidStrHandle = uuid_handle.to_string(); + + // get GUID from the providerName + auto guid = utils::GetProviderGuid(providerName.c_str()); + utils::UUID uuid_name(guid); + auto guidStrName = uuid_name.to_string(); + + ASSERT_STREQ(guidStrHandle.c_str(), guidStrName.c_str()); + etw.close(handle); +} + +TEST(ETWProvider, CheckCloseSuccess) +{ + std::string providerName = "OpenTelemetry-ETW-Provider"; + + static ETWProvider etw; + auto handle = etw.open(providerName.c_str(), ETWProvider::EventFormat::ETW_MANIFEST); + auto result = etw.close(handle); + ASSERT_NE(result, etw.STATUS_ERROR); + ASSERT_FALSE(etw.is_registered(providerName)); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_tracer_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_tracer_test.cc new file mode 100644 index 000000000..1c9c1f09b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/etw/test/etw_tracer_test.cc @@ -0,0 +1,388 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef _WIN32 + +# include <gtest/gtest.h> +# include <map> +# include <string> + +# include "opentelemetry/exporters/etw/etw_tracer_exporter.h" +# include "opentelemetry/sdk/trace/simple_processor.h" + +using namespace OPENTELEMETRY_NAMESPACE; + +using namespace opentelemetry::exporter::etw; + +const char *kGlobalProviderName = "OpenTelemetry-ETW-TLD"; + +std::string getTemporaryValue() +{ + return std::string("Value from Temporary std::string"); +} + +/* clang-format off */ +TEST(ETWTracer, TracerCheck) +{ + // SDK customer specifies their unique ETW ProviderName. Every component or library + // is assumed to have its own instrumentation name. Traces are routed to dedicated + // provider. Standard hash function maps from ProviderName to ProviderGUID. + // + // Prominent naming examples from `logman query providers` : + // + // [Docker] {a3693192-9ed6-46d2-a981-f8226c8363bd} + // ... + // Intel-Autologger-iclsClient {B8D7E9A0-65D5-40BE-AFEA-83593FC0164E} + // Intel-Autologger-iclsProxy {301B773F-50F3-4C8E-83F0-53BA9590A13E} + // Intel-Autologger-PTTEKRecertification {F33E9E07-8792-47E8-B3FA-2C92AB32C5B3} + // ... + // NodeJS-ETW-provider {77754E9B-264B-4D8D-B981-E4135C1ECB0C} + // ... + // OpenSSH {C4B57D35-0636-4BC3-A262-370F249F9802} + // ... + // Windows Connect Now {C100BECE-D33A-4A4B-BF23-BBEF4663D017} + // Windows Defender Firewall API {28C9F48F-D244-45A8-842F-DC9FBC9B6E92} + // Windows Defender Firewall API - GP {0EFF663F-8B6E-4E6D-8182-087A8EAA29CB} + // Windows Defender Firewall Driver {D5E09122-D0B2-4235-ADC1-C89FAAAF1069} + + std::string providerName = kGlobalProviderName; // supply unique instrumentation name here + exporter::etw::TracerProvider tp; + + auto tracer = tp.GetTracer(providerName); + + // Span attributes + Properties attribs = + { + {"attrib1", 1}, + {"attrib2", 2} + }; + { + auto topSpan = tracer->StartSpan("MySpanTop"); + auto topScope = tracer->WithActiveSpan(topSpan); + { + auto outerSpan = tracer->StartSpan("MySpanL2", attribs); + auto outerScope = tracer->WithActiveSpan(outerSpan); + + // Create nested span. Note how we share the attributes here. + // It is Okay to either reuse/share or have your own attributes. + { + auto innerSpan = tracer->StartSpan("MySpanL3", attribs); + auto innerScope = tracer->WithActiveSpan(innerSpan); + + // Add span attribute + EXPECT_NO_THROW(outerSpan->SetAttribute("AttrName1", "AttrValue1")); + + // Add first event + std::string eventName1 = "MyEvent1"; + Properties event1 = + { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"} + }; + EXPECT_NO_THROW(outerSpan->AddEvent(eventName1, event1)); + + // Add second event + std::string eventName2 = "MyEvent2"; + Properties event2 = + { + {"uint32Key", (uint32_t)9876}, + {"uint64Key", (uint64_t)987654321}, + {"strKey", "anotherValue"} + }; + EXPECT_NO_THROW(outerSpan->AddEvent(eventName2, event2)); + + std::string eventName3= "MyEvent3"; + Properties event3 = + { + /* Extra metadata that allows event to flow to A.I. pipeline */ + {"metadata", "ai_event"}, + {"uint32Key", (uint32_t)9876}, + {"uint64Key", (uint64_t)987654321}, + // {"int32array", {{-1,0,1,2,3}} }, + {"tempString", getTemporaryValue() } + }; + EXPECT_NO_THROW(innerSpan->AddEvent(eventName3, event3)); + EXPECT_NO_THROW(innerSpan->End()); + + } + EXPECT_NO_THROW(outerSpan->End()); + + } + EXPECT_NO_THROW(topSpan->End()); + } + + EXPECT_NO_THROW(tracer->CloseWithMicroseconds(0)); +} + +// Lowest decoration level -> smaller ETW event size. +// Expected output in C# listener on the other side: +// no ActivityID GUID, no SpanId, no TraceId. +/* +{ + "Timestamp": "2021-03-19T21:04:38.411193-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 13, + "Message": null, + "ProcessId": 15120, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "C.min/Stop", + "ActivityID": null, + "RelatedActivityID": null, + "Payload": {} +} +*/ +TEST(ETWTracer, TracerCheckMinDecoration) +{ + std::string providerName = kGlobalProviderName; + exporter::etw::TracerProvider tp + ({ + {"enableTraceId", false}, + {"enableSpanId", false}, + {"enableActivityId", false}, + {"enableActivityTracking", true}, + {"enableRelatedActivityId", false}, + {"enableAutoParent", false} + }); + auto tracer = tp.GetTracer(providerName); + { + auto aSpan = tracer->StartSpan("A.min"); + auto aScope = tracer->WithActiveSpan(aSpan); + { + auto bSpan = tracer->StartSpan("B.min"); + auto bScope = tracer->WithActiveSpan(bSpan); + { + auto cSpan = tracer->StartSpan("C.min"); + auto cScope = tracer->WithActiveSpan(cSpan); + EXPECT_NO_THROW(cSpan->End()); + } + EXPECT_NO_THROW(bSpan->End()); + } + EXPECT_NO_THROW(aSpan->End()); +} + tracer->CloseWithMicroseconds(0); +} + +// Highest decoration level -> larger ETW event size +// Expected output in C# listener on the other side: +// ActivityID GUID (==SpanId), SpanId, TraceId. +/* +{ + "Timestamp": "2021-03-19T21:04:38.4120274-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 21, + "Message": null, + "ProcessId": 15120, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "C.max/Stop", + "ActivityID": "d55a2c25-8033-40ab-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "SpanId": "252c5ad53380ab40", + "TraceId": "4dea2a63c188894ea5ab979e5cd7ec36" + } +} +*/ +TEST(ETWTracer, TracerCheckMaxDecoration) +{ + std::string providerName = kGlobalProviderName; + exporter::etw::TracerProvider tp + ({ + {"enableTraceId", true}, + {"enableSpanId", true}, + {"enableActivityId", true}, + {"enableRelatedActivityId", true}, + {"enableAutoParent", true} + }); + auto tracer = tp.GetTracer(providerName); + { + auto aSpan = tracer->StartSpan("A.max"); + auto aScope = tracer->WithActiveSpan(aSpan); + { + auto bSpan = tracer->StartSpan("B.max"); + auto bScope = tracer->WithActiveSpan(bSpan); + { + auto cSpan = tracer->StartSpan("C.max"); + auto cScope = tracer->WithActiveSpan(cSpan); + EXPECT_NO_THROW(cSpan->End()); + } + EXPECT_NO_THROW(bSpan->End()); + } + EXPECT_NO_THROW(aSpan->End()); + } + tracer->CloseWithMicroseconds(0); +} + +TEST(ETWTracer, TracerCheckMsgPack) +{ + std::string providerName = "OpenTelemetry-ETW-MsgPack"; + exporter::etw::TracerProvider tp + ({ + {"enableTraceId", true}, + {"enableSpanId", true}, + {"enableActivityId", true}, + {"enableRelatedActivityId", true}, + {"enableAutoParent", true} + }); + auto tracer = tp.GetTracer(providerName); + { + auto aSpan = tracer->StartSpan("A.max"); + auto aScope = tracer->WithActiveSpan(aSpan); + { + auto bSpan = tracer->StartSpan("B.max"); + auto bScope = tracer->WithActiveSpan(bSpan); + { + auto cSpan = tracer->StartSpan("C.max"); + auto cScope = tracer->WithActiveSpan(cSpan); + std::string eventName = "MyMsgPackEvent"; + Properties event = + { + {"uint32Key", (uint32_t)1234}, + {"uint64Key", (uint64_t)1234567890}, + {"strKey", "someValue"} + }; + cSpan->AddEvent(eventName, event); + EXPECT_NO_THROW(cSpan->End()); + } + EXPECT_NO_THROW(bSpan->End()); + } + EXPECT_NO_THROW(aSpan->End()); + } + tracer->CloseWithMicroseconds(0); +} + +/** + * @brief Global Tracer singleton may be placed in .h header and + * shared across different compilation units. All would get the + * same object. + * + * @return Single global tracer instance. +*/ +static OPENTELEMETRY_NAMESPACE::trace::TracerProvider& GetGlobalTracerProvider() +{ + static exporter::etw::TracerProvider tp + ({ + {"enableTraceId", true}, + {"enableSpanId", true}, + {"enableActivityId", true}, + {"enableRelatedActivityId", true}, + {"enableAutoParent", true} + }); + return tp; +} + +static OPENTELEMETRY_NAMESPACE::trace::Tracer& GetGlobalTracer() +{ + static auto tracer = GetGlobalTracerProvider().GetTracer(kGlobalProviderName); + return (*tracer.get()); +} + +TEST(ETWTracer, GlobalSingletonTracer) +{ + // Obtain a global tracer using C++11 magic static. + auto& globalTracer = GetGlobalTracer(); + auto s1 = globalTracer.StartSpan("Span1"); + auto traceId1 = s1->GetContext().trace_id(); + s1->End(); +/* === Span 1 - "TraceId": "182a64258fb1864ca4e1a542eecbd9bf" +{ + "Timestamp": "2021-05-10T11:45:27.028827-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 5, + "Message": null, + "ProcessId": 23712, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "6ed94703-6b0a-4e76-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 0, + "Kind": 1, + "Name": "Span1", + "SpanId": "0347d96e0a6b764e", + "StartTime": "2021-05-10T18:45:27.028000Z", + "StatusCode": 0, + "StatusMessage": "", + "Success": "True", + "TraceId": "182a64258fb1864ca4e1a542eecbd9bf", + "_name": "Span" + } +} +*/ + + // Obtain a different tracer withs its own trace-id. + auto localTracer = GetGlobalTracerProvider().GetTracer(kGlobalProviderName); + auto s2 = localTracer->StartSpan("Span2"); + auto traceId2 = s2->GetContext().trace_id(); + s2->End(); +/* === Span 2 - "TraceId": "334bf9a1eed98d40a873a606295a9368" +{ + "Timestamp": "2021-05-10T11:45:27.0289654-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 5, + "Message": null, + "ProcessId": 23712, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "3b7b2ecb-2e84-4903-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 0, + "Kind": 1, + "Name": "Span2", + "SpanId": "cb2e7b3b842e0349", + "StartTime": "2021-05-10T18:45:27.028000Z", + "StatusCode": 0, + "StatusMessage": "", + "Success": "True", + "TraceId": "334bf9a1eed98d40a873a606295a9368", + "_name": "Span" + } +} +*/ + + // Obtain the same global tracer with the same trace-id as before. + auto& globalTracer2 = GetGlobalTracer(); + auto s3 = globalTracer2.StartSpan("Span3"); + auto traceId3 = s3->GetContext().trace_id(); + s3->End(); +/* === Span 3 - "TraceId": "182a64258fb1864ca4e1a542eecbd9bf" +{ + "Timestamp": "2021-05-10T11:45:27.0290936-07:00", + "ProviderName": "OpenTelemetry-ETW-TLD", + "Id": 5, + "Message": null, + "ProcessId": 23712, + "Level": "Always", + "Keywords": "0x0000000000000000", + "EventName": "Span", + "ActivityID": "0a970247-ba0e-4d4b-0000-000000000000", + "RelatedActivityID": null, + "Payload": { + "Duration": 1, + "Kind": 1, + "Name": "Span3", + "SpanId": "4702970a0eba4b4d", + "StartTime": "2021-05-10T18:45:27.028000Z", + "StatusCode": 0, + "StatusMessage": "", + "Success": "True", + "TraceId": "182a64258fb1864ca4e1a542eecbd9bf", + "_name": "Span" + } +} +*/ + EXPECT_NE(traceId1, traceId2); + EXPECT_EQ(traceId1, traceId3); + + localTracer->CloseWithMicroseconds(0); + globalTracer.CloseWithMicroseconds(0); +} + +/* clang-format on */ + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/BUILD new file mode 100644 index 000000000..cc1babae0 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/BUILD @@ -0,0 +1,217 @@ +package(default_visibility = ["//visibility:public"]) + +load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake", "configure_make", "configure_make_variant") + +constraint_setting( + name = "incompatible_setting", +) + +constraint_value( + name = "incompatible", + constraint_setting = ":incompatible_setting", +) + +config_setting( + name = "windows", + constraint_values = [ + "@bazel_tools//platforms:windows", + ], + tags = ["jaeger"], + visibility = ["//visibility:private"], +) + +THRIFT_CACHE_ENTRIES = { + "CMAKE_BUILD_TYPE": "Release", + "BUILD_COMPILER": "OFF", + "BUILD_CPP": "ON", + "BUILD_LIBRARIES": "ON", + "BUILD_NODEJS": "OFF", + "BUILD_PYTHON": "OFF", + "BUILD_JAVASCRIPT": "OFF", + "BUILD_C_GLIB": "OFF", + "BUILD_JAVA": "OFF", + "BUILD_TESTING": "OFF", + "BUILD_TUTORIALS": "OFF", + "WITH_HASKELL": "OFF", +} + +THRIFT_CACHE_ENTRIES_WIN = { + "CMAKE_BUILD_TYPE": "Release", + "BUILD_COMPILER": "OFF", + "BUILD_CPP": "ON", + "BUILD_LIBRARIES": "ON", + "BUILD_NODEJS": "OFF", + "BUILD_PYTHON": "OFF", + "BUILD_JAVASCRIPT": "OFF", + "BUILD_C_GLIB": "OFF", + "BUILD_JAVA": "OFF", + "BUILD_TESTING": "OFF", + "BUILD_TUTORIALS": "OFF", + "WITH_HASKELL": "OFF", + "WITH_STDTHREADS": "ON", + "WITH_BOOSTTHREADS": "OFF", + "WITH_BOOST_FUNCTIONAL": "OFF", + "WITH_BOOST_SMART_PTR": "OFF", + "BUILD_SHARED_LIBS": "OFF", + "CMAKE_TOOLCHAIN_FILE": "$VCPKG_DIR/scripts/buildsystems/vcpkg.cmake", +} + +cmake( + name = "thrift", + cache_entries = select({ + "@bazel_tools//platforms:osx": THRIFT_CACHE_ENTRIES, + "@bazel_tools//platforms:linux": THRIFT_CACHE_ENTRIES, + "@bazel_tools//platforms:windows": THRIFT_CACHE_ENTRIES_WIN, + }), + copts = [ + "-Ilibs/exporters/jaeger/openssl/include", + "-fexceptions", + ], + generate_args = select({ + "@bazel_tools//platforms:osx": [], + "@bazel_tools//platforms:linux": [], + "@bazel_tools//platforms:windows": [ + "-G \"NMake Makefiles\"", + ], + }), + install = True, + lib_source = "@com_github_thrift//:all_srcs", + out_lib_dir = select({ + "@bazel_tools//platforms:osx": "lib", + "@bazel_tools//platforms:linux": "lib", + "@bazel_tools//platforms:windows": "bin", + }), + out_static_libs = select({ + "@bazel_tools//platforms:osx": [ + "libthrift.a", + "libthriftz.a", + ], + "@bazel_tools//platforms:linux": [ + "libthrift.a", + "libthriftz.a", + ], + "@bazel_tools//platforms:windows": [ + "thriftmd.lib", + ], + }), + tags = ["jaeger"], + visibility = ["//visibility:private"], + deps = [], +) + +THRIFT_GEN_DEPS = [ + ":thrift", + "//ext/src/http/client/curl:http_client_curl", +] + +THRIFT_GEN_DEPS_WIN = THRIFT_GEN_DEPS + [ + "@boost_all_hdrs//:boost_all_hdrs", +] + +cc_library( + name = "jaeger_thrift_gencpp", + srcs = [ + "thrift-gen/Agent.cpp", + "thrift-gen/Collector.cpp", + "thrift-gen/ZipkinCollector.cpp", + "thrift-gen/jaeger_types.cpp", + "thrift-gen/zipkincore_constants.cpp", + "thrift-gen/zipkincore_types.cpp", + ], + hdrs = [ + "thrift-gen/Agent.h", + "thrift-gen/Collector.h", + "thrift-gen/ZipkinCollector.h", + "thrift-gen/agent_types.h", + "thrift-gen/jaeger_types.h", + "thrift-gen/zipkincore_constants.h", + "thrift-gen/zipkincore_types.h", + ], + copts = [ + "-fexceptions", + ], + strip_include_prefix = "thrift-gen", + tags = ["jaeger"], + deps = select({ + "@bazel_tools//platforms:osx": THRIFT_GEN_DEPS, + "@bazel_tools//platforms:linux": THRIFT_GEN_DEPS, + "@bazel_tools//platforms:windows": THRIFT_GEN_DEPS_WIN, + }), +) + +cc_library( + name = "jaeger_exporter", + srcs = [ + ], + hdrs = [ + "src/THttpTransport.h", + "src/TUDPTransport.h", + "src/http_transport.h", + "src/sender.h", + "src/thrift_sender.h", + "src/transport.h", + "src/udp_transport.h", + ], + copts = ["-fexceptions"], + strip_include_prefix = "src", + tags = ["jaeger"], + deps = [ + ":jaeger_thrift_gencpp", + ], +) + +cc_library( + name = "opentelemetry_exporter_jaeger_trace", + srcs = [ + "src/THttpTransport.cc", + "src/TUDPTransport.cc", + "src/http_transport.cc", + "src/jaeger_exporter.cc", + "src/recordable.cc", + "src/thrift_sender.cc", + "src/udp_transport.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/jaeger/jaeger_exporter.h", + "include/opentelemetry/exporters/jaeger/recordable.h", + ], + copts = ["-fexceptions"], + strip_include_prefix = "include", + tags = ["jaeger"], + deps = [ + ":jaeger_exporter", + "//sdk/src/common:global_log_handler", + ], +) + +cc_test( + name = "jaeger_recordable_test", + srcs = ["test/jaeger_recordable_test.cc"], + copts = ["-fexceptions"], + tags = [ + "jaeger", + "test", + ], + deps = [ + ":opentelemetry_exporter_jaeger_trace", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "jaeger_exporter_test", + srcs = ["test/jaeger_exporter_test.cc"], + copts = ["-fexceptions"], + defines = ["BAZEL_BUILD"], + tags = [ + "jaeger", + "test", + ], + deps = [ + ":opentelemetry_exporter_jaeger_trace", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/CMakeLists.txt new file mode 100644 index 000000000..a95c0cc94 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/CMakeLists.txt @@ -0,0 +1,95 @@ +include_directories(thrift-gen) + +find_package(Thrift REQUIRED) +# vcpkg config recipe points to THRIFT_INCLUDE_DIR=...\thrift . Ensure that the +# include dir for thrift-gen code is 1 level-up from that: +include_directories(SYSTEM ${THRIFT_INCLUDE_DIR}/..) + +set(JAEGER_THRIFT_GENCPP_SOURCES + thrift-gen/Agent.cpp thrift-gen/jaeger_types.cpp thrift-gen/Collector.cpp + thrift-gen/zipkincore_types.cpp) + +set(JAEGER_EXPORTER_SOURCES + src/jaeger_exporter.cc + src/thrift_sender.cc + src/udp_transport.cc + src/recordable.cc + src/TUDPTransport.cc + src/http_transport.cc + src/THttpTransport.cc) + +add_library(opentelemetry_exporter_jaeger_trace ${JAEGER_EXPORTER_SOURCES} + ${JAEGER_THRIFT_GENCPP_SOURCES}) + +set_target_properties(opentelemetry_exporter_jaeger_trace + PROPERTIES EXPORT_NAME jaeger_trace_exporter) + +target_include_directories( + opentelemetry_exporter_jaeger_trace + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + +target_link_libraries( + opentelemetry_exporter_jaeger_trace + PUBLIC opentelemetry_resources opentelemetry_http_client_curl + PRIVATE thrift::thrift) + +if(MSVC) + target_compile_definitions(opentelemetry_exporter_jaeger_trace + PUBLIC NOMINMAX) + if(NOT BUILD_SHARED_LIBS) + target_compile_definitions(opentelemetry_exporter_jaeger_trace + PUBLIC THRIFT_STATIC_DEFINE) + endif() +endif() + +install( + TARGETS opentelemetry_exporter_jaeger_trace + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/jaeger + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h" + PATTERN "recordable.h" EXCLUDE) + +if(BUILD_TESTING) + add_definitions(-DGTEST_LINKED_AS_SHARED_LIBRARY=1) + + add_executable(jaeger_recordable_test test/jaeger_recordable_test.cc) + target_link_libraries( + jaeger_recordable_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_jaeger_trace) + + gtest_add_tests( + TARGET jaeger_recordable_test + TEST_PREFIX exporter. + TEST_LIST jaeger_recordable_test) + + add_executable(jaeger_exporter_test test/jaeger_exporter_test.cc) + if(MSVC) + if(GMOCK_LIB) + unset(GMOCK_LIB CACHE) + endif() + endif() + if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug") + find_library(GMOCK_LIB gmockd PATH_SUFFIXES lib) + else() + find_library(GMOCK_LIB gmock PATH_SUFFIXES lib) + endif() + target_link_libraries( + jaeger_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + ${GMOCK_LIB} opentelemetry_trace opentelemetry_exporter_jaeger_trace) + + target_include_directories(jaeger_exporter_test + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src) + + gtest_add_tests( + TARGET jaeger_exporter_test + TEST_PREFIX exporter. + TEST_LIST jaeger_exporter_test) +endif() # BUILD_TESTING diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/README.md b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/README.md new file mode 100644 index 000000000..d4fa3b45b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/README.md @@ -0,0 +1,61 @@ +# Jaeger Exporter for OpenTelemetry C++ + +## Prerequisite + +* [Get Jaeger](https://www.jaegertracing.io/docs/getting-started/) and run + Jaeger agent. + +## Installation + +### CMake Installation Instructions + +Refer to install instructions +[INSTALL.md](../../INSTALL.md#building-as-standalone-cmake-project). Modify step +2 to create `cmake` build configuration for compiling with Jaeger exporter as +below: + +```console + $ cmake -DWITH_JAEGER=ON .. + -- The C compiler identification is GNU 9.3.0 + -- The CXX compiler identification is GNU 9.3.0 + ... + -- Configuring done + -- Generating done + -- Build files have been written to: /home/<user>/source/opentelemetry-cpp/build + $ +``` + +### Bazel install Instructions + +Refer to install instructions +[INSTALL.md](../../INSTALL.md#building-as-standalone-bazel-project). + +```console +bazel build //exporters/jaeger:opentelemetry_exporter_jaeger_trace +``` + +## Usage + +Install the exporter in your application, initialize and pass the `options` to it. + +```cpp +opentelemetry::exporter::jaeger::JaegerExporterOptions options; +options.server_addr = "localhost"; +options.server_port = 6831; +options.transport_format = opentelemetry::exporter::jaeger::TransportFormat::kThriftUdpCompact; + +auto exporter = std::unique_ptr<opentelemetry::sdk::trace::SpanExporter>( + new opentelemetry::exporter::jaeger::JaegerExporter(options)); +auto processor = std::shared_ptr<sdktrace::SpanProcessor>( + new sdktrace::SimpleSpanProcessor(std::move(exporter))); +auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>( + new sdktrace::TracerProvider(processor)); + +// Set the global trace provider +opentelemetry::trace::Provider::SetTracerProvider(provider); + +``` + +## Viewing your traces + +Please visit the Jaeger UI endpoint <http://localhost:16686>. diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/include/opentelemetry/exporters/jaeger/jaeger_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/include/opentelemetry/exporters/jaeger/jaeger_exporter.h new file mode 100644 index 000000000..284bab2ca --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/include/opentelemetry/exporters/jaeger/jaeger_exporter.h @@ -0,0 +1,93 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <opentelemetry/common/spin_lock_mutex.h> +#include <opentelemetry/ext/http/client/http_client.h> +#include <opentelemetry/sdk/trace/exporter.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ +enum class TransportFormat +{ + kThriftUdp, + kThriftUdpCompact, + kThriftHttp, + kProtobufGrpc, +}; + +class ThriftSender; + +/** + * Struct to hold Jaeger exporter options. + */ +struct JaegerExporterOptions +{ + TransportFormat transport_format = TransportFormat::kThriftUdpCompact; + std::string endpoint = "localhost"; + uint16_t server_port = 6831; + // Only applicable when using kThriftHttp transport. + ext::http::client::Headers headers; +}; + +class JaegerExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * Create a JaegerExporter using all default options. + */ + JaegerExporter(); + + /** + * Create a JaegerExporter using the given options. + */ + explicit JaegerExporter(const JaegerExporterOptions &options); + + /** + * Create a span recordable. + * @return a new initialized Recordable object. + */ + std::unique_ptr<opentelemetry::sdk::trace::Recordable> MakeRecordable() noexcept override; + + /** + * Export a batch of spans. + * @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; + + /** + * Shutdown the exporter. + * @param timeout an option timeout, default to max. + */ + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + +private: + void InitializeEndpoint(); + +private: + // The configuration options associated with this exporter. + bool is_shutdown_ = false; + JaegerExporterOptions options_; + std::unique_ptr<ThriftSender> sender_; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; + // For testing + friend class JaegerExporterTestPeer; + /** + * Create an JaegerExporter using the specified thrift sender. + * Only tests can call this constructor directly. + * @param sender the thrift sender to be used for exporting + */ + JaegerExporter(std::unique_ptr<ThriftSender> sender); +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/include/opentelemetry/exporters/jaeger/recordable.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/include/opentelemetry/exporters/jaeger/recordable.h new file mode 100644 index 000000000..98e1e6131 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/include/opentelemetry/exporters/jaeger/recordable.h @@ -0,0 +1,119 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <jaeger_types.h> +#include <opentelemetry/sdk/trace/recordable.h> +#include <opentelemetry/version.h> + +#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define JAEGER_IS_LITTLE_ENDIAN 1 +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define JAEGER_IS_LITTLE_ENDIAN 0 +#elif defined(_WIN32) +# define JAEGER_IS_LITTLE_ENDIAN 1 +#else +# error "Endian detection needs to be set up for your compiler" +#endif + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +#if JAEGER_IS_LITTLE_ENDIAN == 1 + +# if defined(__clang__) || \ + (defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5)) +inline uint64_t otel_bswap_64(uint64_t host_int) +{ + return __builtin_bswap64(host_int); +} + +# elif defined(_MSC_VER) +inline uint64_t otel_bswap_64(uint64_t host_int) +{ + return _byteswap_uint64(host_int); +} + +# else +# error "Port need to support endianess conversion" + +# endif + +#endif + +using namespace jaegertracing; + +class JaegerRecordable final : public sdk::trace::Recordable +{ +public: + JaegerRecordable(); + + thrift::Span *Span() noexcept { return span_.release(); } + std::vector<thrift::Tag> Tags() noexcept { return std::move(tags_); } + std::vector<thrift::Tag> ResourceTags() noexcept { return std::move(resource_tags_); } + std::vector<thrift::Log> Logs() noexcept { return std::move(logs_); } + std::vector<thrift::SpanRef> References() noexcept { return std::move(references_); } + const std::string &ServiceName() const noexcept { return service_name_; } + + 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 key, + 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 SetStartTime(common::SystemTimestamp start_time) noexcept override; + + void SetSpanKind(opentelemetry::trace::SpanKind span_kind) noexcept override; + + void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override; + + void SetDuration(std::chrono::nanoseconds duration) noexcept override; + + void SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept override; + +private: + void AddTag(const std::string &key, const std::string &value, std::vector<thrift::Tag> &tags); + void AddTag(const std::string &key, const char *value, std::vector<thrift::Tag> &tags); + void AddTag(const std::string &key, bool value, std::vector<thrift::Tag> &tags); + void AddTag(const std::string &key, int64_t value, std::vector<thrift::Tag> &tags); + void AddTag(const std::string &key, double value, std::vector<thrift::Tag> &tags); + + void PopulateAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value, + std::vector<thrift::Tag> &tags); + + void PopulateAttribute(nostd::string_view key, + const sdk::common::OwnedAttributeValue &value, + std::vector<thrift::Tag> &tags); + +private: + std::unique_ptr<thrift::Span> span_; + std::vector<thrift::Tag> tags_; + std::vector<thrift::Tag> resource_tags_; + std::vector<thrift::Log> logs_; + std::vector<thrift::SpanRef> references_; + std::string service_name_; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/THttpTransport.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/THttpTransport.cc new file mode 100644 index 000000000..cbb1b65cb --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/THttpTransport.cc @@ -0,0 +1,61 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "THttpTransport.h" +#include "opentelemetry/ext/http/client/http_client_factory.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +THttpTransport::THttpTransport(std::string endpoint, ext::http::client::Headers extra_headers) + : endpoint(std::move(endpoint)), + headers(std::move(extra_headers)), + client(ext::http::client::HttpClientFactory::CreateSync()) +{ + headers.insert({{"Content-Type", "application/vnd.apache.thrift.binary"}}); +} + +THttpTransport::~THttpTransport() {} + +bool THttpTransport::isOpen() const +{ + return true; +} + +uint32_t THttpTransport::read(uint8_t *buf, uint32_t len) +{ + (void)buf; + (void)len; + return 0; +} + +void THttpTransport::write(const uint8_t *buf, uint32_t len) +{ + request_buffer.insert(request_buffer.end(), buf, buf + len); +} + +bool THttpTransport::sendSpans() +{ + auto result = client->Post(endpoint, request_buffer, headers); + request_buffer.clear(); + + // TODO: Add logging once global log handling is available. + if (!result) + { + return false; + } + + if (result.GetResponse().GetStatusCode() >= 400) + { + return false; + } + + return true; +} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/THttpTransport.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/THttpTransport.h new file mode 100644 index 000000000..9c796e67a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/THttpTransport.h @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <opentelemetry/ext/http/client/http_client.h> +#include <opentelemetry/version.h> + +#include <thrift/transport/TVirtualTransport.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +class THttpTransport : public apache::thrift::transport::TVirtualTransport<THttpTransport> +{ +public: + THttpTransport(std::string endpoint, ext::http::client::Headers extra_headers); + ~THttpTransport() override; + + bool isOpen() const override; + + uint32_t read(uint8_t *buf, uint32_t len); + + void write(const uint8_t *buf, uint32_t len); + + bool sendSpans(); + +private: + std::string endpoint; + ext::http::client::Headers headers; + std::shared_ptr<ext::http::client::HttpClientSync> client; + std::vector<uint8_t> request_buffer; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/TUDPTransport.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/TUDPTransport.cc new file mode 100644 index 000000000..e41112739 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/TUDPTransport.cc @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <sstream> // std::stringstream + +#include "TUDPTransport.h" +#include "opentelemetry/sdk_config.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +TUDPTransport::TUDPTransport(const std::string &host, int port) + : host_(host), port_(port), socket_(THRIFT_INVALID_SOCKET) +{} + +TUDPTransport::~TUDPTransport() +{ + if (server_addr_info_) + { + freeaddrinfo(server_addr_info_); + server_addr_info_ = nullptr; + sockaddr_len = 0; + } + close(); +} + +bool TUDPTransport::isOpen() const +{ + return (socket_ != THRIFT_INVALID_SOCKET); +} + +void TUDPTransport::open() +{ + if (isOpen()) + { + return; + } + + struct addrinfo hints; + int error; + char port[sizeof("65535") + 1]; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + + sprintf(port, "%d", port_); + + error = getaddrinfo(host_.c_str(), port, &hints, &server_addr_info_); + + if (error) + { + OTEL_INTERNAL_LOG_ERROR("Jaeger Exporter: getaddrinfo failed with error: " << error); + return; + } + + socket_ = socket(server_addr_info_->ai_family, server_addr_info_->ai_socktype, + server_addr_info_->ai_protocol); + sockaddr_len = server_addr_info_->ai_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) + : sizeof(struct sockaddr_in6); +} + +void TUDPTransport::close() +{ + if (socket_ != THRIFT_INVALID_SOCKET) + { + ::THRIFT_CLOSESOCKET(socket_); + } + socket_ = THRIFT_INVALID_SOCKET; +} + +uint32_t TUDPTransport::read(uint8_t *buf, uint32_t len) +{ + if (!server_addr_info_) + { + return 0; + } + uint32_t num_read = recvfrom(socket_, +#if defined(_WIN32) + reinterpret_cast<char *>(buf), len, 0, server_addr_info_->ai_addr, + reinterpret_cast<int *>(&sockaddr_len) +#else + buf, len, 0, server_addr_info_->ai_addr, &sockaddr_len +#endif + ); + + return num_read; +} + +void TUDPTransport::write(const uint8_t *buf, uint32_t len) +{ + if (!server_addr_info_) + { + return; + } + sendto(socket_, +#if defined(_WIN32) + reinterpret_cast<const char *>(buf), +#else + buf, +#endif + len, 0, server_addr_info_->ai_addr, sockaddr_len); +} + +void TUDPTransport::flush() {} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/TUDPTransport.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/TUDPTransport.h new file mode 100644 index 000000000..df3151d11 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/TUDPTransport.h @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef _WIN32 +# include <winsock2.h> +#else +# include <netdb.h> +# include <string.h> +# include <sys/socket.h> +# include <sys/types.h> +#endif + +#include <opentelemetry/version.h> +#include <thrift/transport/PlatformSocket.h> +#include <thrift/transport/TVirtualTransport.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +class TUDPTransport : public apache::thrift::transport::TVirtualTransport<TUDPTransport> +{ +public: + TUDPTransport(const std::string &host, int port); + ~TUDPTransport() override; + + bool isOpen() const override; + + void open() override; + + void close() override; + + uint32_t read(uint8_t *buf, uint32_t len); + + void write(const uint8_t *buf, uint32_t len); + + void flush() override; + +private: + std::string host_; + int port_; + THRIFT_SOCKET socket_; + struct addrinfo *server_addr_info_ = nullptr; + uint32_t sockaddr_len = 0; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/http_transport.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/http_transport.cc new file mode 100644 index 000000000..f804ccc84 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/http_transport.cc @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "http_transport.h" + +#include <thrift/protocol/TBinaryProtocol.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using TBinaryProtocol = apache::thrift::protocol::TBinaryProtocol; +using TTransport = apache::thrift::transport::TTransport; + +HttpTransport::HttpTransport(std::string endpoint, ext::http::client::Headers headers) +{ + endpoint_transport_ = std::make_shared<THttpTransport>(std::move(endpoint), std::move(headers)); + protocol_ = std::shared_ptr<TProtocol>(new TBinaryProtocol(endpoint_transport_)); +} + +int HttpTransport::EmitBatch(const thrift::Batch &batch) +{ + batch.write(protocol_.get()); + + if (!endpoint_transport_->sendSpans()) + { + return 0; + } + + return static_cast<int>(batch.spans.size()); +} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/http_transport.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/http_transport.h new file mode 100644 index 000000000..8f5c9c057 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/http_transport.h @@ -0,0 +1,39 @@ +#pragma once + +#include "THttpTransport.h" +#include "transport.h" + +#include <thrift/protocol/TProtocol.h> +#include <thrift/transport/TTransport.h> +#include <memory> +#include <string> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using TProtocol = apache::thrift::protocol::TProtocol; + +class HttpTransport : public Transport +{ +public: + HttpTransport(std::string endpoint, ext::http::client::Headers headers); + + int EmitBatch(const thrift::Batch &batch) override; + + uint32_t MaxPacketSize() const override + { + // Default to 4 MiB POST body size. + return 1 << 22; + } + +private: + std::shared_ptr<THttpTransport> endpoint_transport_; + std::shared_ptr<TProtocol> protocol_; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/jaeger_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/jaeger_exporter.cc new file mode 100644 index 000000000..c07f2f010 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/jaeger_exporter.cc @@ -0,0 +1,111 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <agent_types.h> +#include <opentelemetry/exporters/jaeger/jaeger_exporter.h> +#include <opentelemetry/exporters/jaeger/recordable.h> +#include "opentelemetry/sdk_config.h" + +#include "http_transport.h" +#include "thrift_sender.h" +#include "udp_transport.h" + +#include <mutex> +#include <vector> + +namespace sdk_common = opentelemetry::sdk::common; +namespace trace_sdk = opentelemetry::sdk::trace; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +JaegerExporter::JaegerExporter(const JaegerExporterOptions &options) : options_(options) +{ + InitializeEndpoint(); +} + +JaegerExporter::JaegerExporter() : JaegerExporter(JaegerExporterOptions()) {} + +JaegerExporter::JaegerExporter(std::unique_ptr<ThriftSender> sender) + : options_(JaegerExporterOptions()), sender_(std::move(sender)) +{} + +std::unique_ptr<trace_sdk::Recordable> JaegerExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<sdk::trace::Recordable>(new JaegerRecordable); +} + +sdk_common::ExportResult JaegerExporter::Export( + const nostd::span<std::unique_ptr<sdk::trace::Recordable>> &spans) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[Jaeger Trace Exporter] Exporting " + << spans.size() << " span(s) failed, exporter is shutdown"); + return sdk_common::ExportResult::kFailure; + } + + std::size_t exported_size = 0; + + for (auto &recordable : spans) + { + auto rec = + std::unique_ptr<JaegerRecordable>(static_cast<JaegerRecordable *>(recordable.release())); + if (rec != nullptr) + { + exported_size += sender_->Append(std::move(rec)); + } + } + + exported_size += sender_->Flush(); + + if (exported_size == 0) + { + return sdk_common::ExportResult::kFailure; + } + + return sdk_common::ExportResult::kSuccess; +} + +void JaegerExporter::InitializeEndpoint() +{ + if (options_.transport_format == TransportFormat::kThriftUdpCompact) + { + // TODO: do we need support any authentication mechanism? + auto transport = std::unique_ptr<Transport>( + static_cast<Transport *>(new UDPTransport(options_.endpoint, options_.server_port))); + sender_ = std::unique_ptr<ThriftSender>(new ThriftSender(std::move(transport))); + return; + } + + if (options_.transport_format == TransportFormat::kThriftHttp) + { + auto transport = + std::unique_ptr<HttpTransport>(new HttpTransport(options_.endpoint, options_.headers)); + sender_ = std::unique_ptr<ThriftSender>(new ThriftSender(std::move(transport))); + return; + } + + // The transport format is not implemented. + assert(false); +} + +bool JaegerExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; +} + +bool JaegerExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/recordable.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/recordable.cc new file mode 100644 index 000000000..c4a61a5d2 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/recordable.cc @@ -0,0 +1,365 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/jaeger/recordable.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/resource/experimental_semantic_conventions.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using namespace opentelemetry::sdk::resource; +namespace trace_api = opentelemetry::trace; + +JaegerRecordable::JaegerRecordable() : span_{new thrift::Span} {} + +void JaegerRecordable::PopulateAttribute(nostd::string_view key, + const common::AttributeValue &value, + std::vector<thrift::Tag> &tags) +{ + if (nostd::holds_alternative<int32_t>(value)) + { + AddTag(std::string{key}, int64_t{nostd::get<int32_t>(value)}, tags); + } + else if (nostd::holds_alternative<uint32_t>(value)) + { + AddTag(std::string{key}, int64_t{nostd::get<uint32_t>(value)}, tags); + } + else if (nostd::holds_alternative<int64_t>(value)) + { + AddTag(std::string{key}, nostd::get<int64_t>(value), tags); + } + else if (nostd::holds_alternative<bool>(value)) + { + AddTag(std::string{key}, nostd::get<bool>(value), tags); + } + else if (nostd::holds_alternative<double>(value)) + { + AddTag(std::string{key}, nostd::get<double>(value), tags); + } + else if (nostd::holds_alternative<const char *>(value)) + { + AddTag(std::string{key}, std::string{nostd::get<const char *>(value)}, tags); + } + else if (nostd::holds_alternative<nostd::string_view>(value)) + { + AddTag(std::string{key}, std::string{nostd::get<nostd::string_view>(value)}, tags); + } + else if (nostd::holds_alternative<nostd::span<const bool>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const bool>>(value)) + { + AddTag(std::string{key}, val, tags); + } + } + else if (nostd::holds_alternative<nostd::span<const int32_t>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const int32_t>>(value)) + { + AddTag(std::string{key}, int64_t{val}, tags); + } + } + else if (nostd::holds_alternative<nostd::span<const int64_t>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const int64_t>>(value)) + { + AddTag(std::string{key}, val, tags); + } + } + else if (nostd::holds_alternative<nostd::span<const uint32_t>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const uint32_t>>(value)) + { + AddTag(std::string{key}, int64_t{val}, tags); + } + } + else if (nostd::holds_alternative<nostd::span<const double>>(value)) + { + for (const auto &val : nostd::get<nostd::span<const double>>(value)) + { + AddTag(std::string{key}, val, tags); + } + } + 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)) + { + AddTag(std::string{key}, std::string{val}, tags); + } + } + else + { + OTEL_INTERNAL_LOG_ERROR( + "[TRACE JAEGER Exporter] SetAttribute() failed, attribute type not supported "); + } +} + +void JaegerRecordable::PopulateAttribute(nostd::string_view key, + const sdk::common::OwnedAttributeValue &value, + std::vector<thrift::Tag> &tags) +{ + if (nostd::holds_alternative<int32_t>(value)) + { + AddTag(std::string{key}, int64_t{nostd::get<int32_t>(value)}, tags); + } + else if (nostd::holds_alternative<uint32_t>(value)) + { + AddTag(std::string{key}, int64_t{nostd::get<uint32_t>(value)}, tags); + } + else if (nostd::holds_alternative<int64_t>(value)) + { + AddTag(std::string{key}, nostd::get<int64_t>(value), tags); + } + else if (nostd::holds_alternative<bool>(value)) + { + AddTag(std::string{key}, nostd::get<bool>(value), tags); + } + else if (nostd::holds_alternative<double>(value)) + { + AddTag(std::string{key}, nostd::get<double>(value), tags); + } + else if (nostd::holds_alternative<std::string>(value)) + { + AddTag(std::string{key}, std::string{nostd::get<std::string>(value)}, tags); + } + else + { + OTEL_INTERNAL_LOG_ERROR( + "[TRACE JAEGER Exporter] SetAttribute() failed, attribute type not supported "); + } +} + +void JaegerRecordable::SetIdentity(const trace::SpanContext &span_context, + trace::SpanId parent_span_id) noexcept +{ + // IDs should be converted to big endian before transmission. + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#ids +#if JAEGER_IS_LITTLE_ENDIAN == 1 + span_->__set_traceIdHigh( + otel_bswap_64(*(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data())))); + span_->__set_traceIdLow( + otel_bswap_64(*(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data()) + 1))); + span_->__set_spanId( + otel_bswap_64(*(reinterpret_cast<const int64_t *>(span_context.span_id().Id().data())))); + span_->__set_parentSpanId( + otel_bswap_64(*(reinterpret_cast<const int64_t *>(parent_span_id.Id().data())))); +#else + span_->__set_traceIdLow( + *(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data()))); + span_->__set_traceIdHigh( + *(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data()) + 1)); + span_->__set_spanId(*(reinterpret_cast<const int64_t *>(span_context.span_id().Id().data()))); + span_->__set_parentSpanId(*(reinterpret_cast<const int64_t *>(parent_span_id.Id().data()))); +#endif + + // TODO: set trace_state. +} + +void JaegerRecordable::SetAttribute(nostd::string_view key, + const common::AttributeValue &value) noexcept +{ + PopulateAttribute(key, value, tags_); +} + +void JaegerRecordable::AddEvent(nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept +{ + std::vector<thrift::Tag> tags; + PopulateAttribute("event", static_cast<common::AttributeValue>(name.data()), tags); + + attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + PopulateAttribute(key, value, tags); + return true; + }); + thrift::Log log; + log.__set_fields(tags); + log.__set_timestamp( + std::chrono::duration_cast<std::chrono::microseconds>(timestamp.time_since_epoch()).count()); + logs_.push_back(log); +} + +void JaegerRecordable::SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept +{ + AddTag("otel.library.name", instrumentation_library.GetName(), tags_); + AddTag("otel.library.version", instrumentation_library.GetVersion(), tags_); +} + +void JaegerRecordable::AddLink(const trace::SpanContext &span_context, + const common::KeyValueIterable &attributes) noexcept +{ + // Note: "The Link’s attributes cannot be represented in Jaeger explicitly." + // -- https://opentelemetry.io/docs/reference/specification/trace/sdk_exporters/jaeger/#links + // + // This implementation does not (currently) implement the optional conversion to span logs. + + thrift::SpanRef reference; + + reference.__set_refType(thrift::SpanRefType::FOLLOWS_FROM); + + // IDs should be converted to big endian before transmission. + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#ids +#if JAEGER_IS_LITTLE_ENDIAN == 1 + reference.__set_traceIdHigh( + otel_bswap_64(*(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data())))); + reference.__set_traceIdLow( + otel_bswap_64(*(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data()) + 1))); + reference.__set_spanId( + otel_bswap_64(*(reinterpret_cast<const int64_t *>(span_context.span_id().Id().data())))); +#else + reference.__set_traceIdLow( + *(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data()))); + reference.__set_traceIdHigh( + *(reinterpret_cast<const int64_t *>(span_context.trace_id().Id().data()) + 1)); + reference.__set_spanId(*(reinterpret_cast<const int64_t *>(span_context.span_id().Id().data()))); +#endif + + references_.push_back(reference); +} + +void JaegerRecordable::SetStatus(trace::StatusCode code, nostd::string_view description) noexcept +{ + if (code == trace::StatusCode::kUnset) + { + return; + } + + if (code == trace::StatusCode::kOk) + { + AddTag("otel.status_code", "OK", tags_); + } + else if (code == trace::StatusCode::kError) + { + AddTag("otel.status_code", "ERROR", tags_); + AddTag("error", true, tags_); + } + + AddTag("otel.status_description", std::string{description}, tags_); +} + +void JaegerRecordable::SetName(nostd::string_view name) noexcept +{ + span_->__set_operationName(static_cast<std::string>(name)); +} + +void JaegerRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept +{ + for (const auto &attribute_iter : resource.GetAttributes()) + { + if (attribute_iter.first != "service.name") + { + PopulateAttribute(nostd::string_view{attribute_iter.first}, attribute_iter.second, + resource_tags_); + } + else + { + service_name_ = nostd::get<std::string>(attribute_iter.second); + } + } +} + +void JaegerRecordable::SetStartTime(common::SystemTimestamp start_time) noexcept +{ + span_->__set_startTime( + std::chrono::duration_cast<std::chrono::microseconds>(start_time.time_since_epoch()).count()); +} + +void JaegerRecordable::SetDuration(std::chrono::nanoseconds duration) noexcept +{ + span_->__set_duration(std::chrono::duration_cast<std::chrono::microseconds>(duration).count()); +} + +void JaegerRecordable::SetSpanKind(trace::SpanKind span_kind) noexcept +{ + const char *span_kind_str = nullptr; + + // map SpanKind to Jaeger tag span.kind. + switch (span_kind) + { + case trace_api::SpanKind::kClient: { + span_kind_str = "client"; + break; + } + case trace_api::SpanKind::kServer: { + span_kind_str = "server"; + break; + } + case trace_api::SpanKind::kConsumer: { + span_kind_str = "consumer"; + break; + } + case trace_api::SpanKind::kProducer: { + span_kind_str = "producer"; + break; + } + default: + break; + } + + if (span_kind_str != nullptr) + { + AddTag("span.kind", span_kind_str, tags_); + } +} + +void JaegerRecordable::AddTag(const std::string &key, + const std::string &value, + std::vector<thrift::Tag> &tags) +{ + thrift::Tag tag; + + tag.__set_key(key); + tag.__set_vType(thrift::TagType::STRING); + tag.__set_vStr(value); + + tags.push_back(tag); +} + +void JaegerRecordable::AddTag(const std::string &key, + const char *value, + std::vector<thrift::Tag> &tags) +{ + AddTag(key, std::string{value}, tags); +} + +void JaegerRecordable::AddTag(const std::string &key, bool value, std::vector<thrift::Tag> &tags) +{ + thrift::Tag tag; + + tag.__set_key(key); + tag.__set_vType(thrift::TagType::BOOL); + tag.__set_vBool(value); + + tags.push_back(tag); +} + +void JaegerRecordable::AddTag(const std::string &key, int64_t value, std::vector<thrift::Tag> &tags) +{ + thrift::Tag tag; + + tag.__set_key(key); + tag.__set_vType(thrift::TagType::LONG); + tag.__set_vLong(value); + + tags.push_back(tag); +} + +void JaegerRecordable::AddTag(const std::string &key, double value, std::vector<thrift::Tag> &tags) +{ + thrift::Tag tag; + + tag.__set_key(key); + tag.__set_vType(thrift::TagType::DOUBLE); + tag.__set_vDouble(value); + + tags.push_back(tag); +} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/sender.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/sender.h new file mode 100644 index 000000000..f4827b550 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/sender.h @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <opentelemetry/exporters/jaeger/recordable.h> +#include <opentelemetry/version.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using namespace jaegertracing; + +class Sender +{ +public: + Sender() = default; + virtual ~Sender() = default; + + virtual int Append(std::unique_ptr<JaegerRecordable> &&span) = 0; + + virtual int Flush() = 0; + + virtual void Close() = 0; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/thrift_sender.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/thrift_sender.cc new file mode 100644 index 000000000..46f0dcd5a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/thrift_sender.cc @@ -0,0 +1,99 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "thrift_sender.h" +#include <opentelemetry/exporters/jaeger/recordable.h> +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "udp_transport.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using namespace jaegertracing; + +ThriftSender::ThriftSender(std::unique_ptr<Transport> &&transport) + : transport_(std::move(transport)), + protocol_factory_(new apache::thrift::protocol::TCompactProtocolFactory()), + thrift_buffer_(new apache::thrift::transport::TMemoryBuffer(transport_->MaxPacketSize())) +{} + +int ThriftSender::Append(std::unique_ptr<JaegerRecordable> &&span) noexcept +{ + if (span == nullptr) + { + return 0; + } + + uint32_t max_span_bytes = transport_->MaxPacketSize() - kEmitBatchOverhead; + if (process_.serviceName.empty()) + { + process_.serviceName = span->ServiceName(); + process_.__set_tags(span->ResourceTags()); + + process_bytes_size_ = CalcSizeOfSerializedThrift(process_); + max_span_bytes -= process_bytes_size_; + } + + auto jaeger_span = std::unique_ptr<thrift::Span>(span->Span()); + jaeger_span->__set_tags(span->Tags()); + jaeger_span->__set_logs(span->Logs()); + jaeger_span->__set_references(span->References()); + + const uint32_t span_size = CalcSizeOfSerializedThrift(*jaeger_span); + if (span_size > max_span_bytes) + { + OTEL_INTERNAL_LOG_ERROR("[JAEGER TRACE Exporter] Append() failed: too large span"); + return 0; + } + + byte_buffer_size_ += span_size; + if (byte_buffer_size_ <= max_span_bytes) + { + span_buffer_.push_back(*jaeger_span); + if (byte_buffer_size_ < max_span_bytes) + { + return 0; + } + else + { + // byte buffer is full so flush it before appending new span. + return Flush(); + } + } + + const auto flushed = Flush(); + span_buffer_.push_back(*jaeger_span); + byte_buffer_size_ = span_size + process_bytes_size_; + + return flushed; +} + +int ThriftSender::Flush() +{ + if (span_buffer_.empty()) + { + return 0; + } + + thrift::Batch batch; + batch.__set_process(process_); + batch.__set_spans(span_buffer_); + + int spans_flushed = transport_->EmitBatch(batch); + + ResetBuffers(); + + return spans_flushed; +} + +void ThriftSender::Close() +{ + Flush(); +} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/thrift_sender.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/thrift_sender.h new file mode 100644 index 000000000..0ec8d47f1 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/thrift_sender.h @@ -0,0 +1,78 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <Agent.h> +#include <atomic> +#include <memory> +#include <mutex> +#include <vector> + +#include <thrift/protocol/TCompactProtocol.h> +#include <thrift/transport/TBufferTransports.h> + +#include "sender.h" +#include "transport.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using namespace jaegertracing; + +class ThriftSender : public Sender +{ +public: + static constexpr uint32_t kEmitBatchOverhead = 30; + + ThriftSender(std::unique_ptr<Transport> &&transport); + ~ThriftSender() override { Close(); } + + int Append(std::unique_ptr<JaegerRecordable> &&span) noexcept override; + int Flush() override; + void Close() override; + +private: + void ResetBuffers() + { + span_buffer_.clear(); + byte_buffer_size_ = process_bytes_size_; + } + + template <typename ThriftType> + uint32_t CalcSizeOfSerializedThrift(const ThriftType &base) + { + uint8_t *data = nullptr; + uint32_t size = 0; + + thrift_buffer_->resetBuffer(); + auto protocol = protocol_factory_->getProtocol(thrift_buffer_); + base.write(protocol.get()); + thrift_buffer_->getBuffer(&data, &size); + return size; + } + +private: + std::vector<std::unique_ptr<JaegerRecordable>> spans_; + std::vector<thrift::Span> span_buffer_; + std::unique_ptr<Transport> transport_; + std::unique_ptr<apache::thrift::protocol::TProtocolFactory> protocol_factory_; + std::shared_ptr<apache::thrift::transport::TMemoryBuffer> thrift_buffer_; + thrift::Process process_; + + // Size in bytes of the serialization buffer. + uint32_t byte_buffer_size_ = 0; + uint32_t process_bytes_size_ = 0; + uint32_t max_span_bytes_ = 0; + friend class MockThriftSender; + +protected: + ThriftSender() = default; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/transport.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/transport.h new file mode 100644 index 000000000..8121e3007 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/transport.h @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <opentelemetry/version.h> + +#include <jaeger_types.h> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using namespace jaegertracing; + +class Transport +{ +public: + Transport() = default; + virtual ~Transport() = default; + + virtual int EmitBatch(const thrift::Batch &batch) = 0; + virtual uint32_t MaxPacketSize() const = 0; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/udp_transport.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/udp_transport.cc new file mode 100644 index 000000000..9b1fe0caf --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/udp_transport.cc @@ -0,0 +1,86 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <sstream> // std::stringstream + +#include "opentelemetry/sdk_config.h" +#include "udp_transport.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +UDPTransport::UDPTransport(const std::string &addr, uint16_t port) + : max_packet_size_(kUDPPacketMaxLength) +{ + InitSocket(); + + endpoint_transport_ = std::shared_ptr<TTransport>(new TUDPTransport(addr, port)); + endpoint_transport_->open(); + transport_ = + std::shared_ptr<TTransport>(new TBufferedTransport(endpoint_transport_, max_packet_size_)); + protocol_ = std::shared_ptr<TProtocol>(new TCompactProtocol(transport_)); + agent_ = std::unique_ptr<AgentClient>(new AgentClient(protocol_)); +} + +UDPTransport::~UDPTransport() +{ + CleanSocket(); +} + +void UDPTransport::InitSocket() +{ +#if defined(_WIN32) + /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ + WORD wVersionRequested = MAKEWORD(2, 2); + + WSADATA wsaData; + int err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) + { + OTEL_INTERNAL_LOG_ERROR("Jaeger Exporter: WSAStartup failed with error: " << err); + return; + } + + /* Confirm that the WinSock DLL supports 2.2. */ + /* Note that if the DLL supports versions greater */ + /* than 2.2 in addition to 2.2, it will still return */ + /* 2.2 in wVersion since that is the version we */ + /* requested. */ + + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) + { + OTEL_INTERNAL_LOG_ERROR("Jaeger Exporter: winsock " << LOBYTE(wsaData.wVersion) << "." + << HIBYTE(wsaData.wVersion) + << " is not supported."); + WSACleanup(); + + return; + } +#endif +} + +void UDPTransport::CleanSocket() +{ +#if defined(_WIN32) + WSACleanup(); +#endif +} + +int UDPTransport::EmitBatch(const thrift::Batch &batch) +{ + try + { + agent_->emitBatch(batch); + } + catch (...) + {} + + return static_cast<int>(batch.spans.size()); +} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/udp_transport.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/udp_transport.h new file mode 100644 index 000000000..0997a27c6 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/src/udp_transport.h @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "TUDPTransport.h" +#include "transport.h" + +#include <Agent.h> +#include <thrift/protocol/TBinaryProtocol.h> +#include <thrift/protocol/TCompactProtocol.h> +#include <thrift/protocol/TProtocol.h> +#include <thrift/transport/TBufferTransports.h> +#include <thrift/transport/TSocket.h> +#include <thrift/transport/TTransport.h> +#include <memory> +#include <string> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +using AgentClient = jaegertracing::agent::thrift::AgentClient; +using TBinaryProtocol = apache::thrift::protocol::TBinaryProtocol; +using TCompactProtocol = apache::thrift::protocol::TCompactProtocol; +using TBufferedTransport = apache::thrift::transport::TBufferedTransport; +using TProtocol = apache::thrift::protocol::TProtocol; +using TTransport = apache::thrift::transport::TTransport; + +class UDPTransport : public Transport +{ +public: + static constexpr auto kUDPPacketMaxLength = 65000; + + UDPTransport(const std::string &addr, uint16_t port); + virtual ~UDPTransport(); + + int EmitBatch(const thrift::Batch &batch) override; + + uint32_t MaxPacketSize() const override { return max_packet_size_; } + + void InitSocket(); + void CleanSocket(); + +private: + std::unique_ptr<AgentClient> agent_; + std::shared_ptr<TTransport> endpoint_transport_; + std::shared_ptr<TTransport> transport_; + std::shared_ptr<TProtocol> protocol_; + uint32_t max_packet_size_; +}; + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/test/jaeger_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/test/jaeger_exporter_test.cc new file mode 100644 index 000000000..7f86f877b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/test/jaeger_exporter_test.cc @@ -0,0 +1,175 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <opentelemetry/exporters/jaeger/jaeger_exporter.h> +#include <memory> +#include <vector> +#include "opentelemetry/sdk/trace/batch_span_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" + +#ifdef BAZEL_BUILD +# include "exporters/jaeger/src/thrift_sender.h" +#else +# include "thrift_sender.h" +#endif + +#include <gtest/gtest.h> +#include "gmock/gmock.h" + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; +namespace common = opentelemetry::common; +namespace sdk_common = opentelemetry::sdk::common; + +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace jaeger +{ + +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); +} + +class JaegerExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr<sdk::trace::SpanExporter> GetExporter(std::unique_ptr<ThriftSender> sender) + { + return std::unique_ptr<sdk::trace::SpanExporter>(new JaegerExporter(std::move(sender))); + } + + // Get the options associated with the given exporter. + const JaegerExporterOptions &GetOptions(std::unique_ptr<JaegerExporter> &exporter) + { + return exporter->options_; + } +}; + +class MockThriftSender : public ThriftSender +{ +public: + MOCK_METHOD(int, Append, (std::unique_ptr<JaegerRecordable> &&), (noexcept, override)); +}; + +class MockTransport : public Transport +{ +public: + MOCK_METHOD(int, EmitBatch, (const thrift::Batch &), (override)); + MOCK_METHOD(uint32_t, MaxPacketSize, (), (const, override)); +}; + +// Create spans, let processor call Export() +TEST_F(JaegerExporterTestPeer, ExportIntegrationTest) +{ + auto mock_transport = new MockTransport; + auto mock_thrift_sender = new ThriftSender(std::unique_ptr<MockTransport>{mock_transport}); + auto exporter = GetExporter(std::unique_ptr<ThriftSender>{mock_thrift_sender}); + + 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["double_value"] = static_cast<double>(3.1); + 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)); + + EXPECT_CALL(*mock_transport, EmitBatch(_)).Times(Exactly(1)).WillOnce(Return(1)); + + auto tracer = provider->GetTracer("test"); + auto parent_span = tracer->StartSpan("Test parent span"); + + trace_api::StartSpanOptions child_span_opts = {}; + child_span_opts.parent = parent_span->GetContext(); + auto child_span = tracer->StartSpan("Test child span", child_span_opts); + + child_span->End(); + parent_span->End(); + + auto parent_ctx = parent_span->GetContext(); + auto child_ctx = child_span->GetContext(); + EXPECT_EQ(parent_ctx.trace_id(), child_ctx.trace_id()); + EXPECT_EQ(parent_ctx.trace_state(), child_ctx.trace_state()); + ASSERT_TRUE(parent_ctx.IsValid()); + ASSERT_TRUE(child_ctx.IsValid()); +} + +TEST_F(JaegerExporterTestPeer, ShutdownTest) +{ + auto mock_thrift_sender = new MockThriftSender; + auto exporter = GetExporter(std::unique_ptr<ThriftSender>{mock_thrift_sender}); + + 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_thrift_sender, Append(_)).Times(Exactly(1)).WillOnce(Return(1)); + 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(JaegerExporterTestPeer, ExportTest) +{ + auto mock_thrift_sender = new MockThriftSender; + auto exporter = GetExporter(std::unique_ptr<ThriftSender>{mock_thrift_sender}); + + auto recordable_1 = exporter->MakeRecordable(); + recordable_1->SetName("Test span 1"); + auto recordable_2 = exporter->MakeRecordable(); + recordable_2->SetName("Test span 2"); + + // Test successful send + nostd::span<std::unique_ptr<sdk::trace::Recordable>> batch_1(&recordable_1, 1); + EXPECT_CALL(*mock_thrift_sender, Append(_)).Times(Exactly(1)).WillOnce(Return(1)); + auto result = exporter->Export(batch_1); + EXPECT_EQ(sdk_common::ExportResult::kSuccess, result); + + // Test failed send + nostd::span<std::unique_ptr<sdk::trace::Recordable>> batch_2(&recordable_2, 1); + EXPECT_CALL(*mock_thrift_sender, Append(_)).Times(Exactly(1)).WillOnce(Return(0)); + result = exporter->Export(batch_2); + EXPECT_EQ(sdk::common::ExportResult::kFailure, result); +} + +// Test exporter configuration options +TEST_F(JaegerExporterTestPeer, ConfigTest) +{ + JaegerExporterOptions opts; + opts.endpoint = "localhost"; + opts.server_port = 6851; + std::unique_ptr<JaegerExporter> exporter(new JaegerExporter(opts)); + EXPECT_EQ(GetOptions(exporter).endpoint, "localhost"); + EXPECT_EQ(GetOptions(exporter).server_port, 6851); +} + +} // namespace jaeger +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/test/jaeger_recordable_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/test/jaeger_recordable_test.cc new file mode 100644 index 000000000..4a0a1f74b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/test/jaeger_recordable_test.cc @@ -0,0 +1,334 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <vector> +#include "opentelemetry/exporters/jaeger/recordable.h" +#include "opentelemetry/sdk/instrumentationlibrary/instrumentation_library.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +#include <gtest/gtest.h> + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; +namespace common = opentelemetry::common; + +using namespace jaegertracing; +using namespace opentelemetry::exporter::jaeger; +using namespace opentelemetry::sdk::instrumentationlibrary; +using std::vector; + +using Attributes = std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>>; + +TEST(JaegerSpanRecordable, SetIdentity) +{ + JaegerRecordable rec; + + int64_t trace_id_val[2] = {0x0000000000000000, 0x1000000000000000}; + int64_t span_id_val = 0x2000000000000000; + int64_t parent_span_id_val = 0x3000000000000000; + + const trace::TraceId trace_id{ + nostd::span<uint8_t, 16>(reinterpret_cast<uint8_t *>(trace_id_val), 16)}; + + const trace::SpanId span_id( + nostd::span<uint8_t, 8>(reinterpret_cast<uint8_t *>(&span_id_val), 8)); + + const trace::SpanId parent_span_id( + nostd::span<uint8_t, 8>(reinterpret_cast<uint8_t *>(&parent_span_id_val), 8)); + + const trace::SpanContext span_context{trace_id, span_id, + trace::TraceFlags{trace::TraceFlags::kIsSampled}, true}; + rec.SetIdentity(span_context, parent_span_id); + + std::unique_ptr<thrift::Span> span{rec.Span()}; + +#if JAEGER_IS_LITTLE_ENDIAN == 1 + EXPECT_EQ(span->traceIdLow, otel_bswap_64(trace_id_val[1])); + EXPECT_EQ(span->traceIdHigh, otel_bswap_64(trace_id_val[0])); + EXPECT_EQ(span->spanId, otel_bswap_64(span_id_val)); + EXPECT_EQ(span->parentSpanId, otel_bswap_64(parent_span_id_val)); +#else + EXPECT_EQ(span->traceIdLow, trace_id_val[0]); + EXPECT_EQ(span->traceIdHigh, trace_id_val[1]); + EXPECT_EQ(span->spanId, span_id_val); + EXPECT_EQ(span->parentSpanId, parent_span_id_val); +#endif +} + +TEST(JaegerSpanRecordable, SetName) +{ + JaegerRecordable rec; + + nostd::string_view name = "Test Span"; + rec.SetName(name); + + std::unique_ptr<thrift::Span> span{rec.Span()}; + + EXPECT_EQ(span->operationName, name); +} + +TEST(JaegerSpanRecordable, SetStartTime) +{ + JaegerRecordable 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::microseconds>(start_time.time_since_epoch()).count(); + rec.SetStartTime(start_timestamp); + + std::unique_ptr<thrift::Span> span{rec.Span()}; + + EXPECT_EQ(span->startTime, unix_start); +} + +TEST(JaegerSpanRecordable, SetDuration) +{ + JaegerRecordable rec; + + common::SystemTimestamp start_timestamp; + + std::chrono::microseconds duration(10); + uint64_t unix_end = duration.count(); + + rec.SetStartTime(start_timestamp); + rec.SetDuration(duration); + + std::unique_ptr<thrift::Span> span{rec.Span()}; + + EXPECT_EQ(span->startTime, 0); + EXPECT_EQ(span->duration, unix_end); +} + +TEST(JaegerSpanRecordable, SetStatus) +{ + JaegerRecordable rec; + + const char *error_description = "Error test"; + rec.SetStatus(trace::StatusCode::kError, error_description); + + auto tags = rec.Tags(); + EXPECT_EQ(tags.size(), 3); + + EXPECT_EQ(tags[0].key, "otel.status_code"); + EXPECT_EQ(tags[0].vType, thrift::TagType::STRING); + EXPECT_EQ(tags[0].vStr, "ERROR"); + + EXPECT_EQ(tags[1].key, "error"); + EXPECT_EQ(tags[1].vType, thrift::TagType::BOOL); + EXPECT_EQ(tags[1].vBool, true); + + EXPECT_EQ(tags[2].key, "otel.status_description"); + EXPECT_EQ(tags[2].vType, thrift::TagType::STRING); + EXPECT_EQ(tags[2].vStr, error_description); +} + +TEST(JaegerSpanRecordable, AddEvent) +{ + JaegerRecordable rec; + + std::chrono::system_clock::time_point event_time = std::chrono::system_clock::now(); + common::SystemTimestamp event_timestamp(event_time); + uint64_t epoch_us = + std::chrono::duration_cast<std::chrono::microseconds>(event_time.time_since_epoch()).count(); + + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int64_t values[kNumAttributes] = {4, 7, 23}; + std::map<std::string, int64_t> attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + rec.AddEvent("Test Event", event_timestamp, + common::KeyValueIterableView<std::map<std::string, int64_t>>(attributes)); + thrift::Log log = rec.Logs().at(0); + EXPECT_EQ(log.timestamp, epoch_us); + auto tags = log.fields; + size_t index = 0; + EXPECT_EQ(tags[index].key, "event"); + EXPECT_EQ(tags[index++].vStr, "Test Event"); + while (index <= kNumAttributes) + { + EXPECT_EQ(tags[index].key, keys[index - 1]); + EXPECT_EQ(tags[index].vLong, values[index - 1]); + index++; + } +} + +template <typename value_type> +void addTag(thrift::TagType::type tag_type, + const std::string &key, + value_type value, + vector<thrift::Tag> &tags) +{ + thrift::Tag tag; + + tag.__set_key(key); + tag.__set_vType(tag_type); + if (tag_type == thrift::TagType::LONG) + { + tag.__set_vLong(static_cast<int64_t>(value)); + } + else if (tag_type == thrift::TagType::DOUBLE) + { + tag.__set_vDouble(static_cast<double>(value)); + } + else if (tag_type == thrift::TagType::BOOL) + { + tag.__set_vBool(static_cast<bool>(value)); + } + + tags.push_back(tag); +} + +void addTag(const std::string &key, std::string value, vector<thrift::Tag> &tags) +{ + thrift::Tag tag; + + tag.__set_key(key); + tag.__set_vType(thrift::TagType::STRING); + tag.__set_vStr(value); + + tags.push_back(tag); +} + +TEST(JaegerSpanRecordable, SetAttributes) +{ + JaegerRecordable rec; + std::string string_val{"string_val"}; + vector<common::AttributeValue> values{ + bool{false}, + int32_t{-32}, + int64_t{-64}, + uint32_t{32}, + double{3.14}, + string_val.c_str(), + nostd::string_view{"string_view"}, + }; + for (const auto &val : values) + { + rec.SetAttribute("key1", val); + } + rec.SetAttribute("key2", nostd::span<const bool>{{false, true}}); + rec.SetAttribute("key3", nostd::span<const int32_t>{{-320, 320}}); + rec.SetAttribute("key4", nostd::span<const int64_t>{{-640, 640}}); + rec.SetAttribute("key5", nostd::span<const uint32_t>{{320, 322}}); + rec.SetAttribute("key6", nostd::span<const double>{{4.15, 5.15}}); + rec.SetAttribute("key7", nostd::span<const nostd::string_view>{{"string_v1", "string_v2"}}); + + auto tags = rec.Tags(); + EXPECT_EQ(tags.size(), values.size() + 12); + + vector<thrift::Tag> expected_tags; + addTag(thrift::TagType::BOOL, "key1", bool{false}, expected_tags); + addTag(thrift::TagType::LONG, "key1", int32_t{-32}, expected_tags); + addTag(thrift::TagType::LONG, "key1", int64_t{-64}, expected_tags); + addTag(thrift::TagType::LONG, "key1", int32_t{32}, expected_tags); + addTag(thrift::TagType::DOUBLE, "key1", double{3.14}, expected_tags); + addTag("key1", string_val, expected_tags); + addTag("key1", std::string{"string_view"}, expected_tags); + + addTag(thrift::TagType::BOOL, "key2", bool{false}, expected_tags); + addTag(thrift::TagType::BOOL, "key2", bool{true}, expected_tags); + addTag(thrift::TagType::LONG, "key3", int32_t{-320}, expected_tags); + addTag(thrift::TagType::LONG, "key3", int32_t{320}, expected_tags); + addTag(thrift::TagType::LONG, "key4", int64_t{-640}, expected_tags); + addTag(thrift::TagType::LONG, "key4", int64_t{640}, expected_tags); + addTag(thrift::TagType::LONG, "key5", uint32_t{320}, expected_tags); + addTag(thrift::TagType::LONG, "key5", uint32_t{322}, expected_tags); + addTag(thrift::TagType::DOUBLE, "key6", double{4.15}, expected_tags); + addTag(thrift::TagType::DOUBLE, "key6", double{5.15}, expected_tags); + addTag("key7", std::string{"string_v1"}, expected_tags); + addTag("key7", std::string{"string_v2"}, expected_tags); + + EXPECT_EQ(tags, expected_tags); +} + +TEST(JaegerSpanRecordable, SetInstrumentationLibrary) +{ + JaegerRecordable rec; + + std::string library_name = "opentelemetry-cpp"; + std::string library_version = "0.1.0"; + auto instrumentation_library = InstrumentationLibrary::Create(library_name, library_version); + + rec.SetInstrumentationLibrary(*instrumentation_library); + + auto tags = rec.Tags(); + EXPECT_EQ(tags.size(), 2); + + EXPECT_EQ(tags[0].key, "otel.library.name"); + EXPECT_EQ(tags[0].vType, thrift::TagType::STRING); + EXPECT_EQ(tags[0].vStr, library_name); + + EXPECT_EQ(tags[1].key, "otel.library.version"); + EXPECT_EQ(tags[1].vType, thrift::TagType::STRING); + EXPECT_EQ(tags[1].vStr, library_version); +} + +TEST(JaegerSpanRecordable, SetResource) +{ + JaegerRecordable rec; + + const std::string service_name_key = "service.name"; + std::string service_name_value = "test-jaeger-service-name"; + auto resource = opentelemetry::sdk::resource::Resource::Create( + {{service_name_key, service_name_value}, {"key1", "value1"}, {"key2", "value2"}}); + rec.SetResource(resource); + + auto service_name = rec.ServiceName(); + auto resource_tags = rec.ResourceTags(); + + EXPECT_GE(resource_tags.size(), 2); + EXPECT_EQ(service_name, service_name_value); + + for (const auto &tag : resource_tags) + { + if (tag.key == "key1") + { + EXPECT_EQ(tag.vType, thrift::TagType::STRING); + EXPECT_EQ(tag.vStr, "value1"); + } + else if (tag.key == "key2") + { + EXPECT_EQ(tag.vType, thrift::TagType::STRING); + EXPECT_EQ(tag.vStr, "value2"); + } + } +} + +TEST(JaegerSpanRecordable, AddLink) +{ + JaegerRecordable rec; + + int64_t trace_id_val[2] = {0x0000000000000000, 0x1000000000000000}; + int64_t span_id_val = 0x2000000000000000; + + const trace::TraceId trace_id{ + nostd::span<uint8_t, 16>(reinterpret_cast<uint8_t *>(trace_id_val), 16)}; + + const trace::SpanId span_id( + nostd::span<uint8_t, 8>(reinterpret_cast<uint8_t *>(&span_id_val), 8)); + + const trace::SpanContext span_context{trace_id, span_id, + trace::TraceFlags{trace::TraceFlags::kIsSampled}, true}; + rec.AddLink(span_context, common::KeyValueIterableView<Attributes>({{"attr1", "string"}})); + + auto references = rec.References(); + EXPECT_EQ(references.size(), 1); + + auto reference = references.front(); + +#if JAEGER_IS_LITTLE_ENDIAN == 1 + EXPECT_EQ(reference.traceIdLow, otel_bswap_64(trace_id_val[1])); + EXPECT_EQ(reference.traceIdHigh, otel_bswap_64(trace_id_val[0])); + EXPECT_EQ(reference.spanId, otel_bswap_64(span_id_val)); +#else + EXPECT_EQ(reference.traceIdLow, trace_id_val[0]); + EXPECT_EQ(reference.traceIdHigh, trace_id_val[1]); + EXPECT_EQ(reference.spanId, span_id_val); +#endif +} diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent.cpp new file mode 100644 index 000000000..4ff023650 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent.cpp @@ -0,0 +1,380 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#include "Agent.h" + +namespace jaegertracing { namespace agent { namespace thrift { + + +Agent_emitZipkinBatch_args::~Agent_emitZipkinBatch_args() noexcept { +} + + +uint32_t Agent_emitZipkinBatch_args::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->spans.clear(); + uint32_t _size0; + ::apache::thrift::protocol::TType _etype3; + xfer += iprot->readListBegin(_etype3, _size0); + this->spans.resize(_size0); + uint32_t _i4; + for (_i4 = 0; _i4 < _size0; ++_i4) + { + xfer += this->spans[_i4].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.spans = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t Agent_emitZipkinBatch_args::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Agent_emitZipkinBatch_args"); + + xfer += oprot->writeFieldBegin("spans", ::apache::thrift::protocol::T_LIST, 1); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->spans.size())); + std::vector< ::twitter::zipkin::thrift::Span> ::const_iterator _iter5; + for (_iter5 = this->spans.begin(); _iter5 != this->spans.end(); ++_iter5) + { + xfer += (*_iter5).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +Agent_emitZipkinBatch_pargs::~Agent_emitZipkinBatch_pargs() noexcept { +} + + +uint32_t Agent_emitZipkinBatch_pargs::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Agent_emitZipkinBatch_pargs"); + + xfer += oprot->writeFieldBegin("spans", ::apache::thrift::protocol::T_LIST, 1); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>((*(this->spans)).size())); + std::vector< ::twitter::zipkin::thrift::Span> ::const_iterator _iter6; + for (_iter6 = (*(this->spans)).begin(); _iter6 != (*(this->spans)).end(); ++_iter6) + { + xfer += (*_iter6).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +Agent_emitBatch_args::~Agent_emitBatch_args() noexcept { +} + + +uint32_t Agent_emitBatch_args::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_STRUCT) { + xfer += this->batch.read(iprot); + this->__isset.batch = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t Agent_emitBatch_args::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Agent_emitBatch_args"); + + xfer += oprot->writeFieldBegin("batch", ::apache::thrift::protocol::T_STRUCT, 1); + xfer += this->batch.write(oprot); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +Agent_emitBatch_pargs::~Agent_emitBatch_pargs() noexcept { +} + + +uint32_t Agent_emitBatch_pargs::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Agent_emitBatch_pargs"); + + xfer += oprot->writeFieldBegin("batch", ::apache::thrift::protocol::T_STRUCT, 1); + xfer += (*(this->batch)).write(oprot); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void AgentClient::emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans) +{ + send_emitZipkinBatch(spans); +} + +void AgentClient::send_emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans) +{ + int32_t cseqid = 0; + oprot_->writeMessageBegin("emitZipkinBatch", ::apache::thrift::protocol::T_ONEWAY, cseqid); + + Agent_emitZipkinBatch_pargs args; + args.spans = &spans; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); +} + +void AgentClient::emitBatch(const ::jaegertracing::thrift::Batch& batch) +{ + send_emitBatch(batch); +} + +void AgentClient::send_emitBatch(const ::jaegertracing::thrift::Batch& batch) +{ + int32_t cseqid = 0; + oprot_->writeMessageBegin("emitBatch", ::apache::thrift::protocol::T_ONEWAY, cseqid); + + Agent_emitBatch_pargs args; + args.batch = &batch; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); +} + +bool AgentProcessor::dispatchCall(::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, const std::string& fname, int32_t seqid, void* callContext) { + ProcessMap::iterator pfn; + pfn = processMap_.find(fname); + if (pfn == processMap_.end()) { + iprot->skip(::apache::thrift::protocol::T_STRUCT); + iprot->readMessageEnd(); + iprot->getTransport()->readEnd(); + ::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::UNKNOWN_METHOD, "Invalid method name: '"+fname+"'"); + oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid); + x.write(oprot); + oprot->writeMessageEnd(); + oprot->getTransport()->writeEnd(); + oprot->getTransport()->flush(); + return true; + } + (this->*(pfn->second))(seqid, iprot, oprot, callContext); + return true; +} + +void AgentProcessor::process_emitZipkinBatch(int32_t, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol*, void* callContext) +{ + void* ctx = nullptr; + if (this->eventHandler_.get() != nullptr) { + ctx = this->eventHandler_->getContext("Agent.emitZipkinBatch", callContext); + } + ::apache::thrift::TProcessorContextFreer freer(this->eventHandler_.get(), ctx, "Agent.emitZipkinBatch"); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->preRead(ctx, "Agent.emitZipkinBatch"); + } + + Agent_emitZipkinBatch_args args; + args.read(iprot); + iprot->readMessageEnd(); + uint32_t bytes = iprot->getTransport()->readEnd(); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->postRead(ctx, "Agent.emitZipkinBatch", bytes); + } + + try { + iface_->emitZipkinBatch(args.spans); + } catch (const std::exception&) { + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->handlerError(ctx, "Agent.emitZipkinBatch"); + } + return; + } + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->asyncComplete(ctx, "Agent.emitZipkinBatch"); + } + + return; +} + +void AgentProcessor::process_emitBatch(int32_t, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol*, void* callContext) +{ + void* ctx = nullptr; + if (this->eventHandler_.get() != nullptr) { + ctx = this->eventHandler_->getContext("Agent.emitBatch", callContext); + } + ::apache::thrift::TProcessorContextFreer freer(this->eventHandler_.get(), ctx, "Agent.emitBatch"); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->preRead(ctx, "Agent.emitBatch"); + } + + Agent_emitBatch_args args; + args.read(iprot); + iprot->readMessageEnd(); + uint32_t bytes = iprot->getTransport()->readEnd(); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->postRead(ctx, "Agent.emitBatch", bytes); + } + + try { + iface_->emitBatch(args.batch); + } catch (const std::exception&) { + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->handlerError(ctx, "Agent.emitBatch"); + } + return; + } + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->asyncComplete(ctx, "Agent.emitBatch"); + } + + return; +} + +::std::shared_ptr< ::apache::thrift::TProcessor > AgentProcessorFactory::getProcessor(const ::apache::thrift::TConnectionInfo& connInfo) { + ::apache::thrift::ReleaseHandler< AgentIfFactory > cleanup(handlerFactory_); + ::std::shared_ptr< AgentIf > handler(handlerFactory_->getHandler(connInfo), cleanup); + ::std::shared_ptr< ::apache::thrift::TProcessor > processor(new AgentProcessor(handler)); + return processor; +} + +void AgentConcurrentClient::emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans) +{ + send_emitZipkinBatch(spans); +} + +void AgentConcurrentClient::send_emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans) +{ + int32_t cseqid = 0; + ::apache::thrift::async::TConcurrentSendSentry sentry(this->sync_.get()); + oprot_->writeMessageBegin("emitZipkinBatch", ::apache::thrift::protocol::T_ONEWAY, cseqid); + + Agent_emitZipkinBatch_pargs args; + args.spans = &spans; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); + + sentry.commit(); +} + +void AgentConcurrentClient::emitBatch(const ::jaegertracing::thrift::Batch& batch) +{ + send_emitBatch(batch); +} + +void AgentConcurrentClient::send_emitBatch(const ::jaegertracing::thrift::Batch& batch) +{ + int32_t cseqid = 0; + ::apache::thrift::async::TConcurrentSendSentry sentry(this->sync_.get()); + oprot_->writeMessageBegin("emitBatch", ::apache::thrift::protocol::T_ONEWAY, cseqid); + + Agent_emitBatch_pargs args; + args.batch = &batch; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); + + sentry.commit(); +} + +}}} // namespace + diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent.h new file mode 100644 index 000000000..49abaf54a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent.h @@ -0,0 +1,309 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#ifndef Agent_H +#define Agent_H + +#include <thrift/TDispatchProcessor.h> +#include <thrift/async/TConcurrentClientSyncInfo.h> +#include <memory> +#include "agent_types.h" + +namespace jaegertracing { namespace agent { namespace thrift { + +#ifdef _MSC_VER + #pragma warning( push ) + #pragma warning (disable : 4250 ) //inheriting methods via dominance +#endif + +class AgentIf { + public: + virtual ~AgentIf() {} + virtual void emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans) = 0; + virtual void emitBatch(const ::jaegertracing::thrift::Batch& batch) = 0; +}; + +class AgentIfFactory { + public: + typedef AgentIf Handler; + + virtual ~AgentIfFactory() {} + + virtual AgentIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) = 0; + virtual void releaseHandler(AgentIf* /* handler */) = 0; +}; + +class AgentIfSingletonFactory : virtual public AgentIfFactory { + public: + AgentIfSingletonFactory(const ::std::shared_ptr<AgentIf>& iface) : iface_(iface) {} + virtual ~AgentIfSingletonFactory() {} + + virtual AgentIf* getHandler(const ::apache::thrift::TConnectionInfo&) { + return iface_.get(); + } + virtual void releaseHandler(AgentIf* /* handler */) {} + + protected: + ::std::shared_ptr<AgentIf> iface_; +}; + +class AgentNull : virtual public AgentIf { + public: + virtual ~AgentNull() {} + void emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & /* spans */) { + return; + } + void emitBatch(const ::jaegertracing::thrift::Batch& /* batch */) { + return; + } +}; + +typedef struct _Agent_emitZipkinBatch_args__isset { + _Agent_emitZipkinBatch_args__isset() : spans(false) {} + bool spans :1; +} _Agent_emitZipkinBatch_args__isset; + +class Agent_emitZipkinBatch_args { + public: + + Agent_emitZipkinBatch_args(const Agent_emitZipkinBatch_args&); + Agent_emitZipkinBatch_args& operator=(const Agent_emitZipkinBatch_args&); + Agent_emitZipkinBatch_args() { + } + + virtual ~Agent_emitZipkinBatch_args() noexcept; + std::vector< ::twitter::zipkin::thrift::Span> spans; + + _Agent_emitZipkinBatch_args__isset __isset; + + void __set_spans(const std::vector< ::twitter::zipkin::thrift::Span> & val); + + bool operator == (const Agent_emitZipkinBatch_args & rhs) const + { + if (!(spans == rhs.spans)) + return false; + return true; + } + bool operator != (const Agent_emitZipkinBatch_args &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Agent_emitZipkinBatch_args & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + + +class Agent_emitZipkinBatch_pargs { + public: + + + virtual ~Agent_emitZipkinBatch_pargs() noexcept; + const std::vector< ::twitter::zipkin::thrift::Span> * spans; + + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + +typedef struct _Agent_emitBatch_args__isset { + _Agent_emitBatch_args__isset() : batch(false) {} + bool batch :1; +} _Agent_emitBatch_args__isset; + +class Agent_emitBatch_args { + public: + + Agent_emitBatch_args(const Agent_emitBatch_args&); + Agent_emitBatch_args& operator=(const Agent_emitBatch_args&); + Agent_emitBatch_args() { + } + + virtual ~Agent_emitBatch_args() noexcept; + ::jaegertracing::thrift::Batch batch; + + _Agent_emitBatch_args__isset __isset; + + void __set_batch(const ::jaegertracing::thrift::Batch& val); + + bool operator == (const Agent_emitBatch_args & rhs) const + { + if (!(batch == rhs.batch)) + return false; + return true; + } + bool operator != (const Agent_emitBatch_args &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Agent_emitBatch_args & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + + +class Agent_emitBatch_pargs { + public: + + + virtual ~Agent_emitBatch_pargs() noexcept; + const ::jaegertracing::thrift::Batch* batch; + + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + +class AgentClient : virtual public AgentIf { + public: + AgentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot); + } + AgentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + setProtocol(iprot,oprot); + } + private: + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot,prot); + } + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + piprot_=iprot; + poprot_=oprot; + iprot_ = iprot.get(); + oprot_ = oprot.get(); + } + public: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() { + return piprot_; + } + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() { + return poprot_; + } + void emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans); + void send_emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans); + void emitBatch(const ::jaegertracing::thrift::Batch& batch); + void send_emitBatch(const ::jaegertracing::thrift::Batch& batch); + protected: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot_; + std::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot_; + ::apache::thrift::protocol::TProtocol* iprot_; + ::apache::thrift::protocol::TProtocol* oprot_; +}; + +class AgentProcessor : public ::apache::thrift::TDispatchProcessor { + protected: + ::std::shared_ptr<AgentIf> iface_; + virtual bool dispatchCall(::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, const std::string& fname, int32_t seqid, void* callContext); + private: + typedef void (AgentProcessor::*ProcessFunction)(int32_t, ::apache::thrift::protocol::TProtocol*, ::apache::thrift::protocol::TProtocol*, void*); + typedef std::map<std::string, ProcessFunction> ProcessMap; + ProcessMap processMap_; + void process_emitZipkinBatch(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext); + void process_emitBatch(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext); + public: + AgentProcessor(::std::shared_ptr<AgentIf> iface) : + iface_(iface) { + processMap_["emitZipkinBatch"] = &AgentProcessor::process_emitZipkinBatch; + processMap_["emitBatch"] = &AgentProcessor::process_emitBatch; + } + + virtual ~AgentProcessor() {} +}; + +class AgentProcessorFactory : public ::apache::thrift::TProcessorFactory { + public: + AgentProcessorFactory(const ::std::shared_ptr< AgentIfFactory >& handlerFactory) : + handlerFactory_(handlerFactory) {} + + ::std::shared_ptr< ::apache::thrift::TProcessor > getProcessor(const ::apache::thrift::TConnectionInfo& connInfo); + + protected: + ::std::shared_ptr< AgentIfFactory > handlerFactory_; +}; + +class AgentMultiface : virtual public AgentIf { + public: + AgentMultiface(std::vector<std::shared_ptr<AgentIf> >& ifaces) : ifaces_(ifaces) { + } + virtual ~AgentMultiface() {} + protected: + std::vector<std::shared_ptr<AgentIf> > ifaces_; + AgentMultiface() {} + void add(::std::shared_ptr<AgentIf> iface) { + ifaces_.push_back(iface); + } + public: + void emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans) { + size_t sz = ifaces_.size(); + size_t i = 0; + for (; i < (sz - 1); ++i) { + ifaces_[i]->emitZipkinBatch(spans); + } + ifaces_[i]->emitZipkinBatch(spans); + } + + void emitBatch(const ::jaegertracing::thrift::Batch& batch) { + size_t sz = ifaces_.size(); + size_t i = 0; + for (; i < (sz - 1); ++i) { + ifaces_[i]->emitBatch(batch); + } + ifaces_[i]->emitBatch(batch); + } + +}; + +// The 'concurrent' client is a thread safe client that correctly handles +// out of order responses. It is slower than the regular client, so should +// only be used when you need to share a connection among multiple threads +class AgentConcurrentClient : virtual public AgentIf { + public: + AgentConcurrentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot, std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync) : sync_(sync) +{ + setProtocol(prot); + } + AgentConcurrentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot, std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync) : sync_(sync) +{ + setProtocol(iprot,oprot); + } + private: + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot,prot); + } + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + piprot_=iprot; + poprot_=oprot; + iprot_ = iprot.get(); + oprot_ = oprot.get(); + } + public: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() { + return piprot_; + } + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() { + return poprot_; + } + void emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans); + void send_emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans); + void emitBatch(const ::jaegertracing::thrift::Batch& batch); + void send_emitBatch(const ::jaegertracing::thrift::Batch& batch); + protected: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot_; + std::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot_; + ::apache::thrift::protocol::TProtocol* iprot_; + ::apache::thrift::protocol::TProtocol* oprot_; + std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync_; +}; + +#ifdef _MSC_VER + #pragma warning( pop ) +#endif + +}}} // namespace + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent_server.skeleton.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent_server.skeleton.cpp new file mode 100644 index 000000000..60fdd9436 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Agent_server.skeleton.cpp @@ -0,0 +1,47 @@ +// This autogenerated skeleton file illustrates how to build a server. +// You should copy it to another filename to avoid overwriting it. + +#include "Agent.h" +#include <thrift/protocol/TBinaryProtocol.h> +#include <thrift/server/TSimpleServer.h> +#include <thrift/transport/TServerSocket.h> +#include <thrift/transport/TBufferTransports.h> + +using namespace ::apache::thrift; +using namespace ::apache::thrift::protocol; +using namespace ::apache::thrift::transport; +using namespace ::apache::thrift::server; + +using namespace ::jaegertracing::agent::thrift; + +class AgentHandler : virtual public AgentIf { + public: + AgentHandler() { + // Your initialization goes here + } + + void emitZipkinBatch(const std::vector< ::twitter::zipkin::thrift::Span> & spans) { + // Your implementation goes here + printf("emitZipkinBatch\n"); + } + + void emitBatch(const ::jaegertracing::thrift::Batch& batch) { + // Your implementation goes here + printf("emitBatch\n"); + } + +}; + +int main(int argc, char **argv) { + int port = 9090; + ::std::shared_ptr<AgentHandler> handler(new AgentHandler()); + ::std::shared_ptr<TProcessor> processor(new AgentProcessor(handler)); + ::std::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port)); + ::std::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory()); + ::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory()); + + TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); + server.serve(); + return 0; +} + diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector.cpp new file mode 100644 index 000000000..f96b40f20 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector.cpp @@ -0,0 +1,481 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#include "Collector.h" + +namespace jaegertracing { namespace thrift { + + +Collector_submitBatches_args::~Collector_submitBatches_args() noexcept { +} + + +uint32_t Collector_submitBatches_args::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->batches.clear(); + uint32_t _size52; + ::apache::thrift::protocol::TType _etype55; + xfer += iprot->readListBegin(_etype55, _size52); + this->batches.resize(_size52); + uint32_t _i56; + for (_i56 = 0; _i56 < _size52; ++_i56) + { + xfer += this->batches[_i56].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.batches = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t Collector_submitBatches_args::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Collector_submitBatches_args"); + + xfer += oprot->writeFieldBegin("batches", ::apache::thrift::protocol::T_LIST, 1); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->batches.size())); + std::vector<Batch> ::const_iterator _iter57; + for (_iter57 = this->batches.begin(); _iter57 != this->batches.end(); ++_iter57) + { + xfer += (*_iter57).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +Collector_submitBatches_pargs::~Collector_submitBatches_pargs() noexcept { +} + + +uint32_t Collector_submitBatches_pargs::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Collector_submitBatches_pargs"); + + xfer += oprot->writeFieldBegin("batches", ::apache::thrift::protocol::T_LIST, 1); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>((*(this->batches)).size())); + std::vector<Batch> ::const_iterator _iter58; + for (_iter58 = (*(this->batches)).begin(); _iter58 != (*(this->batches)).end(); ++_iter58) + { + xfer += (*_iter58).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +Collector_submitBatches_result::~Collector_submitBatches_result() noexcept { +} + + +uint32_t Collector_submitBatches_result::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 0: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->success.clear(); + uint32_t _size59; + ::apache::thrift::protocol::TType _etype62; + xfer += iprot->readListBegin(_etype62, _size59); + this->success.resize(_size59); + uint32_t _i63; + for (_i63 = 0; _i63 < _size59; ++_i63) + { + xfer += this->success[_i63].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.success = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t Collector_submitBatches_result::write(::apache::thrift::protocol::TProtocol* oprot) const { + + uint32_t xfer = 0; + + xfer += oprot->writeStructBegin("Collector_submitBatches_result"); + + if (this->__isset.success) { + xfer += oprot->writeFieldBegin("success", ::apache::thrift::protocol::T_LIST, 0); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->success.size())); + std::vector<BatchSubmitResponse> ::const_iterator _iter64; + for (_iter64 = this->success.begin(); _iter64 != this->success.end(); ++_iter64) + { + xfer += (*_iter64).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +Collector_submitBatches_presult::~Collector_submitBatches_presult() noexcept { +} + + +uint32_t Collector_submitBatches_presult::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 0: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + (*(this->success)).clear(); + uint32_t _size65; + ::apache::thrift::protocol::TType _etype68; + xfer += iprot->readListBegin(_etype68, _size65); + (*(this->success)).resize(_size65); + uint32_t _i69; + for (_i69 = 0; _i69 < _size65; ++_i69) + { + xfer += (*(this->success))[_i69].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.success = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +void CollectorClient::submitBatches(std::vector<BatchSubmitResponse> & _return, const std::vector<Batch> & batches) +{ + send_submitBatches(batches); + recv_submitBatches(_return); +} + +void CollectorClient::send_submitBatches(const std::vector<Batch> & batches) +{ + int32_t cseqid = 0; + oprot_->writeMessageBegin("submitBatches", ::apache::thrift::protocol::T_CALL, cseqid); + + Collector_submitBatches_pargs args; + args.batches = &batches; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); +} + +void CollectorClient::recv_submitBatches(std::vector<BatchSubmitResponse> & _return) +{ + + int32_t rseqid = 0; + std::string fname; + ::apache::thrift::protocol::TMessageType mtype; + + iprot_->readMessageBegin(fname, mtype, rseqid); + if (mtype == ::apache::thrift::protocol::T_EXCEPTION) { + ::apache::thrift::TApplicationException x; + x.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + throw x; + } + if (mtype != ::apache::thrift::protocol::T_REPLY) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + } + if (fname.compare("submitBatches") != 0) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + } + Collector_submitBatches_presult result; + result.success = &_return; + result.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + + if (result.__isset.success) { + // _return pointer has now been filled + return; + } + throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "submitBatches failed: unknown result"); +} + +bool CollectorProcessor::dispatchCall(::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, const std::string& fname, int32_t seqid, void* callContext) { + ProcessMap::iterator pfn; + pfn = processMap_.find(fname); + if (pfn == processMap_.end()) { + iprot->skip(::apache::thrift::protocol::T_STRUCT); + iprot->readMessageEnd(); + iprot->getTransport()->readEnd(); + ::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::UNKNOWN_METHOD, "Invalid method name: '"+fname+"'"); + oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid); + x.write(oprot); + oprot->writeMessageEnd(); + oprot->getTransport()->writeEnd(); + oprot->getTransport()->flush(); + return true; + } + (this->*(pfn->second))(seqid, iprot, oprot, callContext); + return true; +} + +void CollectorProcessor::process_submitBatches(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext) +{ + void* ctx = nullptr; + if (this->eventHandler_.get() != nullptr) { + ctx = this->eventHandler_->getContext("Collector.submitBatches", callContext); + } + ::apache::thrift::TProcessorContextFreer freer(this->eventHandler_.get(), ctx, "Collector.submitBatches"); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->preRead(ctx, "Collector.submitBatches"); + } + + Collector_submitBatches_args args; + args.read(iprot); + iprot->readMessageEnd(); + uint32_t bytes = iprot->getTransport()->readEnd(); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->postRead(ctx, "Collector.submitBatches", bytes); + } + + Collector_submitBatches_result result; + try { + iface_->submitBatches(result.success, args.batches); + result.__isset.success = true; + } catch (const std::exception& e) { + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->handlerError(ctx, "Collector.submitBatches"); + } + + ::apache::thrift::TApplicationException x(e.what()); + oprot->writeMessageBegin("submitBatches", ::apache::thrift::protocol::T_EXCEPTION, seqid); + x.write(oprot); + oprot->writeMessageEnd(); + oprot->getTransport()->writeEnd(); + oprot->getTransport()->flush(); + return; + } + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->preWrite(ctx, "Collector.submitBatches"); + } + + oprot->writeMessageBegin("submitBatches", ::apache::thrift::protocol::T_REPLY, seqid); + result.write(oprot); + oprot->writeMessageEnd(); + bytes = oprot->getTransport()->writeEnd(); + oprot->getTransport()->flush(); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->postWrite(ctx, "Collector.submitBatches", bytes); + } +} + +::std::shared_ptr< ::apache::thrift::TProcessor > CollectorProcessorFactory::getProcessor(const ::apache::thrift::TConnectionInfo& connInfo) { + ::apache::thrift::ReleaseHandler< CollectorIfFactory > cleanup(handlerFactory_); + ::std::shared_ptr< CollectorIf > handler(handlerFactory_->getHandler(connInfo), cleanup); + ::std::shared_ptr< ::apache::thrift::TProcessor > processor(new CollectorProcessor(handler)); + return processor; +} + +void CollectorConcurrentClient::submitBatches(std::vector<BatchSubmitResponse> & _return, const std::vector<Batch> & batches) +{ + int32_t seqid = send_submitBatches(batches); + recv_submitBatches(_return, seqid); +} + +int32_t CollectorConcurrentClient::send_submitBatches(const std::vector<Batch> & batches) +{ + int32_t cseqid = this->sync_->generateSeqId(); + ::apache::thrift::async::TConcurrentSendSentry sentry(this->sync_.get()); + oprot_->writeMessageBegin("submitBatches", ::apache::thrift::protocol::T_CALL, cseqid); + + Collector_submitBatches_pargs args; + args.batches = &batches; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); + + sentry.commit(); + return cseqid; +} + +void CollectorConcurrentClient::recv_submitBatches(std::vector<BatchSubmitResponse> & _return, const int32_t seqid) +{ + + int32_t rseqid = 0; + std::string fname; + ::apache::thrift::protocol::TMessageType mtype; + + // the read mutex gets dropped and reacquired as part of waitForWork() + // The destructor of this sentry wakes up other clients + ::apache::thrift::async::TConcurrentRecvSentry sentry(this->sync_.get(), seqid); + + while(true) { + if(!this->sync_->getPending(fname, mtype, rseqid)) { + iprot_->readMessageBegin(fname, mtype, rseqid); + } + if(seqid == rseqid) { + if (mtype == ::apache::thrift::protocol::T_EXCEPTION) { + ::apache::thrift::TApplicationException x; + x.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + sentry.commit(); + throw x; + } + if (mtype != ::apache::thrift::protocol::T_REPLY) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + } + if (fname.compare("submitBatches") != 0) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + + // in a bad state, don't commit + using ::apache::thrift::protocol::TProtocolException; + throw TProtocolException(TProtocolException::INVALID_DATA); + } + Collector_submitBatches_presult result; + result.success = &_return; + result.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + + if (result.__isset.success) { + // _return pointer has now been filled + sentry.commit(); + return; + } + // in a bad state, don't commit + throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "submitBatches failed: unknown result"); + } + // seqid != rseqid + this->sync_->updatePending(fname, mtype, rseqid); + + // this will temporarily unlock the readMutex, and let other clients get work done + this->sync_->waitForWork(seqid); + } // end while(true) +} + +}} // namespace + diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector.h new file mode 100644 index 000000000..75daa69a7 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector.h @@ -0,0 +1,299 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#ifndef Collector_H +#define Collector_H + +#include <thrift/TDispatchProcessor.h> +#include <thrift/async/TConcurrentClientSyncInfo.h> +#include <memory> +#include "jaeger_types.h" + +namespace jaegertracing { namespace thrift { + +#ifdef _MSC_VER + #pragma warning( push ) + #pragma warning (disable : 4250 ) //inheriting methods via dominance +#endif + +class CollectorIf { + public: + virtual ~CollectorIf() {} + virtual void submitBatches(std::vector<BatchSubmitResponse> & _return, const std::vector<Batch> & batches) = 0; +}; + +class CollectorIfFactory { + public: + typedef CollectorIf Handler; + + virtual ~CollectorIfFactory() {} + + virtual CollectorIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) = 0; + virtual void releaseHandler(CollectorIf* /* handler */) = 0; +}; + +class CollectorIfSingletonFactory : virtual public CollectorIfFactory { + public: + CollectorIfSingletonFactory(const ::std::shared_ptr<CollectorIf>& iface) : iface_(iface) {} + virtual ~CollectorIfSingletonFactory() {} + + virtual CollectorIf* getHandler(const ::apache::thrift::TConnectionInfo&) { + return iface_.get(); + } + virtual void releaseHandler(CollectorIf* /* handler */) {} + + protected: + ::std::shared_ptr<CollectorIf> iface_; +}; + +class CollectorNull : virtual public CollectorIf { + public: + virtual ~CollectorNull() {} + void submitBatches(std::vector<BatchSubmitResponse> & /* _return */, const std::vector<Batch> & /* batches */) { + return; + } +}; + +typedef struct _Collector_submitBatches_args__isset { + _Collector_submitBatches_args__isset() : batches(false) {} + bool batches :1; +} _Collector_submitBatches_args__isset; + +class Collector_submitBatches_args { + public: + + Collector_submitBatches_args(const Collector_submitBatches_args&); + Collector_submitBatches_args& operator=(const Collector_submitBatches_args&); + Collector_submitBatches_args() { + } + + virtual ~Collector_submitBatches_args() noexcept; + std::vector<Batch> batches; + + _Collector_submitBatches_args__isset __isset; + + void __set_batches(const std::vector<Batch> & val); + + bool operator == (const Collector_submitBatches_args & rhs) const + { + if (!(batches == rhs.batches)) + return false; + return true; + } + bool operator != (const Collector_submitBatches_args &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Collector_submitBatches_args & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + + +class Collector_submitBatches_pargs { + public: + + + virtual ~Collector_submitBatches_pargs() noexcept; + const std::vector<Batch> * batches; + + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + +typedef struct _Collector_submitBatches_result__isset { + _Collector_submitBatches_result__isset() : success(false) {} + bool success :1; +} _Collector_submitBatches_result__isset; + +class Collector_submitBatches_result { + public: + + Collector_submitBatches_result(const Collector_submitBatches_result&); + Collector_submitBatches_result& operator=(const Collector_submitBatches_result&); + Collector_submitBatches_result() { + } + + virtual ~Collector_submitBatches_result() noexcept; + std::vector<BatchSubmitResponse> success; + + _Collector_submitBatches_result__isset __isset; + + void __set_success(const std::vector<BatchSubmitResponse> & val); + + bool operator == (const Collector_submitBatches_result & rhs) const + { + if (!(success == rhs.success)) + return false; + return true; + } + bool operator != (const Collector_submitBatches_result &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Collector_submitBatches_result & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + +typedef struct _Collector_submitBatches_presult__isset { + _Collector_submitBatches_presult__isset() : success(false) {} + bool success :1; +} _Collector_submitBatches_presult__isset; + +class Collector_submitBatches_presult { + public: + + + virtual ~Collector_submitBatches_presult() noexcept; + std::vector<BatchSubmitResponse> * success; + + _Collector_submitBatches_presult__isset __isset; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + +}; + +class CollectorClient : virtual public CollectorIf { + public: + CollectorClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot); + } + CollectorClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + setProtocol(iprot,oprot); + } + private: + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot,prot); + } + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + piprot_=iprot; + poprot_=oprot; + iprot_ = iprot.get(); + oprot_ = oprot.get(); + } + public: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() { + return piprot_; + } + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() { + return poprot_; + } + void submitBatches(std::vector<BatchSubmitResponse> & _return, const std::vector<Batch> & batches); + void send_submitBatches(const std::vector<Batch> & batches); + void recv_submitBatches(std::vector<BatchSubmitResponse> & _return); + protected: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot_; + std::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot_; + ::apache::thrift::protocol::TProtocol* iprot_; + ::apache::thrift::protocol::TProtocol* oprot_; +}; + +class CollectorProcessor : public ::apache::thrift::TDispatchProcessor { + protected: + ::std::shared_ptr<CollectorIf> iface_; + virtual bool dispatchCall(::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, const std::string& fname, int32_t seqid, void* callContext); + private: + typedef void (CollectorProcessor::*ProcessFunction)(int32_t, ::apache::thrift::protocol::TProtocol*, ::apache::thrift::protocol::TProtocol*, void*); + typedef std::map<std::string, ProcessFunction> ProcessMap; + ProcessMap processMap_; + void process_submitBatches(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext); + public: + CollectorProcessor(::std::shared_ptr<CollectorIf> iface) : + iface_(iface) { + processMap_["submitBatches"] = &CollectorProcessor::process_submitBatches; + } + + virtual ~CollectorProcessor() {} +}; + +class CollectorProcessorFactory : public ::apache::thrift::TProcessorFactory { + public: + CollectorProcessorFactory(const ::std::shared_ptr< CollectorIfFactory >& handlerFactory) : + handlerFactory_(handlerFactory) {} + + ::std::shared_ptr< ::apache::thrift::TProcessor > getProcessor(const ::apache::thrift::TConnectionInfo& connInfo); + + protected: + ::std::shared_ptr< CollectorIfFactory > handlerFactory_; +}; + +class CollectorMultiface : virtual public CollectorIf { + public: + CollectorMultiface(std::vector<std::shared_ptr<CollectorIf> >& ifaces) : ifaces_(ifaces) { + } + virtual ~CollectorMultiface() {} + protected: + std::vector<std::shared_ptr<CollectorIf> > ifaces_; + CollectorMultiface() {} + void add(::std::shared_ptr<CollectorIf> iface) { + ifaces_.push_back(iface); + } + public: + void submitBatches(std::vector<BatchSubmitResponse> & _return, const std::vector<Batch> & batches) { + size_t sz = ifaces_.size(); + size_t i = 0; + for (; i < (sz - 1); ++i) { + ifaces_[i]->submitBatches(_return, batches); + } + ifaces_[i]->submitBatches(_return, batches); + return; + } + +}; + +// The 'concurrent' client is a thread safe client that correctly handles +// out of order responses. It is slower than the regular client, so should +// only be used when you need to share a connection among multiple threads +class CollectorConcurrentClient : virtual public CollectorIf { + public: + CollectorConcurrentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot, std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync) : sync_(sync) +{ + setProtocol(prot); + } + CollectorConcurrentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot, std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync) : sync_(sync) +{ + setProtocol(iprot,oprot); + } + private: + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot,prot); + } + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + piprot_=iprot; + poprot_=oprot; + iprot_ = iprot.get(); + oprot_ = oprot.get(); + } + public: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() { + return piprot_; + } + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() { + return poprot_; + } + void submitBatches(std::vector<BatchSubmitResponse> & _return, const std::vector<Batch> & batches); + int32_t send_submitBatches(const std::vector<Batch> & batches); + void recv_submitBatches(std::vector<BatchSubmitResponse> & _return, const int32_t seqid); + protected: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot_; + std::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot_; + ::apache::thrift::protocol::TProtocol* iprot_; + ::apache::thrift::protocol::TProtocol* oprot_; + std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync_; +}; + +#ifdef _MSC_VER + #pragma warning( pop ) +#endif + +}} // namespace + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector_server.skeleton.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector_server.skeleton.cpp new file mode 100644 index 000000000..c59c8aa49 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/Collector_server.skeleton.cpp @@ -0,0 +1,42 @@ +// This autogenerated skeleton file illustrates how to build a server. +// You should copy it to another filename to avoid overwriting it. + +#include "Collector.h" +#include <thrift/protocol/TBinaryProtocol.h> +#include <thrift/server/TSimpleServer.h> +#include <thrift/transport/TServerSocket.h> +#include <thrift/transport/TBufferTransports.h> + +using namespace ::apache::thrift; +using namespace ::apache::thrift::protocol; +using namespace ::apache::thrift::transport; +using namespace ::apache::thrift::server; + +using namespace ::jaegertracing::thrift; + +class CollectorHandler : virtual public CollectorIf { + public: + CollectorHandler() { + // Your initialization goes here + } + + void submitBatches(std::vector<BatchSubmitResponse> & _return, const std::vector<Batch> & batches) { + // Your implementation goes here + printf("submitBatches\n"); + } + +}; + +int main(int argc, char **argv) { + int port = 9090; + ::std::shared_ptr<CollectorHandler> handler(new CollectorHandler()); + ::std::shared_ptr<TProcessor> processor(new CollectorProcessor(handler)); + ::std::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port)); + ::std::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory()); + ::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory()); + + TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); + server.serve(); + return 0; +} + diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector.cpp new file mode 100644 index 000000000..42d813dfd --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector.cpp @@ -0,0 +1,481 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#include "ZipkinCollector.h" + +namespace twitter { namespace zipkin { namespace thrift { + + +ZipkinCollector_submitZipkinBatch_args::~ZipkinCollector_submitZipkinBatch_args() noexcept { +} + + +uint32_t ZipkinCollector_submitZipkinBatch_args::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->spans.clear(); + uint32_t _size23; + ::apache::thrift::protocol::TType _etype26; + xfer += iprot->readListBegin(_etype26, _size23); + this->spans.resize(_size23); + uint32_t _i27; + for (_i27 = 0; _i27 < _size23; ++_i27) + { + xfer += this->spans[_i27].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.spans = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t ZipkinCollector_submitZipkinBatch_args::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("ZipkinCollector_submitZipkinBatch_args"); + + xfer += oprot->writeFieldBegin("spans", ::apache::thrift::protocol::T_LIST, 1); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->spans.size())); + std::vector<Span> ::const_iterator _iter28; + for (_iter28 = this->spans.begin(); _iter28 != this->spans.end(); ++_iter28) + { + xfer += (*_iter28).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +ZipkinCollector_submitZipkinBatch_pargs::~ZipkinCollector_submitZipkinBatch_pargs() noexcept { +} + + +uint32_t ZipkinCollector_submitZipkinBatch_pargs::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("ZipkinCollector_submitZipkinBatch_pargs"); + + xfer += oprot->writeFieldBegin("spans", ::apache::thrift::protocol::T_LIST, 1); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>((*(this->spans)).size())); + std::vector<Span> ::const_iterator _iter29; + for (_iter29 = (*(this->spans)).begin(); _iter29 != (*(this->spans)).end(); ++_iter29) + { + xfer += (*_iter29).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +ZipkinCollector_submitZipkinBatch_result::~ZipkinCollector_submitZipkinBatch_result() noexcept { +} + + +uint32_t ZipkinCollector_submitZipkinBatch_result::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 0: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->success.clear(); + uint32_t _size30; + ::apache::thrift::protocol::TType _etype33; + xfer += iprot->readListBegin(_etype33, _size30); + this->success.resize(_size30); + uint32_t _i34; + for (_i34 = 0; _i34 < _size30; ++_i34) + { + xfer += this->success[_i34].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.success = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t ZipkinCollector_submitZipkinBatch_result::write(::apache::thrift::protocol::TProtocol* oprot) const { + + uint32_t xfer = 0; + + xfer += oprot->writeStructBegin("ZipkinCollector_submitZipkinBatch_result"); + + if (this->__isset.success) { + xfer += oprot->writeFieldBegin("success", ::apache::thrift::protocol::T_LIST, 0); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->success.size())); + std::vector<Response> ::const_iterator _iter35; + for (_iter35 = this->success.begin(); _iter35 != this->success.end(); ++_iter35) + { + xfer += (*_iter35).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + + +ZipkinCollector_submitZipkinBatch_presult::~ZipkinCollector_submitZipkinBatch_presult() noexcept { +} + + +uint32_t ZipkinCollector_submitZipkinBatch_presult::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 0: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + (*(this->success)).clear(); + uint32_t _size36; + ::apache::thrift::protocol::TType _etype39; + xfer += iprot->readListBegin(_etype39, _size36); + (*(this->success)).resize(_size36); + uint32_t _i40; + for (_i40 = 0; _i40 < _size36; ++_i40) + { + xfer += (*(this->success))[_i40].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.success = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +void ZipkinCollectorClient::submitZipkinBatch(std::vector<Response> & _return, const std::vector<Span> & spans) +{ + send_submitZipkinBatch(spans); + recv_submitZipkinBatch(_return); +} + +void ZipkinCollectorClient::send_submitZipkinBatch(const std::vector<Span> & spans) +{ + int32_t cseqid = 0; + oprot_->writeMessageBegin("submitZipkinBatch", ::apache::thrift::protocol::T_CALL, cseqid); + + ZipkinCollector_submitZipkinBatch_pargs args; + args.spans = &spans; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); +} + +void ZipkinCollectorClient::recv_submitZipkinBatch(std::vector<Response> & _return) +{ + + int32_t rseqid = 0; + std::string fname; + ::apache::thrift::protocol::TMessageType mtype; + + iprot_->readMessageBegin(fname, mtype, rseqid); + if (mtype == ::apache::thrift::protocol::T_EXCEPTION) { + ::apache::thrift::TApplicationException x; + x.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + throw x; + } + if (mtype != ::apache::thrift::protocol::T_REPLY) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + } + if (fname.compare("submitZipkinBatch") != 0) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + } + ZipkinCollector_submitZipkinBatch_presult result; + result.success = &_return; + result.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + + if (result.__isset.success) { + // _return pointer has now been filled + return; + } + throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "submitZipkinBatch failed: unknown result"); +} + +bool ZipkinCollectorProcessor::dispatchCall(::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, const std::string& fname, int32_t seqid, void* callContext) { + ProcessMap::iterator pfn; + pfn = processMap_.find(fname); + if (pfn == processMap_.end()) { + iprot->skip(::apache::thrift::protocol::T_STRUCT); + iprot->readMessageEnd(); + iprot->getTransport()->readEnd(); + ::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::UNKNOWN_METHOD, "Invalid method name: '"+fname+"'"); + oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid); + x.write(oprot); + oprot->writeMessageEnd(); + oprot->getTransport()->writeEnd(); + oprot->getTransport()->flush(); + return true; + } + (this->*(pfn->second))(seqid, iprot, oprot, callContext); + return true; +} + +void ZipkinCollectorProcessor::process_submitZipkinBatch(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext) +{ + void* ctx = nullptr; + if (this->eventHandler_.get() != nullptr) { + ctx = this->eventHandler_->getContext("ZipkinCollector.submitZipkinBatch", callContext); + } + ::apache::thrift::TProcessorContextFreer freer(this->eventHandler_.get(), ctx, "ZipkinCollector.submitZipkinBatch"); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->preRead(ctx, "ZipkinCollector.submitZipkinBatch"); + } + + ZipkinCollector_submitZipkinBatch_args args; + args.read(iprot); + iprot->readMessageEnd(); + uint32_t bytes = iprot->getTransport()->readEnd(); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->postRead(ctx, "ZipkinCollector.submitZipkinBatch", bytes); + } + + ZipkinCollector_submitZipkinBatch_result result; + try { + iface_->submitZipkinBatch(result.success, args.spans); + result.__isset.success = true; + } catch (const std::exception& e) { + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->handlerError(ctx, "ZipkinCollector.submitZipkinBatch"); + } + + ::apache::thrift::TApplicationException x(e.what()); + oprot->writeMessageBegin("submitZipkinBatch", ::apache::thrift::protocol::T_EXCEPTION, seqid); + x.write(oprot); + oprot->writeMessageEnd(); + oprot->getTransport()->writeEnd(); + oprot->getTransport()->flush(); + return; + } + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->preWrite(ctx, "ZipkinCollector.submitZipkinBatch"); + } + + oprot->writeMessageBegin("submitZipkinBatch", ::apache::thrift::protocol::T_REPLY, seqid); + result.write(oprot); + oprot->writeMessageEnd(); + bytes = oprot->getTransport()->writeEnd(); + oprot->getTransport()->flush(); + + if (this->eventHandler_.get() != nullptr) { + this->eventHandler_->postWrite(ctx, "ZipkinCollector.submitZipkinBatch", bytes); + } +} + +::std::shared_ptr< ::apache::thrift::TProcessor > ZipkinCollectorProcessorFactory::getProcessor(const ::apache::thrift::TConnectionInfo& connInfo) { + ::apache::thrift::ReleaseHandler< ZipkinCollectorIfFactory > cleanup(handlerFactory_); + ::std::shared_ptr< ZipkinCollectorIf > handler(handlerFactory_->getHandler(connInfo), cleanup); + ::std::shared_ptr< ::apache::thrift::TProcessor > processor(new ZipkinCollectorProcessor(handler)); + return processor; +} + +void ZipkinCollectorConcurrentClient::submitZipkinBatch(std::vector<Response> & _return, const std::vector<Span> & spans) +{ + int32_t seqid = send_submitZipkinBatch(spans); + recv_submitZipkinBatch(_return, seqid); +} + +int32_t ZipkinCollectorConcurrentClient::send_submitZipkinBatch(const std::vector<Span> & spans) +{ + int32_t cseqid = this->sync_->generateSeqId(); + ::apache::thrift::async::TConcurrentSendSentry sentry(this->sync_.get()); + oprot_->writeMessageBegin("submitZipkinBatch", ::apache::thrift::protocol::T_CALL, cseqid); + + ZipkinCollector_submitZipkinBatch_pargs args; + args.spans = &spans; + args.write(oprot_); + + oprot_->writeMessageEnd(); + oprot_->getTransport()->writeEnd(); + oprot_->getTransport()->flush(); + + sentry.commit(); + return cseqid; +} + +void ZipkinCollectorConcurrentClient::recv_submitZipkinBatch(std::vector<Response> & _return, const int32_t seqid) +{ + + int32_t rseqid = 0; + std::string fname; + ::apache::thrift::protocol::TMessageType mtype; + + // the read mutex gets dropped and reacquired as part of waitForWork() + // The destructor of this sentry wakes up other clients + ::apache::thrift::async::TConcurrentRecvSentry sentry(this->sync_.get(), seqid); + + while(true) { + if(!this->sync_->getPending(fname, mtype, rseqid)) { + iprot_->readMessageBegin(fname, mtype, rseqid); + } + if(seqid == rseqid) { + if (mtype == ::apache::thrift::protocol::T_EXCEPTION) { + ::apache::thrift::TApplicationException x; + x.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + sentry.commit(); + throw x; + } + if (mtype != ::apache::thrift::protocol::T_REPLY) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + } + if (fname.compare("submitZipkinBatch") != 0) { + iprot_->skip(::apache::thrift::protocol::T_STRUCT); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + + // in a bad state, don't commit + using ::apache::thrift::protocol::TProtocolException; + throw TProtocolException(TProtocolException::INVALID_DATA); + } + ZipkinCollector_submitZipkinBatch_presult result; + result.success = &_return; + result.read(iprot_); + iprot_->readMessageEnd(); + iprot_->getTransport()->readEnd(); + + if (result.__isset.success) { + // _return pointer has now been filled + sentry.commit(); + return; + } + // in a bad state, don't commit + throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "submitZipkinBatch failed: unknown result"); + } + // seqid != rseqid + this->sync_->updatePending(fname, mtype, rseqid); + + // this will temporarily unlock the readMutex, and let other clients get work done + this->sync_->waitForWork(seqid); + } // end while(true) +} + +}}} // namespace + diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector.h new file mode 100644 index 000000000..97e111d24 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector.h @@ -0,0 +1,299 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#ifndef ZipkinCollector_H +#define ZipkinCollector_H + +#include <thrift/TDispatchProcessor.h> +#include <thrift/async/TConcurrentClientSyncInfo.h> +#include <memory> +#include "zipkincore_types.h" + +namespace twitter { namespace zipkin { namespace thrift { + +#ifdef _MSC_VER + #pragma warning( push ) + #pragma warning (disable : 4250 ) //inheriting methods via dominance +#endif + +class ZipkinCollectorIf { + public: + virtual ~ZipkinCollectorIf() {} + virtual void submitZipkinBatch(std::vector<Response> & _return, const std::vector<Span> & spans) = 0; +}; + +class ZipkinCollectorIfFactory { + public: + typedef ZipkinCollectorIf Handler; + + virtual ~ZipkinCollectorIfFactory() {} + + virtual ZipkinCollectorIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) = 0; + virtual void releaseHandler(ZipkinCollectorIf* /* handler */) = 0; +}; + +class ZipkinCollectorIfSingletonFactory : virtual public ZipkinCollectorIfFactory { + public: + ZipkinCollectorIfSingletonFactory(const ::std::shared_ptr<ZipkinCollectorIf>& iface) : iface_(iface) {} + virtual ~ZipkinCollectorIfSingletonFactory() {} + + virtual ZipkinCollectorIf* getHandler(const ::apache::thrift::TConnectionInfo&) { + return iface_.get(); + } + virtual void releaseHandler(ZipkinCollectorIf* /* handler */) {} + + protected: + ::std::shared_ptr<ZipkinCollectorIf> iface_; +}; + +class ZipkinCollectorNull : virtual public ZipkinCollectorIf { + public: + virtual ~ZipkinCollectorNull() {} + void submitZipkinBatch(std::vector<Response> & /* _return */, const std::vector<Span> & /* spans */) { + return; + } +}; + +typedef struct _ZipkinCollector_submitZipkinBatch_args__isset { + _ZipkinCollector_submitZipkinBatch_args__isset() : spans(false) {} + bool spans :1; +} _ZipkinCollector_submitZipkinBatch_args__isset; + +class ZipkinCollector_submitZipkinBatch_args { + public: + + ZipkinCollector_submitZipkinBatch_args(const ZipkinCollector_submitZipkinBatch_args&); + ZipkinCollector_submitZipkinBatch_args& operator=(const ZipkinCollector_submitZipkinBatch_args&); + ZipkinCollector_submitZipkinBatch_args() { + } + + virtual ~ZipkinCollector_submitZipkinBatch_args() noexcept; + std::vector<Span> spans; + + _ZipkinCollector_submitZipkinBatch_args__isset __isset; + + void __set_spans(const std::vector<Span> & val); + + bool operator == (const ZipkinCollector_submitZipkinBatch_args & rhs) const + { + if (!(spans == rhs.spans)) + return false; + return true; + } + bool operator != (const ZipkinCollector_submitZipkinBatch_args &rhs) const { + return !(*this == rhs); + } + + bool operator < (const ZipkinCollector_submitZipkinBatch_args & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + + +class ZipkinCollector_submitZipkinBatch_pargs { + public: + + + virtual ~ZipkinCollector_submitZipkinBatch_pargs() noexcept; + const std::vector<Span> * spans; + + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + +typedef struct _ZipkinCollector_submitZipkinBatch_result__isset { + _ZipkinCollector_submitZipkinBatch_result__isset() : success(false) {} + bool success :1; +} _ZipkinCollector_submitZipkinBatch_result__isset; + +class ZipkinCollector_submitZipkinBatch_result { + public: + + ZipkinCollector_submitZipkinBatch_result(const ZipkinCollector_submitZipkinBatch_result&); + ZipkinCollector_submitZipkinBatch_result& operator=(const ZipkinCollector_submitZipkinBatch_result&); + ZipkinCollector_submitZipkinBatch_result() { + } + + virtual ~ZipkinCollector_submitZipkinBatch_result() noexcept; + std::vector<Response> success; + + _ZipkinCollector_submitZipkinBatch_result__isset __isset; + + void __set_success(const std::vector<Response> & val); + + bool operator == (const ZipkinCollector_submitZipkinBatch_result & rhs) const + { + if (!(success == rhs.success)) + return false; + return true; + } + bool operator != (const ZipkinCollector_submitZipkinBatch_result &rhs) const { + return !(*this == rhs); + } + + bool operator < (const ZipkinCollector_submitZipkinBatch_result & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + +}; + +typedef struct _ZipkinCollector_submitZipkinBatch_presult__isset { + _ZipkinCollector_submitZipkinBatch_presult__isset() : success(false) {} + bool success :1; +} _ZipkinCollector_submitZipkinBatch_presult__isset; + +class ZipkinCollector_submitZipkinBatch_presult { + public: + + + virtual ~ZipkinCollector_submitZipkinBatch_presult() noexcept; + std::vector<Response> * success; + + _ZipkinCollector_submitZipkinBatch_presult__isset __isset; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + +}; + +class ZipkinCollectorClient : virtual public ZipkinCollectorIf { + public: + ZipkinCollectorClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot); + } + ZipkinCollectorClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + setProtocol(iprot,oprot); + } + private: + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot,prot); + } + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + piprot_=iprot; + poprot_=oprot; + iprot_ = iprot.get(); + oprot_ = oprot.get(); + } + public: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() { + return piprot_; + } + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() { + return poprot_; + } + void submitZipkinBatch(std::vector<Response> & _return, const std::vector<Span> & spans); + void send_submitZipkinBatch(const std::vector<Span> & spans); + void recv_submitZipkinBatch(std::vector<Response> & _return); + protected: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot_; + std::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot_; + ::apache::thrift::protocol::TProtocol* iprot_; + ::apache::thrift::protocol::TProtocol* oprot_; +}; + +class ZipkinCollectorProcessor : public ::apache::thrift::TDispatchProcessor { + protected: + ::std::shared_ptr<ZipkinCollectorIf> iface_; + virtual bool dispatchCall(::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, const std::string& fname, int32_t seqid, void* callContext); + private: + typedef void (ZipkinCollectorProcessor::*ProcessFunction)(int32_t, ::apache::thrift::protocol::TProtocol*, ::apache::thrift::protocol::TProtocol*, void*); + typedef std::map<std::string, ProcessFunction> ProcessMap; + ProcessMap processMap_; + void process_submitZipkinBatch(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext); + public: + ZipkinCollectorProcessor(::std::shared_ptr<ZipkinCollectorIf> iface) : + iface_(iface) { + processMap_["submitZipkinBatch"] = &ZipkinCollectorProcessor::process_submitZipkinBatch; + } + + virtual ~ZipkinCollectorProcessor() {} +}; + +class ZipkinCollectorProcessorFactory : public ::apache::thrift::TProcessorFactory { + public: + ZipkinCollectorProcessorFactory(const ::std::shared_ptr< ZipkinCollectorIfFactory >& handlerFactory) : + handlerFactory_(handlerFactory) {} + + ::std::shared_ptr< ::apache::thrift::TProcessor > getProcessor(const ::apache::thrift::TConnectionInfo& connInfo); + + protected: + ::std::shared_ptr< ZipkinCollectorIfFactory > handlerFactory_; +}; + +class ZipkinCollectorMultiface : virtual public ZipkinCollectorIf { + public: + ZipkinCollectorMultiface(std::vector<std::shared_ptr<ZipkinCollectorIf> >& ifaces) : ifaces_(ifaces) { + } + virtual ~ZipkinCollectorMultiface() {} + protected: + std::vector<std::shared_ptr<ZipkinCollectorIf> > ifaces_; + ZipkinCollectorMultiface() {} + void add(::std::shared_ptr<ZipkinCollectorIf> iface) { + ifaces_.push_back(iface); + } + public: + void submitZipkinBatch(std::vector<Response> & _return, const std::vector<Span> & spans) { + size_t sz = ifaces_.size(); + size_t i = 0; + for (; i < (sz - 1); ++i) { + ifaces_[i]->submitZipkinBatch(_return, spans); + } + ifaces_[i]->submitZipkinBatch(_return, spans); + return; + } + +}; + +// The 'concurrent' client is a thread safe client that correctly handles +// out of order responses. It is slower than the regular client, so should +// only be used when you need to share a connection among multiple threads +class ZipkinCollectorConcurrentClient : virtual public ZipkinCollectorIf { + public: + ZipkinCollectorConcurrentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot, std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync) : sync_(sync) +{ + setProtocol(prot); + } + ZipkinCollectorConcurrentClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot, std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync) : sync_(sync) +{ + setProtocol(iprot,oprot); + } + private: + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) { + setProtocol(prot,prot); + } + void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) { + piprot_=iprot; + poprot_=oprot; + iprot_ = iprot.get(); + oprot_ = oprot.get(); + } + public: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() { + return piprot_; + } + std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() { + return poprot_; + } + void submitZipkinBatch(std::vector<Response> & _return, const std::vector<Span> & spans); + int32_t send_submitZipkinBatch(const std::vector<Span> & spans); + void recv_submitZipkinBatch(std::vector<Response> & _return, const int32_t seqid); + protected: + std::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot_; + std::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot_; + ::apache::thrift::protocol::TProtocol* iprot_; + ::apache::thrift::protocol::TProtocol* oprot_; + std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync_; +}; + +#ifdef _MSC_VER + #pragma warning( pop ) +#endif + +}}} // namespace + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector_server.skeleton.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector_server.skeleton.cpp new file mode 100644 index 000000000..cca6ef752 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/ZipkinCollector_server.skeleton.cpp @@ -0,0 +1,42 @@ +// This autogenerated skeleton file illustrates how to build a server. +// You should copy it to another filename to avoid overwriting it. + +#include "ZipkinCollector.h" +#include <thrift/protocol/TBinaryProtocol.h> +#include <thrift/server/TSimpleServer.h> +#include <thrift/transport/TServerSocket.h> +#include <thrift/transport/TBufferTransports.h> + +using namespace ::apache::thrift; +using namespace ::apache::thrift::protocol; +using namespace ::apache::thrift::transport; +using namespace ::apache::thrift::server; + +using namespace ::twitter::zipkin::thrift; + +class ZipkinCollectorHandler : virtual public ZipkinCollectorIf { + public: + ZipkinCollectorHandler() { + // Your initialization goes here + } + + void submitZipkinBatch(std::vector<Response> & _return, const std::vector<Span> & spans) { + // Your implementation goes here + printf("submitZipkinBatch\n"); + } + +}; + +int main(int argc, char **argv) { + int port = 9090; + ::std::shared_ptr<ZipkinCollectorHandler> handler(new ZipkinCollectorHandler()); + ::std::shared_ptr<TProcessor> processor(new ZipkinCollectorProcessor(handler)); + ::std::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port)); + ::std::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory()); + ::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory()); + + TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); + server.serve(); + return 0; +} + diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/agent_types.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/agent_types.h new file mode 100644 index 000000000..0b576e34f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/agent_types.h @@ -0,0 +1,28 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#ifndef agent_TYPES_H +#define agent_TYPES_H + +#include <iosfwd> + +#include <thrift/Thrift.h> +#include <thrift/TApplicationException.h> +#include <thrift/TBase.h> +#include <thrift/protocol/TProtocol.h> +#include <thrift/transport/TTransport.h> + +#include <functional> +#include <memory> +#include "jaeger_types.h" +#include "zipkincore_types.h" + + +namespace jaegertracing { namespace agent { namespace thrift { + +}}} // namespace + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/jaeger_types.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/jaeger_types.cpp new file mode 100644 index 000000000..5e4140cc9 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/jaeger_types.cpp @@ -0,0 +1,1354 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#include "jaeger_types.h" + +#include <algorithm> +#include <ostream> + +#include <thrift/TToString.h> + +namespace jaegertracing { namespace thrift { + +int _kTagTypeValues[] = { + TagType::STRING, + TagType::DOUBLE, + TagType::BOOL, + TagType::LONG, + TagType::BINARY +}; +const char* _kTagTypeNames[] = { + "STRING", + "DOUBLE", + "BOOL", + "LONG", + "BINARY" +}; +const std::map<int, const char*> _TagType_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(5, _kTagTypeValues, _kTagTypeNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); + +std::ostream& operator<<(std::ostream& out, const TagType::type& val) { + std::map<int, const char*>::const_iterator it = _TagType_VALUES_TO_NAMES.find(val); + if (it != _TagType_VALUES_TO_NAMES.end()) { + out << it->second; + } else { + out << static_cast<int>(val); + } + return out; +} + +std::string to_string(const TagType::type& val) { + std::map<int, const char*>::const_iterator it = _TagType_VALUES_TO_NAMES.find(val); + if (it != _TagType_VALUES_TO_NAMES.end()) { + return std::string(it->second); + } else { + return std::to_string(static_cast<int>(val)); + } +} + +int _kSpanRefTypeValues[] = { + SpanRefType::CHILD_OF, + SpanRefType::FOLLOWS_FROM +}; +const char* _kSpanRefTypeNames[] = { + "CHILD_OF", + "FOLLOWS_FROM" +}; +const std::map<int, const char*> _SpanRefType_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(2, _kSpanRefTypeValues, _kSpanRefTypeNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); + +std::ostream& operator<<(std::ostream& out, const SpanRefType::type& val) { + std::map<int, const char*>::const_iterator it = _SpanRefType_VALUES_TO_NAMES.find(val); + if (it != _SpanRefType_VALUES_TO_NAMES.end()) { + out << it->second; + } else { + out << static_cast<int>(val); + } + return out; +} + +std::string to_string(const SpanRefType::type& val) { + std::map<int, const char*>::const_iterator it = _SpanRefType_VALUES_TO_NAMES.find(val); + if (it != _SpanRefType_VALUES_TO_NAMES.end()) { + return std::string(it->second); + } else { + return std::to_string(static_cast<int>(val)); + } +} + + +Tag::~Tag() noexcept { +} + + +void Tag::__set_key(const std::string& val) { + this->key = val; +} + +void Tag::__set_vType(const TagType::type val) { + this->vType = val; +} + +void Tag::__set_vStr(const std::string& val) { + this->vStr = val; +__isset.vStr = true; +} + +void Tag::__set_vDouble(const double val) { + this->vDouble = val; +__isset.vDouble = true; +} + +void Tag::__set_vBool(const bool val) { + this->vBool = val; +__isset.vBool = true; +} + +void Tag::__set_vLong(const int64_t val) { + this->vLong = val; +__isset.vLong = true; +} + +void Tag::__set_vBinary(const std::string& val) { + this->vBinary = val; +__isset.vBinary = true; +} +std::ostream& operator<<(std::ostream& out, const Tag& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Tag::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_key = false; + bool isset_vType = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->key); + isset_key = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_I32) { + int32_t ecast0; + xfer += iprot->readI32(ecast0); + this->vType = (TagType::type)ecast0; + isset_vType = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 3: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->vStr); + this->__isset.vStr = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 4: + if (ftype == ::apache::thrift::protocol::T_DOUBLE) { + xfer += iprot->readDouble(this->vDouble); + this->__isset.vDouble = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 5: + if (ftype == ::apache::thrift::protocol::T_BOOL) { + xfer += iprot->readBool(this->vBool); + this->__isset.vBool = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 6: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->vLong); + this->__isset.vLong = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 7: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readBinary(this->vBinary); + this->__isset.vBinary = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_key) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_vType) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t Tag::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Tag"); + + xfer += oprot->writeFieldBegin("key", ::apache::thrift::protocol::T_STRING, 1); + xfer += oprot->writeString(this->key); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("vType", ::apache::thrift::protocol::T_I32, 2); + xfer += oprot->writeI32((int32_t)this->vType); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.vStr) { + xfer += oprot->writeFieldBegin("vStr", ::apache::thrift::protocol::T_STRING, 3); + xfer += oprot->writeString(this->vStr); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.vDouble) { + xfer += oprot->writeFieldBegin("vDouble", ::apache::thrift::protocol::T_DOUBLE, 4); + xfer += oprot->writeDouble(this->vDouble); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.vBool) { + xfer += oprot->writeFieldBegin("vBool", ::apache::thrift::protocol::T_BOOL, 5); + xfer += oprot->writeBool(this->vBool); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.vLong) { + xfer += oprot->writeFieldBegin("vLong", ::apache::thrift::protocol::T_I64, 6); + xfer += oprot->writeI64(this->vLong); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.vBinary) { + xfer += oprot->writeFieldBegin("vBinary", ::apache::thrift::protocol::T_STRING, 7); + xfer += oprot->writeBinary(this->vBinary); + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Tag &a, Tag &b) { + using ::std::swap; + swap(a.key, b.key); + swap(a.vType, b.vType); + swap(a.vStr, b.vStr); + swap(a.vDouble, b.vDouble); + swap(a.vBool, b.vBool); + swap(a.vLong, b.vLong); + swap(a.vBinary, b.vBinary); + swap(a.__isset, b.__isset); +} + +Tag::Tag(const Tag& other1) { + key = other1.key; + vType = other1.vType; + vStr = other1.vStr; + vDouble = other1.vDouble; + vBool = other1.vBool; + vLong = other1.vLong; + vBinary = other1.vBinary; + __isset = other1.__isset; +} +Tag& Tag::operator=(const Tag& other2) { + key = other2.key; + vType = other2.vType; + vStr = other2.vStr; + vDouble = other2.vDouble; + vBool = other2.vBool; + vLong = other2.vLong; + vBinary = other2.vBinary; + __isset = other2.__isset; + return *this; +} +void Tag::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Tag("; + out << "key=" << to_string(key); + out << ", " << "vType=" << to_string(vType); + out << ", " << "vStr="; (__isset.vStr ? (out << to_string(vStr)) : (out << "<null>")); + out << ", " << "vDouble="; (__isset.vDouble ? (out << to_string(vDouble)) : (out << "<null>")); + out << ", " << "vBool="; (__isset.vBool ? (out << to_string(vBool)) : (out << "<null>")); + out << ", " << "vLong="; (__isset.vLong ? (out << to_string(vLong)) : (out << "<null>")); + out << ", " << "vBinary="; (__isset.vBinary ? (out << to_string(vBinary)) : (out << "<null>")); + out << ")"; +} + + +Log::~Log() noexcept { +} + + +void Log::__set_timestamp(const int64_t val) { + this->timestamp = val; +} + +void Log::__set_fields(const std::vector<Tag> & val) { + this->fields = val; +} +std::ostream& operator<<(std::ostream& out, const Log& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Log::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_timestamp = false; + bool isset_fields = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->timestamp); + isset_timestamp = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->fields.clear(); + uint32_t _size3; + ::apache::thrift::protocol::TType _etype6; + xfer += iprot->readListBegin(_etype6, _size3); + this->fields.resize(_size3); + uint32_t _i7; + for (_i7 = 0; _i7 < _size3; ++_i7) + { + xfer += this->fields[_i7].read(iprot); + } + xfer += iprot->readListEnd(); + } + isset_fields = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_timestamp) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_fields) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t Log::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Log"); + + xfer += oprot->writeFieldBegin("timestamp", ::apache::thrift::protocol::T_I64, 1); + xfer += oprot->writeI64(this->timestamp); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("fields", ::apache::thrift::protocol::T_LIST, 2); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->fields.size())); + std::vector<Tag> ::const_iterator _iter8; + for (_iter8 = this->fields.begin(); _iter8 != this->fields.end(); ++_iter8) + { + xfer += (*_iter8).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Log &a, Log &b) { + using ::std::swap; + swap(a.timestamp, b.timestamp); + swap(a.fields, b.fields); +} + +Log::Log(const Log& other9) { + timestamp = other9.timestamp; + fields = other9.fields; +} +Log& Log::operator=(const Log& other10) { + timestamp = other10.timestamp; + fields = other10.fields; + return *this; +} +void Log::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Log("; + out << "timestamp=" << to_string(timestamp); + out << ", " << "fields=" << to_string(fields); + out << ")"; +} + + +SpanRef::~SpanRef() noexcept { +} + + +void SpanRef::__set_refType(const SpanRefType::type val) { + this->refType = val; +} + +void SpanRef::__set_traceIdLow(const int64_t val) { + this->traceIdLow = val; +} + +void SpanRef::__set_traceIdHigh(const int64_t val) { + this->traceIdHigh = val; +} + +void SpanRef::__set_spanId(const int64_t val) { + this->spanId = val; +} +std::ostream& operator<<(std::ostream& out, const SpanRef& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t SpanRef::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_refType = false; + bool isset_traceIdLow = false; + bool isset_traceIdHigh = false; + bool isset_spanId = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_I32) { + int32_t ecast11; + xfer += iprot->readI32(ecast11); + this->refType = (SpanRefType::type)ecast11; + isset_refType = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->traceIdLow); + isset_traceIdLow = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 3: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->traceIdHigh); + isset_traceIdHigh = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 4: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->spanId); + isset_spanId = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_refType) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_traceIdLow) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_traceIdHigh) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_spanId) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t SpanRef::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("SpanRef"); + + xfer += oprot->writeFieldBegin("refType", ::apache::thrift::protocol::T_I32, 1); + xfer += oprot->writeI32((int32_t)this->refType); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("traceIdLow", ::apache::thrift::protocol::T_I64, 2); + xfer += oprot->writeI64(this->traceIdLow); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("traceIdHigh", ::apache::thrift::protocol::T_I64, 3); + xfer += oprot->writeI64(this->traceIdHigh); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("spanId", ::apache::thrift::protocol::T_I64, 4); + xfer += oprot->writeI64(this->spanId); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(SpanRef &a, SpanRef &b) { + using ::std::swap; + swap(a.refType, b.refType); + swap(a.traceIdLow, b.traceIdLow); + swap(a.traceIdHigh, b.traceIdHigh); + swap(a.spanId, b.spanId); +} + +SpanRef::SpanRef(const SpanRef& other12) { + refType = other12.refType; + traceIdLow = other12.traceIdLow; + traceIdHigh = other12.traceIdHigh; + spanId = other12.spanId; +} +SpanRef& SpanRef::operator=(const SpanRef& other13) { + refType = other13.refType; + traceIdLow = other13.traceIdLow; + traceIdHigh = other13.traceIdHigh; + spanId = other13.spanId; + return *this; +} +void SpanRef::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "SpanRef("; + out << "refType=" << to_string(refType); + out << ", " << "traceIdLow=" << to_string(traceIdLow); + out << ", " << "traceIdHigh=" << to_string(traceIdHigh); + out << ", " << "spanId=" << to_string(spanId); + out << ")"; +} + + +Span::~Span() noexcept { +} + + +void Span::__set_traceIdLow(const int64_t val) { + this->traceIdLow = val; +} + +void Span::__set_traceIdHigh(const int64_t val) { + this->traceIdHigh = val; +} + +void Span::__set_spanId(const int64_t val) { + this->spanId = val; +} + +void Span::__set_parentSpanId(const int64_t val) { + this->parentSpanId = val; +} + +void Span::__set_operationName(const std::string& val) { + this->operationName = val; +} + +void Span::__set_references(const std::vector<SpanRef> & val) { + this->references = val; +__isset.references = true; +} + +void Span::__set_flags(const int32_t val) { + this->flags = val; +} + +void Span::__set_startTime(const int64_t val) { + this->startTime = val; +} + +void Span::__set_duration(const int64_t val) { + this->duration = val; +} + +void Span::__set_tags(const std::vector<Tag> & val) { + this->tags = val; +__isset.tags = true; +} + +void Span::__set_logs(const std::vector<Log> & val) { + this->logs = val; +__isset.logs = true; +} +std::ostream& operator<<(std::ostream& out, const Span& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Span::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_traceIdLow = false; + bool isset_traceIdHigh = false; + bool isset_spanId = false; + bool isset_parentSpanId = false; + bool isset_operationName = false; + bool isset_flags = false; + bool isset_startTime = false; + bool isset_duration = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->traceIdLow); + isset_traceIdLow = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->traceIdHigh); + isset_traceIdHigh = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 3: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->spanId); + isset_spanId = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 4: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->parentSpanId); + isset_parentSpanId = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 5: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->operationName); + isset_operationName = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 6: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->references.clear(); + uint32_t _size14; + ::apache::thrift::protocol::TType _etype17; + xfer += iprot->readListBegin(_etype17, _size14); + this->references.resize(_size14); + uint32_t _i18; + for (_i18 = 0; _i18 < _size14; ++_i18) + { + xfer += this->references[_i18].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.references = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 7: + if (ftype == ::apache::thrift::protocol::T_I32) { + xfer += iprot->readI32(this->flags); + isset_flags = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 8: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->startTime); + isset_startTime = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 9: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->duration); + isset_duration = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 10: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->tags.clear(); + uint32_t _size19; + ::apache::thrift::protocol::TType _etype22; + xfer += iprot->readListBegin(_etype22, _size19); + this->tags.resize(_size19); + uint32_t _i23; + for (_i23 = 0; _i23 < _size19; ++_i23) + { + xfer += this->tags[_i23].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.tags = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 11: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->logs.clear(); + uint32_t _size24; + ::apache::thrift::protocol::TType _etype27; + xfer += iprot->readListBegin(_etype27, _size24); + this->logs.resize(_size24); + uint32_t _i28; + for (_i28 = 0; _i28 < _size24; ++_i28) + { + xfer += this->logs[_i28].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.logs = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_traceIdLow) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_traceIdHigh) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_spanId) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_parentSpanId) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_operationName) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_flags) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_startTime) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_duration) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t Span::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Span"); + + xfer += oprot->writeFieldBegin("traceIdLow", ::apache::thrift::protocol::T_I64, 1); + xfer += oprot->writeI64(this->traceIdLow); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("traceIdHigh", ::apache::thrift::protocol::T_I64, 2); + xfer += oprot->writeI64(this->traceIdHigh); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("spanId", ::apache::thrift::protocol::T_I64, 3); + xfer += oprot->writeI64(this->spanId); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("parentSpanId", ::apache::thrift::protocol::T_I64, 4); + xfer += oprot->writeI64(this->parentSpanId); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("operationName", ::apache::thrift::protocol::T_STRING, 5); + xfer += oprot->writeString(this->operationName); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.references) { + xfer += oprot->writeFieldBegin("references", ::apache::thrift::protocol::T_LIST, 6); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->references.size())); + std::vector<SpanRef> ::const_iterator _iter29; + for (_iter29 = this->references.begin(); _iter29 != this->references.end(); ++_iter29) + { + xfer += (*_iter29).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldBegin("flags", ::apache::thrift::protocol::T_I32, 7); + xfer += oprot->writeI32(this->flags); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("startTime", ::apache::thrift::protocol::T_I64, 8); + xfer += oprot->writeI64(this->startTime); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("duration", ::apache::thrift::protocol::T_I64, 9); + xfer += oprot->writeI64(this->duration); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.tags) { + xfer += oprot->writeFieldBegin("tags", ::apache::thrift::protocol::T_LIST, 10); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->tags.size())); + std::vector<Tag> ::const_iterator _iter30; + for (_iter30 = this->tags.begin(); _iter30 != this->tags.end(); ++_iter30) + { + xfer += (*_iter30).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.logs) { + xfer += oprot->writeFieldBegin("logs", ::apache::thrift::protocol::T_LIST, 11); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->logs.size())); + std::vector<Log> ::const_iterator _iter31; + for (_iter31 = this->logs.begin(); _iter31 != this->logs.end(); ++_iter31) + { + xfer += (*_iter31).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Span &a, Span &b) { + using ::std::swap; + swap(a.traceIdLow, b.traceIdLow); + swap(a.traceIdHigh, b.traceIdHigh); + swap(a.spanId, b.spanId); + swap(a.parentSpanId, b.parentSpanId); + swap(a.operationName, b.operationName); + swap(a.references, b.references); + swap(a.flags, b.flags); + swap(a.startTime, b.startTime); + swap(a.duration, b.duration); + swap(a.tags, b.tags); + swap(a.logs, b.logs); + swap(a.__isset, b.__isset); +} + +Span::Span(const Span& other32) { + traceIdLow = other32.traceIdLow; + traceIdHigh = other32.traceIdHigh; + spanId = other32.spanId; + parentSpanId = other32.parentSpanId; + operationName = other32.operationName; + references = other32.references; + flags = other32.flags; + startTime = other32.startTime; + duration = other32.duration; + tags = other32.tags; + logs = other32.logs; + __isset = other32.__isset; +} +Span& Span::operator=(const Span& other33) { + traceIdLow = other33.traceIdLow; + traceIdHigh = other33.traceIdHigh; + spanId = other33.spanId; + parentSpanId = other33.parentSpanId; + operationName = other33.operationName; + references = other33.references; + flags = other33.flags; + startTime = other33.startTime; + duration = other33.duration; + tags = other33.tags; + logs = other33.logs; + __isset = other33.__isset; + return *this; +} +void Span::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Span("; + out << "traceIdLow=" << to_string(traceIdLow); + out << ", " << "traceIdHigh=" << to_string(traceIdHigh); + out << ", " << "spanId=" << to_string(spanId); + out << ", " << "parentSpanId=" << to_string(parentSpanId); + out << ", " << "operationName=" << to_string(operationName); + out << ", " << "references="; (__isset.references ? (out << to_string(references)) : (out << "<null>")); + out << ", " << "flags=" << to_string(flags); + out << ", " << "startTime=" << to_string(startTime); + out << ", " << "duration=" << to_string(duration); + out << ", " << "tags="; (__isset.tags ? (out << to_string(tags)) : (out << "<null>")); + out << ", " << "logs="; (__isset.logs ? (out << to_string(logs)) : (out << "<null>")); + out << ")"; +} + + +Process::~Process() noexcept { +} + + +void Process::__set_serviceName(const std::string& val) { + this->serviceName = val; +} + +void Process::__set_tags(const std::vector<Tag> & val) { + this->tags = val; +__isset.tags = true; +} +std::ostream& operator<<(std::ostream& out, const Process& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Process::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_serviceName = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->serviceName); + isset_serviceName = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->tags.clear(); + uint32_t _size34; + ::apache::thrift::protocol::TType _etype37; + xfer += iprot->readListBegin(_etype37, _size34); + this->tags.resize(_size34); + uint32_t _i38; + for (_i38 = 0; _i38 < _size34; ++_i38) + { + xfer += this->tags[_i38].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.tags = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_serviceName) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t Process::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Process"); + + xfer += oprot->writeFieldBegin("serviceName", ::apache::thrift::protocol::T_STRING, 1); + xfer += oprot->writeString(this->serviceName); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.tags) { + xfer += oprot->writeFieldBegin("tags", ::apache::thrift::protocol::T_LIST, 2); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->tags.size())); + std::vector<Tag> ::const_iterator _iter39; + for (_iter39 = this->tags.begin(); _iter39 != this->tags.end(); ++_iter39) + { + xfer += (*_iter39).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Process &a, Process &b) { + using ::std::swap; + swap(a.serviceName, b.serviceName); + swap(a.tags, b.tags); + swap(a.__isset, b.__isset); +} + +Process::Process(const Process& other40) { + serviceName = other40.serviceName; + tags = other40.tags; + __isset = other40.__isset; +} +Process& Process::operator=(const Process& other41) { + serviceName = other41.serviceName; + tags = other41.tags; + __isset = other41.__isset; + return *this; +} +void Process::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Process("; + out << "serviceName=" << to_string(serviceName); + out << ", " << "tags="; (__isset.tags ? (out << to_string(tags)) : (out << "<null>")); + out << ")"; +} + + +Batch::~Batch() noexcept { +} + + +void Batch::__set_process(const Process& val) { + this->process = val; +} + +void Batch::__set_spans(const std::vector<Span> & val) { + this->spans = val; +} +std::ostream& operator<<(std::ostream& out, const Batch& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Batch::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_process = false; + bool isset_spans = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_STRUCT) { + xfer += this->process.read(iprot); + isset_process = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->spans.clear(); + uint32_t _size42; + ::apache::thrift::protocol::TType _etype45; + xfer += iprot->readListBegin(_etype45, _size42); + this->spans.resize(_size42); + uint32_t _i46; + for (_i46 = 0; _i46 < _size42; ++_i46) + { + xfer += this->spans[_i46].read(iprot); + } + xfer += iprot->readListEnd(); + } + isset_spans = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_process) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_spans) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t Batch::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Batch"); + + xfer += oprot->writeFieldBegin("process", ::apache::thrift::protocol::T_STRUCT, 1); + xfer += this->process.write(oprot); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("spans", ::apache::thrift::protocol::T_LIST, 2); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->spans.size())); + std::vector<Span> ::const_iterator _iter47; + for (_iter47 = this->spans.begin(); _iter47 != this->spans.end(); ++_iter47) + { + xfer += (*_iter47).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Batch &a, Batch &b) { + using ::std::swap; + swap(a.process, b.process); + swap(a.spans, b.spans); +} + +Batch::Batch(const Batch& other48) { + process = other48.process; + spans = other48.spans; +} +Batch& Batch::operator=(const Batch& other49) { + process = other49.process; + spans = other49.spans; + return *this; +} +void Batch::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Batch("; + out << "process=" << to_string(process); + out << ", " << "spans=" << to_string(spans); + out << ")"; +} + + +BatchSubmitResponse::~BatchSubmitResponse() noexcept { +} + + +void BatchSubmitResponse::__set_ok(const bool val) { + this->ok = val; +} +std::ostream& operator<<(std::ostream& out, const BatchSubmitResponse& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t BatchSubmitResponse::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_ok = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_BOOL) { + xfer += iprot->readBool(this->ok); + isset_ok = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_ok) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t BatchSubmitResponse::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("BatchSubmitResponse"); + + xfer += oprot->writeFieldBegin("ok", ::apache::thrift::protocol::T_BOOL, 1); + xfer += oprot->writeBool(this->ok); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(BatchSubmitResponse &a, BatchSubmitResponse &b) { + using ::std::swap; + swap(a.ok, b.ok); +} + +BatchSubmitResponse::BatchSubmitResponse(const BatchSubmitResponse& other50) { + ok = other50.ok; +} +BatchSubmitResponse& BatchSubmitResponse::operator=(const BatchSubmitResponse& other51) { + ok = other51.ok; + return *this; +} +void BatchSubmitResponse::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "BatchSubmitResponse("; + out << "ok=" << to_string(ok); + out << ")"; +} + +}} // namespace diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/jaeger_types.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/jaeger_types.h new file mode 100644 index 000000000..b30a3740b --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/jaeger_types.h @@ -0,0 +1,481 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#ifndef jaeger_TYPES_H +#define jaeger_TYPES_H + +#include <iosfwd> + +#include <thrift/Thrift.h> +#include <thrift/TApplicationException.h> +#include <thrift/TBase.h> +#include <thrift/protocol/TProtocol.h> +#include <thrift/transport/TTransport.h> + +#include <functional> +#include <memory> + + +namespace jaegertracing { namespace thrift { + +struct TagType { + enum type { + STRING = 0, + DOUBLE = 1, + BOOL = 2, + LONG = 3, + BINARY = 4 + }; +}; + +extern const std::map<int, const char*> _TagType_VALUES_TO_NAMES; + +std::ostream& operator<<(std::ostream& out, const TagType::type& val); + +std::string to_string(const TagType::type& val); + +struct SpanRefType { + enum type { + CHILD_OF = 0, + FOLLOWS_FROM = 1 + }; +}; + +extern const std::map<int, const char*> _SpanRefType_VALUES_TO_NAMES; + +std::ostream& operator<<(std::ostream& out, const SpanRefType::type& val); + +std::string to_string(const SpanRefType::type& val); + +class Tag; + +class Log; + +class SpanRef; + +class Span; + +class Process; + +class Batch; + +class BatchSubmitResponse; + +typedef struct _Tag__isset { + _Tag__isset() : vStr(false), vDouble(false), vBool(false), vLong(false), vBinary(false) {} + bool vStr :1; + bool vDouble :1; + bool vBool :1; + bool vLong :1; + bool vBinary :1; +} _Tag__isset; + +class Tag : public virtual ::apache::thrift::TBase { + public: + + Tag(const Tag&); + Tag& operator=(const Tag&); + Tag() : key(), vType((TagType::type)0), vStr(), vDouble(0), vBool(0), vLong(0), vBinary() { + } + + virtual ~Tag() noexcept; + std::string key; + /** + * + * @see TagType + */ + TagType::type vType; + std::string vStr; + double vDouble; + bool vBool; + int64_t vLong; + std::string vBinary; + + _Tag__isset __isset; + + void __set_key(const std::string& val); + + void __set_vType(const TagType::type val); + + void __set_vStr(const std::string& val); + + void __set_vDouble(const double val); + + void __set_vBool(const bool val); + + void __set_vLong(const int64_t val); + + void __set_vBinary(const std::string& val); + + bool operator == (const Tag & rhs) const + { + if (!(key == rhs.key)) + return false; + if (!(vType == rhs.vType)) + return false; + if (__isset.vStr != rhs.__isset.vStr) + return false; + else if (__isset.vStr && !(vStr == rhs.vStr)) + return false; + if (__isset.vDouble != rhs.__isset.vDouble) + return false; + else if (__isset.vDouble && !(vDouble == rhs.vDouble)) + return false; + if (__isset.vBool != rhs.__isset.vBool) + return false; + else if (__isset.vBool && !(vBool == rhs.vBool)) + return false; + if (__isset.vLong != rhs.__isset.vLong) + return false; + else if (__isset.vLong && !(vLong == rhs.vLong)) + return false; + if (__isset.vBinary != rhs.__isset.vBinary) + return false; + else if (__isset.vBinary && !(vBinary == rhs.vBinary)) + return false; + return true; + } + bool operator != (const Tag &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Tag & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Tag &a, Tag &b); + +std::ostream& operator<<(std::ostream& out, const Tag& obj); + + +class Log : public virtual ::apache::thrift::TBase { + public: + + Log(const Log&); + Log& operator=(const Log&); + Log() : timestamp(0) { + } + + virtual ~Log() noexcept; + int64_t timestamp; + std::vector<Tag> fields; + + void __set_timestamp(const int64_t val); + + void __set_fields(const std::vector<Tag> & val); + + bool operator == (const Log & rhs) const + { + if (!(timestamp == rhs.timestamp)) + return false; + if (!(fields == rhs.fields)) + return false; + return true; + } + bool operator != (const Log &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Log & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Log &a, Log &b); + +std::ostream& operator<<(std::ostream& out, const Log& obj); + + +class SpanRef : public virtual ::apache::thrift::TBase { + public: + + SpanRef(const SpanRef&); + SpanRef& operator=(const SpanRef&); + SpanRef() : refType((SpanRefType::type)0), traceIdLow(0), traceIdHigh(0), spanId(0) { + } + + virtual ~SpanRef() noexcept; + /** + * + * @see SpanRefType + */ + SpanRefType::type refType; + int64_t traceIdLow; + int64_t traceIdHigh; + int64_t spanId; + + void __set_refType(const SpanRefType::type val); + + void __set_traceIdLow(const int64_t val); + + void __set_traceIdHigh(const int64_t val); + + void __set_spanId(const int64_t val); + + bool operator == (const SpanRef & rhs) const + { + if (!(refType == rhs.refType)) + return false; + if (!(traceIdLow == rhs.traceIdLow)) + return false; + if (!(traceIdHigh == rhs.traceIdHigh)) + return false; + if (!(spanId == rhs.spanId)) + return false; + return true; + } + bool operator != (const SpanRef &rhs) const { + return !(*this == rhs); + } + + bool operator < (const SpanRef & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(SpanRef &a, SpanRef &b); + +std::ostream& operator<<(std::ostream& out, const SpanRef& obj); + +typedef struct _Span__isset { + _Span__isset() : references(false), tags(false), logs(false) {} + bool references :1; + bool tags :1; + bool logs :1; +} _Span__isset; + +class Span : public virtual ::apache::thrift::TBase { + public: + + Span(const Span&); + Span& operator=(const Span&); + Span() : traceIdLow(0), traceIdHigh(0), spanId(0), parentSpanId(0), operationName(), flags(0), startTime(0), duration(0) { + } + + virtual ~Span() noexcept; + int64_t traceIdLow; + int64_t traceIdHigh; + int64_t spanId; + int64_t parentSpanId; + std::string operationName; + std::vector<SpanRef> references; + int32_t flags; + int64_t startTime; + int64_t duration; + std::vector<Tag> tags; + std::vector<Log> logs; + + _Span__isset __isset; + + void __set_traceIdLow(const int64_t val); + + void __set_traceIdHigh(const int64_t val); + + void __set_spanId(const int64_t val); + + void __set_parentSpanId(const int64_t val); + + void __set_operationName(const std::string& val); + + void __set_references(const std::vector<SpanRef> & val); + + void __set_flags(const int32_t val); + + void __set_startTime(const int64_t val); + + void __set_duration(const int64_t val); + + void __set_tags(const std::vector<Tag> & val); + + void __set_logs(const std::vector<Log> & val); + + bool operator == (const Span & rhs) const + { + if (!(traceIdLow == rhs.traceIdLow)) + return false; + if (!(traceIdHigh == rhs.traceIdHigh)) + return false; + if (!(spanId == rhs.spanId)) + return false; + if (!(parentSpanId == rhs.parentSpanId)) + return false; + if (!(operationName == rhs.operationName)) + return false; + if (__isset.references != rhs.__isset.references) + return false; + else if (__isset.references && !(references == rhs.references)) + return false; + if (!(flags == rhs.flags)) + return false; + if (!(startTime == rhs.startTime)) + return false; + if (!(duration == rhs.duration)) + return false; + if (__isset.tags != rhs.__isset.tags) + return false; + else if (__isset.tags && !(tags == rhs.tags)) + return false; + if (__isset.logs != rhs.__isset.logs) + return false; + else if (__isset.logs && !(logs == rhs.logs)) + return false; + return true; + } + bool operator != (const Span &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Span & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Span &a, Span &b); + +std::ostream& operator<<(std::ostream& out, const Span& obj); + +typedef struct _Process__isset { + _Process__isset() : tags(false) {} + bool tags :1; +} _Process__isset; + +class Process : public virtual ::apache::thrift::TBase { + public: + + Process(const Process&); + Process& operator=(const Process&); + Process() : serviceName() { + } + + virtual ~Process() noexcept; + std::string serviceName; + std::vector<Tag> tags; + + _Process__isset __isset; + + void __set_serviceName(const std::string& val); + + void __set_tags(const std::vector<Tag> & val); + + bool operator == (const Process & rhs) const + { + if (!(serviceName == rhs.serviceName)) + return false; + if (__isset.tags != rhs.__isset.tags) + return false; + else if (__isset.tags && !(tags == rhs.tags)) + return false; + return true; + } + bool operator != (const Process &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Process & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Process &a, Process &b); + +std::ostream& operator<<(std::ostream& out, const Process& obj); + + +class Batch : public virtual ::apache::thrift::TBase { + public: + + Batch(const Batch&); + Batch& operator=(const Batch&); + Batch() { + } + + virtual ~Batch() noexcept; + Process process; + std::vector<Span> spans; + + void __set_process(const Process& val); + + void __set_spans(const std::vector<Span> & val); + + bool operator == (const Batch & rhs) const + { + if (!(process == rhs.process)) + return false; + if (!(spans == rhs.spans)) + return false; + return true; + } + bool operator != (const Batch &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Batch & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Batch &a, Batch &b); + +std::ostream& operator<<(std::ostream& out, const Batch& obj); + + +class BatchSubmitResponse : public virtual ::apache::thrift::TBase { + public: + + BatchSubmitResponse(const BatchSubmitResponse&); + BatchSubmitResponse& operator=(const BatchSubmitResponse&); + BatchSubmitResponse() : ok(0) { + } + + virtual ~BatchSubmitResponse() noexcept; + bool ok; + + void __set_ok(const bool val); + + bool operator == (const BatchSubmitResponse & rhs) const + { + if (!(ok == rhs.ok)) + return false; + return true; + } + bool operator != (const BatchSubmitResponse &rhs) const { + return !(*this == rhs); + } + + bool operator < (const BatchSubmitResponse & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(BatchSubmitResponse &a, BatchSubmitResponse &b); + +std::ostream& operator<<(std::ostream& out, const BatchSubmitResponse& obj); + +}} // namespace + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_constants.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_constants.cpp new file mode 100644 index 000000000..8dd451e48 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_constants.cpp @@ -0,0 +1,49 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#include "zipkincore_constants.h" + +namespace twitter { namespace zipkin { namespace thrift { + +const zipkincoreConstants g_zipkincore_constants; + +zipkincoreConstants::zipkincoreConstants() { + CLIENT_SEND = "cs"; + + CLIENT_RECV = "cr"; + + SERVER_SEND = "ss"; + + SERVER_RECV = "sr"; + + MESSAGE_SEND = "ms"; + + MESSAGE_RECV = "mr"; + + WIRE_SEND = "ws"; + + WIRE_RECV = "wr"; + + CLIENT_SEND_FRAGMENT = "csf"; + + CLIENT_RECV_FRAGMENT = "crf"; + + SERVER_SEND_FRAGMENT = "ssf"; + + SERVER_RECV_FRAGMENT = "srf"; + + LOCAL_COMPONENT = "lc"; + + CLIENT_ADDR = "ca"; + + SERVER_ADDR = "sa"; + + MESSAGE_ADDR = "ma"; + +} + +}}} // namespace + diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_constants.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_constants.h new file mode 100644 index 000000000..22b4b3b67 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_constants.h @@ -0,0 +1,40 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#ifndef zipkincore_CONSTANTS_H +#define zipkincore_CONSTANTS_H + +#include "zipkincore_types.h" + +namespace twitter { namespace zipkin { namespace thrift { + +class zipkincoreConstants { + public: + zipkincoreConstants(); + + std::string CLIENT_SEND; + std::string CLIENT_RECV; + std::string SERVER_SEND; + std::string SERVER_RECV; + std::string MESSAGE_SEND; + std::string MESSAGE_RECV; + std::string WIRE_SEND; + std::string WIRE_RECV; + std::string CLIENT_SEND_FRAGMENT; + std::string CLIENT_RECV_FRAGMENT; + std::string SERVER_SEND_FRAGMENT; + std::string SERVER_RECV_FRAGMENT; + std::string LOCAL_COMPONENT; + std::string CLIENT_ADDR; + std::string SERVER_ADDR; + std::string MESSAGE_ADDR; +}; + +extern const zipkincoreConstants g_zipkincore_constants; + +}}} // namespace + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_types.cpp b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_types.cpp new file mode 100644 index 000000000..a8b52e902 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_types.cpp @@ -0,0 +1,913 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#include "zipkincore_types.h" + +#include <algorithm> +#include <ostream> + +#include <thrift/TToString.h> + +namespace twitter { namespace zipkin { namespace thrift { + +int _kAnnotationTypeValues[] = { + AnnotationType::BOOL, + AnnotationType::BYTES, + AnnotationType::I16, + AnnotationType::I32, + AnnotationType::I64, + AnnotationType::DOUBLE, + AnnotationType::STRING +}; +const char* _kAnnotationTypeNames[] = { + "BOOL", + "BYTES", + "I16", + "I32", + "I64", + "DOUBLE", + "STRING" +}; +const std::map<int, const char*> _AnnotationType_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(7, _kAnnotationTypeValues, _kAnnotationTypeNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); + +std::ostream& operator<<(std::ostream& out, const AnnotationType::type& val) { + std::map<int, const char*>::const_iterator it = _AnnotationType_VALUES_TO_NAMES.find(val); + if (it != _AnnotationType_VALUES_TO_NAMES.end()) { + out << it->second; + } else { + out << static_cast<int>(val); + } + return out; +} + +std::string to_string(const AnnotationType::type& val) { + std::map<int, const char*>::const_iterator it = _AnnotationType_VALUES_TO_NAMES.find(val); + if (it != _AnnotationType_VALUES_TO_NAMES.end()) { + return std::string(it->second); + } else { + return std::to_string(static_cast<int>(val)); + } +} + + +Endpoint::~Endpoint() noexcept { +} + + +void Endpoint::__set_ipv4(const int32_t val) { + this->ipv4 = val; +} + +void Endpoint::__set_port(const int16_t val) { + this->port = val; +} + +void Endpoint::__set_service_name(const std::string& val) { + this->service_name = val; +} + +void Endpoint::__set_ipv6(const std::string& val) { + this->ipv6 = val; +__isset.ipv6 = true; +} +std::ostream& operator<<(std::ostream& out, const Endpoint& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Endpoint::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_I32) { + xfer += iprot->readI32(this->ipv4); + this->__isset.ipv4 = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_I16) { + xfer += iprot->readI16(this->port); + this->__isset.port = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 3: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->service_name); + this->__isset.service_name = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 4: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readBinary(this->ipv6); + this->__isset.ipv6 = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t Endpoint::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Endpoint"); + + xfer += oprot->writeFieldBegin("ipv4", ::apache::thrift::protocol::T_I32, 1); + xfer += oprot->writeI32(this->ipv4); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("port", ::apache::thrift::protocol::T_I16, 2); + xfer += oprot->writeI16(this->port); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("service_name", ::apache::thrift::protocol::T_STRING, 3); + xfer += oprot->writeString(this->service_name); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.ipv6) { + xfer += oprot->writeFieldBegin("ipv6", ::apache::thrift::protocol::T_STRING, 4); + xfer += oprot->writeBinary(this->ipv6); + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Endpoint &a, Endpoint &b) { + using ::std::swap; + swap(a.ipv4, b.ipv4); + swap(a.port, b.port); + swap(a.service_name, b.service_name); + swap(a.ipv6, b.ipv6); + swap(a.__isset, b.__isset); +} + +Endpoint::Endpoint(const Endpoint& other0) { + ipv4 = other0.ipv4; + port = other0.port; + service_name = other0.service_name; + ipv6 = other0.ipv6; + __isset = other0.__isset; +} +Endpoint& Endpoint::operator=(const Endpoint& other1) { + ipv4 = other1.ipv4; + port = other1.port; + service_name = other1.service_name; + ipv6 = other1.ipv6; + __isset = other1.__isset; + return *this; +} +void Endpoint::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Endpoint("; + out << "ipv4=" << to_string(ipv4); + out << ", " << "port=" << to_string(port); + out << ", " << "service_name=" << to_string(service_name); + out << ", " << "ipv6="; (__isset.ipv6 ? (out << to_string(ipv6)) : (out << "<null>")); + out << ")"; +} + + +Annotation::~Annotation() noexcept { +} + + +void Annotation::__set_timestamp(const int64_t val) { + this->timestamp = val; +} + +void Annotation::__set_value(const std::string& val) { + this->value = val; +} + +void Annotation::__set_host(const Endpoint& val) { + this->host = val; +__isset.host = true; +} +std::ostream& operator<<(std::ostream& out, const Annotation& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Annotation::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->timestamp); + this->__isset.timestamp = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->value); + this->__isset.value = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 3: + if (ftype == ::apache::thrift::protocol::T_STRUCT) { + xfer += this->host.read(iprot); + this->__isset.host = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t Annotation::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Annotation"); + + xfer += oprot->writeFieldBegin("timestamp", ::apache::thrift::protocol::T_I64, 1); + xfer += oprot->writeI64(this->timestamp); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("value", ::apache::thrift::protocol::T_STRING, 2); + xfer += oprot->writeString(this->value); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.host) { + xfer += oprot->writeFieldBegin("host", ::apache::thrift::protocol::T_STRUCT, 3); + xfer += this->host.write(oprot); + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Annotation &a, Annotation &b) { + using ::std::swap; + swap(a.timestamp, b.timestamp); + swap(a.value, b.value); + swap(a.host, b.host); + swap(a.__isset, b.__isset); +} + +Annotation::Annotation(const Annotation& other2) { + timestamp = other2.timestamp; + value = other2.value; + host = other2.host; + __isset = other2.__isset; +} +Annotation& Annotation::operator=(const Annotation& other3) { + timestamp = other3.timestamp; + value = other3.value; + host = other3.host; + __isset = other3.__isset; + return *this; +} +void Annotation::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Annotation("; + out << "timestamp=" << to_string(timestamp); + out << ", " << "value=" << to_string(value); + out << ", " << "host="; (__isset.host ? (out << to_string(host)) : (out << "<null>")); + out << ")"; +} + + +BinaryAnnotation::~BinaryAnnotation() noexcept { +} + + +void BinaryAnnotation::__set_key(const std::string& val) { + this->key = val; +} + +void BinaryAnnotation::__set_value(const std::string& val) { + this->value = val; +} + +void BinaryAnnotation::__set_annotation_type(const AnnotationType::type val) { + this->annotation_type = val; +} + +void BinaryAnnotation::__set_host(const Endpoint& val) { + this->host = val; +__isset.host = true; +} +std::ostream& operator<<(std::ostream& out, const BinaryAnnotation& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t BinaryAnnotation::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->key); + this->__isset.key = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readBinary(this->value); + this->__isset.value = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 3: + if (ftype == ::apache::thrift::protocol::T_I32) { + int32_t ecast4; + xfer += iprot->readI32(ecast4); + this->annotation_type = (AnnotationType::type)ecast4; + this->__isset.annotation_type = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 4: + if (ftype == ::apache::thrift::protocol::T_STRUCT) { + xfer += this->host.read(iprot); + this->__isset.host = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t BinaryAnnotation::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("BinaryAnnotation"); + + xfer += oprot->writeFieldBegin("key", ::apache::thrift::protocol::T_STRING, 1); + xfer += oprot->writeString(this->key); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("value", ::apache::thrift::protocol::T_STRING, 2); + xfer += oprot->writeBinary(this->value); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("annotation_type", ::apache::thrift::protocol::T_I32, 3); + xfer += oprot->writeI32((int32_t)this->annotation_type); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.host) { + xfer += oprot->writeFieldBegin("host", ::apache::thrift::protocol::T_STRUCT, 4); + xfer += this->host.write(oprot); + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(BinaryAnnotation &a, BinaryAnnotation &b) { + using ::std::swap; + swap(a.key, b.key); + swap(a.value, b.value); + swap(a.annotation_type, b.annotation_type); + swap(a.host, b.host); + swap(a.__isset, b.__isset); +} + +BinaryAnnotation::BinaryAnnotation(const BinaryAnnotation& other5) { + key = other5.key; + value = other5.value; + annotation_type = other5.annotation_type; + host = other5.host; + __isset = other5.__isset; +} +BinaryAnnotation& BinaryAnnotation::operator=(const BinaryAnnotation& other6) { + key = other6.key; + value = other6.value; + annotation_type = other6.annotation_type; + host = other6.host; + __isset = other6.__isset; + return *this; +} +void BinaryAnnotation::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "BinaryAnnotation("; + out << "key=" << to_string(key); + out << ", " << "value=" << to_string(value); + out << ", " << "annotation_type=" << to_string(annotation_type); + out << ", " << "host="; (__isset.host ? (out << to_string(host)) : (out << "<null>")); + out << ")"; +} + + +Span::~Span() noexcept { +} + + +void Span::__set_trace_id(const int64_t val) { + this->trace_id = val; +} + +void Span::__set_name(const std::string& val) { + this->name = val; +} + +void Span::__set_id(const int64_t val) { + this->id = val; +} + +void Span::__set_parent_id(const int64_t val) { + this->parent_id = val; +__isset.parent_id = true; +} + +void Span::__set_annotations(const std::vector<Annotation> & val) { + this->annotations = val; +} + +void Span::__set_binary_annotations(const std::vector<BinaryAnnotation> & val) { + this->binary_annotations = val; +} + +void Span::__set_debug(const bool val) { + this->debug = val; +__isset.debug = true; +} + +void Span::__set_timestamp(const int64_t val) { + this->timestamp = val; +__isset.timestamp = true; +} + +void Span::__set_duration(const int64_t val) { + this->duration = val; +__isset.duration = true; +} + +void Span::__set_trace_id_high(const int64_t val) { + this->trace_id_high = val; +__isset.trace_id_high = true; +} +std::ostream& operator<<(std::ostream& out, const Span& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Span::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->trace_id); + this->__isset.trace_id = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 3: + if (ftype == ::apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(this->name); + this->__isset.name = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 4: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->id); + this->__isset.id = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 5: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->parent_id); + this->__isset.parent_id = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 6: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->annotations.clear(); + uint32_t _size7; + ::apache::thrift::protocol::TType _etype10; + xfer += iprot->readListBegin(_etype10, _size7); + this->annotations.resize(_size7); + uint32_t _i11; + for (_i11 = 0; _i11 < _size7; ++_i11) + { + xfer += this->annotations[_i11].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.annotations = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 8: + if (ftype == ::apache::thrift::protocol::T_LIST) { + { + this->binary_annotations.clear(); + uint32_t _size12; + ::apache::thrift::protocol::TType _etype15; + xfer += iprot->readListBegin(_etype15, _size12); + this->binary_annotations.resize(_size12); + uint32_t _i16; + for (_i16 = 0; _i16 < _size12; ++_i16) + { + xfer += this->binary_annotations[_i16].read(iprot); + } + xfer += iprot->readListEnd(); + } + this->__isset.binary_annotations = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 9: + if (ftype == ::apache::thrift::protocol::T_BOOL) { + xfer += iprot->readBool(this->debug); + this->__isset.debug = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 10: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->timestamp); + this->__isset.timestamp = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 11: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->duration); + this->__isset.duration = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 12: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->trace_id_high); + this->__isset.trace_id_high = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + return xfer; +} + +uint32_t Span::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Span"); + + xfer += oprot->writeFieldBegin("trace_id", ::apache::thrift::protocol::T_I64, 1); + xfer += oprot->writeI64(this->trace_id); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("name", ::apache::thrift::protocol::T_STRING, 3); + xfer += oprot->writeString(this->name); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("id", ::apache::thrift::protocol::T_I64, 4); + xfer += oprot->writeI64(this->id); + xfer += oprot->writeFieldEnd(); + + if (this->__isset.parent_id) { + xfer += oprot->writeFieldBegin("parent_id", ::apache::thrift::protocol::T_I64, 5); + xfer += oprot->writeI64(this->parent_id); + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldBegin("annotations", ::apache::thrift::protocol::T_LIST, 6); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->annotations.size())); + std::vector<Annotation> ::const_iterator _iter17; + for (_iter17 = this->annotations.begin(); _iter17 != this->annotations.end(); ++_iter17) + { + xfer += (*_iter17).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("binary_annotations", ::apache::thrift::protocol::T_LIST, 8); + { + xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast<uint32_t>(this->binary_annotations.size())); + std::vector<BinaryAnnotation> ::const_iterator _iter18; + for (_iter18 = this->binary_annotations.begin(); _iter18 != this->binary_annotations.end(); ++_iter18) + { + xfer += (*_iter18).write(oprot); + } + xfer += oprot->writeListEnd(); + } + xfer += oprot->writeFieldEnd(); + + if (this->__isset.debug) { + xfer += oprot->writeFieldBegin("debug", ::apache::thrift::protocol::T_BOOL, 9); + xfer += oprot->writeBool(this->debug); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.timestamp) { + xfer += oprot->writeFieldBegin("timestamp", ::apache::thrift::protocol::T_I64, 10); + xfer += oprot->writeI64(this->timestamp); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.duration) { + xfer += oprot->writeFieldBegin("duration", ::apache::thrift::protocol::T_I64, 11); + xfer += oprot->writeI64(this->duration); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.trace_id_high) { + xfer += oprot->writeFieldBegin("trace_id_high", ::apache::thrift::protocol::T_I64, 12); + xfer += oprot->writeI64(this->trace_id_high); + xfer += oprot->writeFieldEnd(); + } + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Span &a, Span &b) { + using ::std::swap; + swap(a.trace_id, b.trace_id); + swap(a.name, b.name); + swap(a.id, b.id); + swap(a.parent_id, b.parent_id); + swap(a.annotations, b.annotations); + swap(a.binary_annotations, b.binary_annotations); + swap(a.debug, b.debug); + swap(a.timestamp, b.timestamp); + swap(a.duration, b.duration); + swap(a.trace_id_high, b.trace_id_high); + swap(a.__isset, b.__isset); +} + +Span::Span(const Span& other19) { + trace_id = other19.trace_id; + name = other19.name; + id = other19.id; + parent_id = other19.parent_id; + annotations = other19.annotations; + binary_annotations = other19.binary_annotations; + debug = other19.debug; + timestamp = other19.timestamp; + duration = other19.duration; + trace_id_high = other19.trace_id_high; + __isset = other19.__isset; +} +Span& Span::operator=(const Span& other20) { + trace_id = other20.trace_id; + name = other20.name; + id = other20.id; + parent_id = other20.parent_id; + annotations = other20.annotations; + binary_annotations = other20.binary_annotations; + debug = other20.debug; + timestamp = other20.timestamp; + duration = other20.duration; + trace_id_high = other20.trace_id_high; + __isset = other20.__isset; + return *this; +} +void Span::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Span("; + out << "trace_id=" << to_string(trace_id); + out << ", " << "name=" << to_string(name); + out << ", " << "id=" << to_string(id); + out << ", " << "parent_id="; (__isset.parent_id ? (out << to_string(parent_id)) : (out << "<null>")); + out << ", " << "annotations=" << to_string(annotations); + out << ", " << "binary_annotations=" << to_string(binary_annotations); + out << ", " << "debug="; (__isset.debug ? (out << to_string(debug)) : (out << "<null>")); + out << ", " << "timestamp="; (__isset.timestamp ? (out << to_string(timestamp)) : (out << "<null>")); + out << ", " << "duration="; (__isset.duration ? (out << to_string(duration)) : (out << "<null>")); + out << ", " << "trace_id_high="; (__isset.trace_id_high ? (out << to_string(trace_id_high)) : (out << "<null>")); + out << ")"; +} + + +Response::~Response() noexcept { +} + + +void Response::__set_ok(const bool val) { + this->ok = val; +} +std::ostream& operator<<(std::ostream& out, const Response& obj) +{ + obj.printTo(out); + return out; +} + + +uint32_t Response::read(::apache::thrift::protocol::TProtocol* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_ok = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_BOOL) { + xfer += iprot->readBool(this->ok); + isset_ok = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_ok) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +uint32_t Response::write(::apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("Response"); + + xfer += oprot->writeFieldBegin("ok", ::apache::thrift::protocol::T_BOOL, 1); + xfer += oprot->writeBool(this->ok); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + +void swap(Response &a, Response &b) { + using ::std::swap; + swap(a.ok, b.ok); +} + +Response::Response(const Response& other21) { + ok = other21.ok; +} +Response& Response::operator=(const Response& other22) { + ok = other22.ok; + return *this; +} +void Response::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "Response("; + out << "ok=" << to_string(ok); + out << ")"; +} + +}}} // namespace diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_types.h b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_types.h new file mode 100644 index 000000000..c03421f43 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/jaeger/thrift-gen/zipkincore_types.h @@ -0,0 +1,493 @@ +/** + * Autogenerated by Thrift Compiler (0.14.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +#ifndef zipkincore_TYPES_H +#define zipkincore_TYPES_H + +#include <iosfwd> + +#include <thrift/Thrift.h> +#include <thrift/TApplicationException.h> +#include <thrift/TBase.h> +#include <thrift/protocol/TProtocol.h> +#include <thrift/transport/TTransport.h> + +#include <functional> +#include <memory> + + +namespace twitter { namespace zipkin { namespace thrift { + +struct AnnotationType { + enum type { + BOOL = 0, + BYTES = 1, + I16 = 2, + I32 = 3, + I64 = 4, + DOUBLE = 5, + STRING = 6 + }; +}; + +extern const std::map<int, const char*> _AnnotationType_VALUES_TO_NAMES; + +std::ostream& operator<<(std::ostream& out, const AnnotationType::type& val); + +std::string to_string(const AnnotationType::type& val); + +class Endpoint; + +class Annotation; + +class BinaryAnnotation; + +class Span; + +class Response; + +typedef struct _Endpoint__isset { + _Endpoint__isset() : ipv4(false), port(false), service_name(false), ipv6(false) {} + bool ipv4 :1; + bool port :1; + bool service_name :1; + bool ipv6 :1; +} _Endpoint__isset; + +/** + * Indicates the network context of a service recording an annotation with two + * exceptions. + * + * When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR, + * the endpoint indicates the source or destination of an RPC. This exception + * allows zipkin to display network context of uninstrumented services, or + * clients such as web browsers. + */ +class Endpoint : public virtual ::apache::thrift::TBase { + public: + + Endpoint(const Endpoint&); + Endpoint& operator=(const Endpoint&); + Endpoint() : ipv4(0), port(0), service_name(), ipv6() { + } + + virtual ~Endpoint() noexcept; + /** + * IPv4 host address packed into 4 bytes. + * + * Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4 + */ + int32_t ipv4; + /** + * IPv4 port + * + * Note: this is to be treated as an unsigned integer, so watch for negatives. + * + * Conventionally, when the port isn't known, port = 0. + */ + int16_t port; + /** + * Service name in lowercase, such as "memcache" or "zipkin-web" + * + * Conventionally, when the service name isn't known, service_name = "unknown". + */ + std::string service_name; + /** + * IPv6 host address packed into 16 bytes. Ex Inet6Address.getBytes() + */ + std::string ipv6; + + _Endpoint__isset __isset; + + void __set_ipv4(const int32_t val); + + void __set_port(const int16_t val); + + void __set_service_name(const std::string& val); + + void __set_ipv6(const std::string& val); + + bool operator == (const Endpoint & rhs) const + { + if (!(ipv4 == rhs.ipv4)) + return false; + if (!(port == rhs.port)) + return false; + if (!(service_name == rhs.service_name)) + return false; + if (__isset.ipv6 != rhs.__isset.ipv6) + return false; + else if (__isset.ipv6 && !(ipv6 == rhs.ipv6)) + return false; + return true; + } + bool operator != (const Endpoint &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Endpoint & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Endpoint &a, Endpoint &b); + +std::ostream& operator<<(std::ostream& out, const Endpoint& obj); + +typedef struct _Annotation__isset { + _Annotation__isset() : timestamp(false), value(false), host(false) {} + bool timestamp :1; + bool value :1; + bool host :1; +} _Annotation__isset; + +/** + * An annotation is similar to a log statement. It includes a host field which + * allows these events to be attributed properly, and also aggregatable. + */ +class Annotation : public virtual ::apache::thrift::TBase { + public: + + Annotation(const Annotation&); + Annotation& operator=(const Annotation&); + Annotation() : timestamp(0), value() { + } + + virtual ~Annotation() noexcept; + /** + * Microseconds from epoch. + * + * This value should use the most precise value possible. For example, + * gettimeofday or syncing nanoTime against a tick of currentTimeMillis. + */ + int64_t timestamp; + std::string value; + /** + * Always the host that recorded the event. By specifying the host you allow + * rollup of all events (such as client requests to a service) by IP address. + */ + Endpoint host; + + _Annotation__isset __isset; + + void __set_timestamp(const int64_t val); + + void __set_value(const std::string& val); + + void __set_host(const Endpoint& val); + + bool operator == (const Annotation & rhs) const + { + if (!(timestamp == rhs.timestamp)) + return false; + if (!(value == rhs.value)) + return false; + if (__isset.host != rhs.__isset.host) + return false; + else if (__isset.host && !(host == rhs.host)) + return false; + return true; + } + bool operator != (const Annotation &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Annotation & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Annotation &a, Annotation &b); + +std::ostream& operator<<(std::ostream& out, const Annotation& obj); + +typedef struct _BinaryAnnotation__isset { + _BinaryAnnotation__isset() : key(false), value(false), annotation_type(false), host(false) {} + bool key :1; + bool value :1; + bool annotation_type :1; + bool host :1; +} _BinaryAnnotation__isset; + +/** + * Binary annotations are tags applied to a Span to give it context. For + * example, a binary annotation of "http.uri" could the path to a resource in a + * RPC call. + * + * Binary annotations of type STRING are always queryable, though more a + * historical implementation detail than a structural concern. + * + * Binary annotations can repeat, and vary on the host. Similar to Annotation, + * the host indicates who logged the event. This allows you to tell the + * difference between the client and server side of the same key. For example, + * the key "http.uri" might be different on the client and server side due to + * rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field, + * you can see the different points of view, which often help in debugging. + */ +class BinaryAnnotation : public virtual ::apache::thrift::TBase { + public: + + BinaryAnnotation(const BinaryAnnotation&); + BinaryAnnotation& operator=(const BinaryAnnotation&); + BinaryAnnotation() : key(), value(), annotation_type((AnnotationType::type)0) { + } + + virtual ~BinaryAnnotation() noexcept; + std::string key; + std::string value; + /** + * + * @see AnnotationType + */ + AnnotationType::type annotation_type; + /** + * The host that recorded tag, which allows you to differentiate between + * multiple tags with the same key. There are two exceptions to this. + * + * When the key is CLIENT_ADDR or SERVER_ADDR, host indicates the source or + * destination of an RPC. This exception allows zipkin to display network + * context of uninstrumented services, or clients such as web browsers. + */ + Endpoint host; + + _BinaryAnnotation__isset __isset; + + void __set_key(const std::string& val); + + void __set_value(const std::string& val); + + void __set_annotation_type(const AnnotationType::type val); + + void __set_host(const Endpoint& val); + + bool operator == (const BinaryAnnotation & rhs) const + { + if (!(key == rhs.key)) + return false; + if (!(value == rhs.value)) + return false; + if (!(annotation_type == rhs.annotation_type)) + return false; + if (__isset.host != rhs.__isset.host) + return false; + else if (__isset.host && !(host == rhs.host)) + return false; + return true; + } + bool operator != (const BinaryAnnotation &rhs) const { + return !(*this == rhs); + } + + bool operator < (const BinaryAnnotation & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(BinaryAnnotation &a, BinaryAnnotation &b); + +std::ostream& operator<<(std::ostream& out, const BinaryAnnotation& obj); + +typedef struct _Span__isset { + _Span__isset() : trace_id(false), name(false), id(false), parent_id(false), annotations(false), binary_annotations(false), debug(true), timestamp(false), duration(false), trace_id_high(false) {} + bool trace_id :1; + bool name :1; + bool id :1; + bool parent_id :1; + bool annotations :1; + bool binary_annotations :1; + bool debug :1; + bool timestamp :1; + bool duration :1; + bool trace_id_high :1; +} _Span__isset; + +/** + * A trace is a series of spans (often RPC calls) which form a latency tree. + * + * The root span is where trace_id = id and parent_id = Nil. The root span is + * usually the longest interval in the trace, starting with a SERVER_RECV + * annotation and ending with a SERVER_SEND. + */ +class Span : public virtual ::apache::thrift::TBase { + public: + + Span(const Span&); + Span& operator=(const Span&); + Span() : trace_id(0), name(), id(0), parent_id(0), debug(false), timestamp(0), duration(0), trace_id_high(0) { + } + + virtual ~Span() noexcept; + int64_t trace_id; + /** + * Span name in lowercase, rpc method for example + * + * Conventionally, when the span name isn't known, name = "unknown". + */ + std::string name; + int64_t id; + int64_t parent_id; + std::vector<Annotation> annotations; + std::vector<BinaryAnnotation> binary_annotations; + bool debug; + /** + * Microseconds from epoch of the creation of this span. + * + * This value should be set directly by instrumentation, using the most + * precise value possible. For example, gettimeofday or syncing nanoTime + * against a tick of currentTimeMillis. + * + * For compatibility with instrumentation that precede this field, collectors + * or span stores can derive this via Annotation.timestamp. + * For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp. + * + * This field is optional for compatibility with old data: first-party span + * stores are expected to support this at time of introduction. + */ + int64_t timestamp; + /** + * Measurement of duration in microseconds, used to support queries. + * + * This value should be set directly, where possible. Doing so encourages + * precise measurement decoupled from problems of clocks, such as skew or NTP + * updates causing time to move backwards. + * + * For compatibility with instrumentation that precede this field, collectors + * or span stores can derive this by subtracting Annotation.timestamp. + * For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp. + * + * If this field is persisted as unset, zipkin will continue to work, except + * duration query support will be implementation-specific. Similarly, setting + * this field non-atomically is implementation-specific. + * + * This field is i64 vs i32 to support spans longer than 35 minutes. + */ + int64_t duration; + /** + * Optional unique 8-byte additional identifier for a trace. If non zero, this + * means the trace uses 128 bit traceIds instead of 64 bit. + */ + int64_t trace_id_high; + + _Span__isset __isset; + + void __set_trace_id(const int64_t val); + + void __set_name(const std::string& val); + + void __set_id(const int64_t val); + + void __set_parent_id(const int64_t val); + + void __set_annotations(const std::vector<Annotation> & val); + + void __set_binary_annotations(const std::vector<BinaryAnnotation> & val); + + void __set_debug(const bool val); + + void __set_timestamp(const int64_t val); + + void __set_duration(const int64_t val); + + void __set_trace_id_high(const int64_t val); + + bool operator == (const Span & rhs) const + { + if (!(trace_id == rhs.trace_id)) + return false; + if (!(name == rhs.name)) + return false; + if (!(id == rhs.id)) + return false; + if (__isset.parent_id != rhs.__isset.parent_id) + return false; + else if (__isset.parent_id && !(parent_id == rhs.parent_id)) + return false; + if (!(annotations == rhs.annotations)) + return false; + if (!(binary_annotations == rhs.binary_annotations)) + return false; + if (__isset.debug != rhs.__isset.debug) + return false; + else if (__isset.debug && !(debug == rhs.debug)) + return false; + if (__isset.timestamp != rhs.__isset.timestamp) + return false; + else if (__isset.timestamp && !(timestamp == rhs.timestamp)) + return false; + if (__isset.duration != rhs.__isset.duration) + return false; + else if (__isset.duration && !(duration == rhs.duration)) + return false; + if (__isset.trace_id_high != rhs.__isset.trace_id_high) + return false; + else if (__isset.trace_id_high && !(trace_id_high == rhs.trace_id_high)) + return false; + return true; + } + bool operator != (const Span &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Span & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Span &a, Span &b); + +std::ostream& operator<<(std::ostream& out, const Span& obj); + + +class Response : public virtual ::apache::thrift::TBase { + public: + + Response(const Response&); + Response& operator=(const Response&); + Response() : ok(0) { + } + + virtual ~Response() noexcept; + bool ok; + + void __set_ok(const bool val); + + bool operator == (const Response & rhs) const + { + if (!(ok == rhs.ok)) + return false; + return true; + } + bool operator != (const Response &rhs) const { + return !(*this == rhs); + } + + bool operator < (const Response & ) const; + + uint32_t read(::apache::thrift::protocol::TProtocol* iprot); + uint32_t write(::apache::thrift::protocol::TProtocol* oprot) const; + + virtual void printTo(std::ostream& out) const; +}; + +void swap(Response &a, Response &b); + +std::ostream& operator<<(std::ostream& out, const Response& obj); + +}}} // namespace + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/memory/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/memory/BUILD new file mode 100644 index 000000000..b2dd48347 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/memory/BUILD @@ -0,0 +1,57 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "in_memory_span_data", + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_span_data.h", + ], + strip_include_prefix = "include", + tags = ["memory"], + deps = [ + "//api", + "//sdk/src/resource", + "//sdk/src/trace", + ], +) + +cc_test( + name = "in_memory_span_data_test", + srcs = ["test/in_memory_span_data_test.cc"], + tags = [ + "memory", + "test", + ], + deps = [ + ":in_memory_span_data", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "in_memory_span_exporter", + hdrs = [ + "include/opentelemetry/exporters/memory/in_memory_span_exporter.h", + ], + strip_include_prefix = "include", + tags = [ + "memory", + "test", + ], + deps = [ + ":in_memory_span_data", + "//sdk/src/trace", + ], +) + +cc_test( + name = "in_memory_span_exporter_test", + srcs = ["test/in_memory_span_exporter_test.cc"], + tags = [ + "memory", + "test", + ], + deps = [ + ":in_memory_span_exporter", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/memory/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/memory/CMakeLists.txt new file mode 100644 index 000000000..d489ec160 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/memory/CMakeLists.txt @@ -0,0 +1,46 @@ +add_library(opentelemetry_exporter_in_memory INTERFACE) + +target_include_directories( + opentelemetry_exporter_in_memory + INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + +set_target_properties(opentelemetry_exporter_in_memory + PROPERTIES EXPORT_NAME in_memory_span_exporter) + +install( + TARGETS opentelemetry_exporter_in_memory + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/memory + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h") + +if(BUILD_TESTING) + add_executable(in_memory_span_data_test test/in_memory_span_data_test.cc) + add_executable(in_memory_span_exporter_test + test/in_memory_span_exporter_test.cc) + + target_link_libraries( + in_memory_span_data_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_in_memory opentelemetry_resources) + + target_link_libraries( + in_memory_span_exporter_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_exporter_in_memory + opentelemetry_resources) + + gtest_add_tests( + TARGET in_memory_span_data_test + TEST_PREFIX exporter. + TEST_LIST in_memory_span_data_test) + gtest_add_tests( + TARGET in_memory_span_exporter_test + TEST_PREFIX exporter. + TEST_LIST in_memory_span_exporter_test) +endif() diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_data.h b/src/jaegertracing/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_data.h new file mode 100644 index 000000000..df6d377b9 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_data.h @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/sdk/common/circular_buffer.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include <vector> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ +/** + * A wrapper class holding in memory exporter data + */ +class InMemorySpanData final +{ +public: + /** + * @param buffer_size a required value that sets the size of the CircularBuffer + */ + InMemorySpanData(size_t buffer_size) : spans_received_(buffer_size) {} + + /** + * @param data a required unique pointer to the data to add to the CircularBuffer + */ + void Add(std::unique_ptr<opentelemetry::sdk::trace::SpanData> data) noexcept + { + std::unique_ptr<opentelemetry::sdk::trace::SpanData> span_data( + static_cast<opentelemetry::sdk::trace::SpanData *>(data.release())); + spans_received_.Add(span_data); + } + + /** + * @return Returns a vector of unique pointers containing all the span data in the + * CircularBuffer. This operation will empty the Buffer, which is why the data + * is returned as unique pointers + */ + std::vector<std::unique_ptr<opentelemetry::sdk::trace::SpanData>> GetSpans() noexcept + { + std::vector<std::unique_ptr<opentelemetry::sdk::trace::SpanData>> res; + + // Pointer swap is required because the Consume function requires that the + // AtomicUniquePointer be set to null + spans_received_.Consume( + spans_received_.size(), + [&](opentelemetry::sdk::common::CircularBufferRange< + opentelemetry::sdk::common::AtomicUniquePtr<opentelemetry::sdk::trace::SpanData>> + range) noexcept { + range.ForEach( + [&](opentelemetry::sdk::common::AtomicUniquePtr<opentelemetry::sdk::trace::SpanData> + &ptr) noexcept { + std::unique_ptr<opentelemetry::sdk::trace::SpanData> swap_ptr = + std::unique_ptr<opentelemetry::sdk::trace::SpanData>(nullptr); + ptr.Swap(swap_ptr); + res.push_back( + std::unique_ptr<opentelemetry::sdk::trace::SpanData>(swap_ptr.release())); + return true; + }); + }); + + return res; + } + +private: + opentelemetry::sdk::common::CircularBuffer<opentelemetry::sdk::trace::SpanData> spans_received_; +}; +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter.h new file mode 100644 index 000000000..28b7bc34e --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/memory/include/opentelemetry/exporters/memory/in_memory_span_exporter.h @@ -0,0 +1,100 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#include <mutex> +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/exporters/memory/in_memory_span_data.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk_config.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace memory +{ +const size_t MAX_BUFFER_SIZE = 100; + +/** + * A in memory exporter that switches a flag once a valid recordable was received + * and keeps track of all received spans in memory. + */ +class InMemorySpanExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * @param buffer_size an optional value that sets the size of the InMemorySpanData + */ + InMemorySpanExporter(size_t buffer_size = MAX_BUFFER_SIZE) + : data_(new opentelemetry::exporter::memory::InMemorySpanData(buffer_size)) + {} + + /** + * @return Returns a unique pointer to an empty recordable object + */ + std::unique_ptr<sdk::trace::Recordable> MakeRecordable() noexcept override + { + return std::unique_ptr<sdk::trace::Recordable>(new sdk::trace::SpanData()); + } + + /** + * @param recordables a required span containing unique pointers to the data + * to add to the InMemorySpanData + * @return Returns the result of the operation + */ + sdk::common::ExportResult Export( + const nostd::span<std::unique_ptr<sdk::trace::Recordable>> &recordables) noexcept override + { + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[In Memory Span Exporter] Exporting " + << recordables.size() << " span(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + for (auto &recordable : recordables) + { + auto span = std::unique_ptr<sdk::trace::SpanData>( + static_cast<sdk::trace::SpanData *>(recordable.release())); + if (span != nullptr) + { + data_->Add(std::move(span)); + } + } + + return sdk::common::ExportResult::kSuccess; + } + + /** + * @param timeout an optional value containing the timeout of the exporter + * note: passing custom timeout values is not currently supported for this exporter + * @return Returns the status of the operation + */ + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override + { + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; + }; + + /** + * @return Returns a shared pointer to this exporters InMemorySpanData + */ + std::shared_ptr<opentelemetry::exporter::memory::InMemorySpanData> GetData() noexcept + { + return data_; + } + +private: + std::shared_ptr<opentelemetry::exporter::memory::InMemorySpanData> data_; + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + const bool isShutdown() const noexcept + { + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; + } +}; +} // namespace memory +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/memory/test/in_memory_span_data_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/memory/test/in_memory_span_data_test.cc new file mode 100644 index 000000000..bc1955026 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/memory/test/in_memory_span_data_test.cc @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_span_data.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include <gtest/gtest.h> + +using opentelemetry::exporter::memory::InMemorySpanData; +using opentelemetry::sdk::trace::Recordable; +using opentelemetry::sdk::trace::SpanData; + +TEST(InMemorySpanData, AddRecordable) +{ + InMemorySpanData data(100); + + ASSERT_EQ(0, data.GetSpans().size()); + + std::unique_ptr<SpanData> spandata(new SpanData()); + + data.Add(std::move(spandata)); + + // Consumes all spans in exporter + ASSERT_EQ(1, data.GetSpans().size()); + + ASSERT_EQ(0, data.GetSpans().size()); +} diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/memory/test/in_memory_span_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/memory/test/in_memory_span_exporter_test.cc new file mode 100644 index 000000000..56a0ef779 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/memory/test/in_memory_span_exporter_test.cc @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include <gtest/gtest.h> + +using opentelemetry::exporter::memory::InMemorySpanExporter; +using opentelemetry::sdk::trace::Recordable; +using opentelemetry::sdk::trace::SpanData; + +TEST(InMemorySpanExporter, ExportBatch) +{ + InMemorySpanExporter exporter; + + ASSERT_EQ(0, exporter.GetData().get()->GetSpans().size()); + + std::unique_ptr<Recordable> spandata(new SpanData()); + opentelemetry::nostd::span<std::unique_ptr<Recordable>> batch(&spandata, 1); + + exporter.Export(batch); + + ASSERT_EQ(1, exporter.GetData().get()->GetSpans().size()); + + // Consumes all spans in exporter + ASSERT_EQ(0, exporter.GetData().get()->GetSpans().size()); +} diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/BUILD new file mode 100644 index 000000000..917b70450 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/BUILD @@ -0,0 +1,131 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "ostream_log_exporter", + srcs = [ + "src/log_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/ostream/common_utils.h", + "include/opentelemetry/exporters/ostream/log_exporter.h", + ], + strip_include_prefix = "include", + tags = ["ostream"], + deps = [ + "//sdk/src/logs", + ], +) + +cc_test( + name = "ostream_log_test", + srcs = ["test/ostream_log_test.cc"], + tags = [ + "ostream", + "test", + ], + deps = [ + ":ostream_log_exporter", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "ostream_metrics_exporter_deprecated", + srcs = [ + "src/metrics_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/ostream/metrics_exporter.h", + ], + strip_include_prefix = "include", + tags = ["ostream"], + deps = [ + "//sdk/src/_metrics:metrics_deprecated", + ], +) + +cc_library( + name = "ostream_metric_exporter", + srcs = [ + "src/metric_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/ostream/common_utils.h", + "include/opentelemetry/exporters/ostream/metric_exporter.h", + ], + strip_include_prefix = "include", + tags = [ + "metrics", + "ostream", + ], + deps = [ + "//sdk/src/metrics", + ], +) + +cc_test( + name = "ostream_metric_test", + srcs = ["test/ostream_metric_test.cc"], + tags = [ + "ostream", + "test", + ], + deps = [ + ":ostream_metric_exporter", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "ostream_metrics_test_deprecated", + srcs = ["test/ostream_metrics_test.cc"], + tags = [ + "ostream", + "test", + ], + deps = [ + ":ostream_metrics_exporter_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "ostream_span_exporter", + srcs = [ + "src/span_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/ostream/common_utils.h", + "include/opentelemetry/exporters/ostream/span_exporter.h", + ], + strip_include_prefix = "include", + tags = ["ostream"], + deps = [ + "//sdk/src/trace", + ], +) + +cc_library( + name = "ostream_capture", + hdrs = [ + "test/ostream_capture.h", + ], + tags = ["ostream"], + deps = [ + "//api", + ], +) + +cc_test( + name = "ostream_span_test", + srcs = ["test/ostream_span_test.cc"], + tags = [ + "ostream", + "test", + ], + deps = [ + ":ostream_capture", + ":ostream_span_exporter", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/CMakeLists.txt new file mode 100644 index 000000000..db2562ddd --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/CMakeLists.txt @@ -0,0 +1,125 @@ +add_library(opentelemetry_exporter_ostream_span src/span_exporter.cc) + +set_target_properties(opentelemetry_exporter_ostream_span + PROPERTIES EXPORT_NAME ostream_span_exporter) + +target_include_directories( + opentelemetry_exporter_ostream_span + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>") + +target_link_libraries(opentelemetry_exporter_ostream_span + PUBLIC opentelemetry_trace) + +install( + TARGETS opentelemetry_exporter_ostream_span + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/ostream + DESTINATION include/opentelemetry/exporters + PATTERN "*.h" + PATTERN "metrics_exporter.h" EXCLUDE + PATTERN "log_Exporter.h" EXCLUDE) + +if(BUILD_TESTING) + add_executable(ostream_span_test test/ostream_span_test.cc) + target_link_libraries(ostream_span_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_ostream_span) + gtest_add_tests( + TARGET ostream_span_test + TEST_PREFIX exporter. + TEST_LIST ostream_span_test) +endif() # BUILD_TESTING + +if(WITH_METRICS_PREVIEW) + add_library(opentelemetry_exporter_ostream_metrics_deprecated + src/metrics_exporter.cc) + set_target_properties(opentelemetry_exporter_ostream_metrics_deprecated + PROPERTIES EXPORT_NAME ostream_metrics_exporter) + target_include_directories( + opentelemetry_exporter_ostream_metrics_deprecated + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>") + target_link_libraries(opentelemetry_exporter_ostream_metrics_deprecated + PUBLIC opentelemetry_metrics_deprecated) + install( + TARGETS opentelemetry_exporter_ostream_metrics_deprecated + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install( + DIRECTORY include/opentelemetry/exporters/ostream + DESTINATION include/opentelemetry/exporters + PATTERN "metrics_exporter.h") + if(BUILD_TESTING) + add_executable(ostream_metrics_test test/ostream_metrics_test.cc) + target_link_libraries(ostream_metrics_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_ostream_metrics_deprecated) + gtest_add_tests( + TARGET ostream_metrics_test + TEST_PREFIX exporter. + TEST_LIST ostream_metrics_test) + endif() +else() + add_library(opentelemetry_exporter_ostream_metrics src/metric_exporter.cc) + set_target_properties(opentelemetry_exporter_ostream_metrics + PROPERTIES EXPORT_NAME ostream_metrics_exporter) + target_include_directories( + opentelemetry_exporter_ostream_metrics + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>") + target_link_libraries(opentelemetry_exporter_ostream_metrics + PUBLIC opentelemetry_metrics) + install( + TARGETS opentelemetry_exporter_ostream_metrics + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install( + DIRECTORY include/opentelemetry/exporters/ostream + DESTINATION include/opentelemetry/exporters + PATTERN "metric_exporter.h") + if(BUILD_TESTING) + add_executable(ostream_metric_test test/ostream_metric_test.cc) + target_link_libraries( + ostream_metric_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_ostream_metrics opentelemetry_resources) + gtest_add_tests( + TARGET ostream_metric_test + TEST_PREFIX exporter. + TEST_LIST ostream_metric_test) + endif() +endif() + +if(WITH_LOGS_PREVIEW) + add_library(opentelemetry_exporter_ostream_logs src/log_exporter.cc) + set_target_properties(opentelemetry_exporter_ostream_logs + PROPERTIES EXPORT_NAME ostream_log_exporter) + target_include_directories( + opentelemetry_exporter_ostream_logs + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>") + target_link_libraries(opentelemetry_exporter_ostream_logs + PUBLIC opentelemetry_logs) + install( + TARGETS opentelemetry_exporter_ostream_logs + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install( + DIRECTORY include/opentelemetry/exporters/ostream + DESTINATION include/opentelemetry/exporters + PATTERN "log_exporter.h") + if(BUILD_TESTING) + add_executable(ostream_log_test test/ostream_log_test.cc) + target_link_libraries(ostream_log_test ${GTEST_BOTH_LIBRARIES} + opentelemetry_exporter_ostream_logs) + gtest_add_tests( + TARGET ostream_log_test + TEST_PREFIX exporter. + TEST_LIST ostream_log_test) + endif() +endif() diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h new file mode 100644 index 000000000..cfebfe8fc --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <sstream> +#include <string> +#include <vector> +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/common/attribute_utils.h" + +#pragma once +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace ostream_common +{ +/* + print_value is used to print out the value of an attribute within a vector. + These values are held in a variant which makes the process of printing them much more + complicated. +*/ + +template <typename T> +void print_value(const T &item, std::ostream &sout) +{ + sout << item; +} + +template <typename T> +void print_value(const std::vector<T> &vec, std::ostream &sout) +{ + sout << '['; + size_t i = 1; + size_t sz = vec.size(); + for (auto v : vec) + { + sout << v; + if (i != sz) + sout << ','; + i++; + }; + sout << ']'; +} + +// Prior to C++14, generic lambda is not available so fallback to functor. +#if __cplusplus < 201402L + +class OwnedAttributeValueVisitor +{ +public: + OwnedAttributeValueVisitor(std::ostream &sout) : sout_(sout) {} + + template <typename T> + void operator()(T &&arg) + { + print_value(arg, sout_); + } + +private: + std::ostream &sout_; +}; + +#endif + +void print_value(const opentelemetry::sdk::common::OwnedAttributeValue &value, std::ostream &sout) +{ +#if __cplusplus < 201402L + opentelemetry::nostd::visit(OwnedAttributeValueVisitor(sout), value); +#else + opentelemetry::nostd::visit( + [&sout](auto &&arg) { + /* explicit this is needed by some gcc versions (observed with v5.4.0)*/ + print_value(arg, sout); + }, + value); +#endif +} + +} // namespace ostream_common +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE
\ No newline at end of file diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h new file mode 100644 index 000000000..2f6acbb48 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_LOGS_PREVIEW + +# include "opentelemetry/common/spin_lock_mutex.h" +# include "opentelemetry/nostd/type_traits.h" +# include "opentelemetry/sdk/logs/exporter.h" +# include "opentelemetry/sdk/logs/log_record.h" +# include "opentelemetry/version.h" + +# include <iostream> +# include <sstream> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ +/** + * The OStreamLogExporter exports logs through an ostream (default set to std::cout) + */ +class OStreamLogExporter final : public opentelemetry::sdk::logs::LogExporter +{ +public: + /** + * Create an OStreamLogExporter. This constructor takes in a reference to an ostream that the + * Export() method will send log data into. The default ostream is set to stdout. + */ + explicit OStreamLogExporter(std::ostream &sout = std::cout) noexcept; + + std::unique_ptr<sdk::logs::Recordable> MakeRecordable() noexcept override; + + /** + * Exports a span of logs sent from the processor. + */ + opentelemetry::sdk::common::ExportResult Export( + const opentelemetry::nostd::span<std::unique_ptr<sdk::logs::Recordable>> &records) noexcept + override; + + /** + * Marks the OStream Log Exporter as shut down. + */ + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + +private: + // The OStream to send the logs to + std::ostream &sout_; + // Whether this exporter has been shut down + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; + void printAttributes( + const std::unordered_map<std::string, opentelemetry::sdk::common::OwnedAttributeValue> &map, + const std::string prefix = "\n\t"); +}; +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/metric_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/metric_exporter.h new file mode 100644 index 000000000..e34332d77 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/metric_exporter.h @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifndef ENABLE_METRICS_PREVIEW + +# include <iostream> +# include <string> +# include "opentelemetry/common/spin_lock_mutex.h" +# include "opentelemetry/sdk/metrics/data/metric_data.h" +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" +# include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ + +/** + * The OStreamMetricExporter exports record data through an ostream + */ +class OStreamMetricExporter final : public opentelemetry::sdk::metrics::MetricExporter +{ +public: + /** + * Create an OStreamMetricExporter. This constructor takes in a reference to an ostream that the + * export() function will send metrics data into. + * The default ostream is set to stdout + */ + explicit OStreamMetricExporter(std::ostream &sout = std::cout) noexcept; + + /** + * Export + * @param data metrics data + */ + sdk::common::ExportResult Export(const sdk::metrics::ResourceMetrics &data) noexcept override; + + /** + * Force flush the exporter. + */ + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + /** + * Shut down the exporter. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. + * @return return the status of this operation + */ + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + +private: + std::ostream &sout_; + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; + void printInstrumentationInfoMetricData( + const sdk::metrics::InstrumentationInfoMetrics &info_metrics); + void printPointData(const opentelemetry::sdk::metrics::PointType &point_data); + void printPointAttributes(const opentelemetry::sdk::metrics::PointAttributes &point_attributes); +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/metrics_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/metrics_exporter.h new file mode 100644 index 000000000..5ae168aa0 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/metrics_exporter.h @@ -0,0 +1,166 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_METRICS_PREVIEW + +# include <iostream> +# include <string> +# include "opentelemetry/sdk/_metrics/aggregator/exact_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/gauge_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/histogram_aggregator.h" +# include "opentelemetry/sdk/_metrics/exporter.h" +# include "opentelemetry/sdk/_metrics/record.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ + +/** + * The OStreamMetricsExporter exports record data through an ostream + */ +class OStreamMetricsExporter final : public opentelemetry::sdk::metrics::MetricsExporter +{ +public: + /** + * Create an OStreamMetricsExporter. This constructor takes in a reference to an ostream that the + * export() function will send span data into. + * The default ostream is set to stdout + */ + explicit OStreamMetricsExporter(std::ostream &sout = std::cout) noexcept; + + sdk::common::ExportResult Export( + const std::vector<opentelemetry::sdk::metrics::Record> &records) noexcept override; + +private: + std::ostream &sout_; + + /** + * Send specific data from the given AggregatorVariant based on what AggregatorKind + * it is holding. Each Aggregator holds data differently, so each have their own + * custom printing. + */ + template <typename T> + void PrintAggregatorVariant(opentelemetry::sdk::metrics::AggregatorVariant value) + { + auto agg = nostd::get<std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<T>>>(value); + auto aggKind = agg->get_aggregator_kind(); + + if (!agg) + return; + switch (aggKind) + { + case opentelemetry::sdk::metrics::AggregatorKind::Counter: { + sout_ << "\n sum : " << agg->get_checkpoint()[0]; + } + break; + case opentelemetry::sdk::metrics::AggregatorKind::MinMaxSumCount: { + auto mmsc = agg->get_checkpoint(); + sout_ << "\n min : " << mmsc[0] << "\n max : " << mmsc[1] + << "\n sum : " << mmsc[2] << "\n count : " << mmsc[3]; + } + break; + case opentelemetry::sdk::metrics::AggregatorKind::Gauge: { + auto timestamp = agg->get_checkpoint_timestamp(); + + sout_ << "\n last value : " << agg->get_checkpoint()[0] + << "\n timestamp : " << std::to_string(timestamp.time_since_epoch().count()); + } + break; + case opentelemetry::sdk::metrics::AggregatorKind::Exact: { + // TODO: Find better way to print quantiles + if (agg->get_quant_estimation()) + { + sout_ << "\n quantiles : " + << "[0: " << agg->get_quantiles(0) << ", " + << ".25: " << agg->get_quantiles(.25) << ", " + << ".50: " << agg->get_quantiles(.50) << ", " + << ".75: " << agg->get_quantiles(.75) << ", " + << "1: " << agg->get_quantiles(1) << ']'; + } + else + { + auto vec = agg->get_checkpoint(); + size_t size = vec.size(); + size_t i = 1; + + sout_ << "\n values : " << '['; + + for (auto val : vec) + { + sout_ << val; + if (i != size) + sout_ << ", "; + i++; + } + sout_ << ']'; + } + } + break; + case opentelemetry::sdk::metrics::AggregatorKind::Histogram: { + auto boundaries = agg->get_boundaries(); + auto counts = agg->get_counts(); + + size_t boundaries_size = boundaries.size(); + size_t counts_size = counts.size(); + + sout_ << "\n buckets : " << '['; + + for (size_t i = 0; i < boundaries_size; i++) + { + sout_ << boundaries[i]; + + if (i != boundaries_size - 1) + sout_ << ", "; + } + sout_ << ']'; + + sout_ << "\n counts : " << '['; + for (size_t i = 0; i < counts_size; i++) + { + sout_ << counts[i]; + + if (i != counts_size - 1) + sout_ << ", "; + } + sout_ << ']'; + } + break; + case opentelemetry::sdk::metrics::AggregatorKind::Sketch: { + auto boundaries = agg->get_boundaries(); + auto counts = agg->get_counts(); + + size_t boundaries_size = boundaries.size(); + size_t counts_size = counts.size(); + + sout_ << "\n buckets : " << '['; + + for (size_t i = 0; i < boundaries_size; i++) + { + sout_ << boundaries[i]; + + if (i != boundaries_size - 1) + sout_ << ", "; + } + sout_ << ']'; + + sout_ << "\n counts : " << '['; + for (size_t i = 0; i < counts_size; i++) + { + sout_ << counts[i]; + + if (i != counts_size - 1) + sout_ << ", "; + } + sout_ << ']'; + } + break; + } + } +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h new file mode 100644 index 000000000..c8603db02 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/nostd/type_traits.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/version.h" + +#include <iostream> +#include <map> +#include <sstream> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace trace +{ + +/** + * The OStreamSpanExporter exports span data through an ostream + */ +class OStreamSpanExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * Create an OStreamSpanExporter. This constructor takes in a reference to an ostream that the + * export() function will send span data into. + * The default ostream is set to stdout + */ + explicit OStreamSpanExporter(std::ostream &sout = std::cout) noexcept; + + std::unique_ptr<opentelemetry::sdk::trace::Recordable> MakeRecordable() noexcept override; + + sdk::common::ExportResult Export( + const opentelemetry::nostd::span<std::unique_ptr<opentelemetry::sdk::trace::Recordable>> + &spans) noexcept override; + + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + +private: + std::ostream &sout_; + bool is_shutdown_ = false; + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; + + // Mapping status number to the string from api/include/opentelemetry/trace/canonical_code.h + std::map<int, std::string> statusMap{{0, "Unset"}, {1, "Ok"}, {2, "Error"}}; + + // various print helpers + void printAttributes( + const std::unordered_map<std::string, opentelemetry::sdk::common::OwnedAttributeValue> &map, + const std::string prefix = "\n\t"); + + void printEvents(const std::vector<opentelemetry::sdk::trace::SpanDataEvent> &events); + + void printLinks(const std::vector<opentelemetry::sdk::trace::SpanDataLink> &links); + + void printResources(const opentelemetry::sdk::resource::Resource &resources); + + void printInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library); +}; +} // namespace trace +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/log_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/log_exporter.cc new file mode 100644 index 000000000..e6ddd4c9f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/log_exporter.cc @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW +# include "opentelemetry/exporters/ostream/log_exporter.h" +# include <mutex> +# include "opentelemetry/exporters/ostream/common_utils.h" +# include "opentelemetry/sdk_config.h" + +# include <iostream> +# include <type_traits> + +namespace nostd = opentelemetry::nostd; +namespace sdklogs = opentelemetry::sdk::logs; +namespace sdkcommon = opentelemetry::sdk::common; +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ + +/*********************** Constructor ***********************/ + +OStreamLogExporter::OStreamLogExporter(std::ostream &sout) noexcept : sout_(sout) {} + +/*********************** Exporter methods ***********************/ + +std::unique_ptr<sdklogs::Recordable> OStreamLogExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<sdklogs::Recordable>(new sdklogs::LogRecord()); +} + +sdk::common::ExportResult OStreamLogExporter::Export( + const nostd::span<std::unique_ptr<sdklogs::Recordable>> &records) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[Ostream Log Exporter] Exporting " + << records.size() << " log(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + + for (auto &record : records) + { + // Convert recordable to a LogRecord so that the getters of the LogRecord can be used + auto log_record = + std::unique_ptr<sdklogs::LogRecord>(static_cast<sdklogs::LogRecord *>(record.release())); + + if (log_record == nullptr) + { + // TODO: Log Internal SDK error "recordable data was lost" + continue; + } + + // Convert trace, spanid, traceflags into exportable representation + constexpr int trace_id_len = 32; + constexpr int span_id__len = 16; + constexpr int trace_flags_len = 2; + + char trace_id[trace_id_len] = {0}; + char span_id[span_id__len] = {0}; + char trace_flags[trace_flags_len] = {0}; + + log_record->GetTraceId().ToLowerBase16(trace_id); + log_record->GetSpanId().ToLowerBase16(span_id); + log_record->GetTraceFlags().ToLowerBase16(trace_flags); + + // Print out each field of the log record, noting that severity is separated + // into severity_num and severity_text + sout_ << "{\n" + << " timestamp : " << log_record->GetTimestamp().time_since_epoch().count() << "\n" + << " severity_num : " << static_cast<std::uint32_t>(log_record->GetSeverity()) << "\n" + << " severity_text : "; + + std::uint32_t severity_index = static_cast<std::uint32_t>(log_record->GetSeverity()); + if (severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value) + { + sout_ << "Invalid severity(" << severity_index << ")\n"; + } + else + { + sout_ << opentelemetry::logs::SeverityNumToText[severity_index] << "\n"; + } + + sout_ << " body : " << log_record->GetBody() << "\n" + << " resource : "; + + printAttributes(log_record->GetResource().GetAttributes()); + + sout_ << "\n" + << " attributes : "; + + printAttributes(log_record->GetAttributes()); + + sout_ << "\n" + << " trace_id : " << std::string(trace_id, trace_id_len) << "\n" + << " span_id : " << std::string(span_id, span_id__len) << "\n" + << " trace_flags : " << std::string(trace_flags, trace_flags_len) << "\n" + << "}\n"; + } + + return sdk::common::ExportResult::kSuccess; +} + +bool OStreamLogExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; +} + +bool OStreamLogExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} + +void OStreamLogExporter::printAttributes( + const std::unordered_map<std::string, sdkcommon::OwnedAttributeValue> &map, + const std::string prefix) +{ + for (const auto &kv : map) + { + sout_ << prefix << kv.first << ": "; + opentelemetry::exporter::ostream_common::print_value(kv.second, sout_); + } +} + +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/metric_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/metric_exporter.cc new file mode 100644 index 000000000..2e90e0845 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/metric_exporter.cc @@ -0,0 +1,214 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <chrono> +#ifndef ENABLE_METRICS_PREVIEW +# include <algorithm> +# include "opentelemetry/exporters/ostream/common_utils.h" +# include "opentelemetry/exporters/ostream/metric_exporter.h" +# include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" +# include "opentelemetry/sdk_config.h" + +namespace +{ +std::string timeToString(opentelemetry::common::SystemTimestamp time_stamp) +{ + std::time_t epoch_time = std::chrono::system_clock::to_time_t(time_stamp); + + struct tm *tm_ptr = nullptr; +# if defined(_MSC_VER) + struct tm buf_tm; + if (!gmtime_s(&buf_tm, &epoch_time)) + { + tm_ptr = &buf_tm; + } +# else + tm_ptr = std::gmtime(&epoch_time); +# endif + + char buf[100]; + char *date_str = nullptr; + if (tm_ptr == nullptr) + { + OTEL_INTERNAL_LOG_ERROR("[OStream Metric] gmtime failed for " << epoch_time); + } + else if (std::strftime(buf, sizeof(buf), "%c", tm_ptr) > 0) + { + date_str = buf; + } + else + { + OTEL_INTERNAL_LOG_ERROR("[OStream Metric] strftime failed for " << epoch_time); + } + + return std::string{date_str}; +} +} // namespace + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ + +template <typename Container> +inline void printVec(std::ostream &os, Container &vec) +{ + using T = typename std::decay<decltype(*vec.begin())>::type; + os << '['; + if (vec.size() > 1) + { + std::copy(vec.begin(), vec.end(), std::ostream_iterator<T>(os, ", ")); + } + os << ']'; +} + +OStreamMetricExporter::OStreamMetricExporter(std::ostream &sout) noexcept : sout_(sout) {} + +sdk::common::ExportResult OStreamMetricExporter::Export( + const sdk::metrics::ResourceMetrics &data) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[OStream Metric] Exporting " + << data.instrumentation_info_metric_data_.size() + << " records(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + + for (auto &record : data.instrumentation_info_metric_data_) + { + printInstrumentationInfoMetricData(record); + } + return sdk::common::ExportResult::kSuccess; +} + +void OStreamMetricExporter::printInstrumentationInfoMetricData( + const sdk::metrics::InstrumentationInfoMetrics &info_metric) +{ + // sout_ is shared + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + sout_ << "{"; + sout_ << "\n name\t\t: " << info_metric.instrumentation_library_->GetName() + << "\n schema url\t: " << info_metric.instrumentation_library_->GetSchemaURL() + << "\n version\t: " << info_metric.instrumentation_library_->GetVersion(); + for (const auto &record : info_metric.metric_data_) + { + sout_ << "\n start time\t: " << timeToString(record.start_ts) + << "\n end time\t: " << timeToString(record.end_ts) + << "\n name\t\t: " << record.instrument_descriptor.name_ + << "\n description\t: " << record.instrument_descriptor.description_ + << "\n unit\t\t: " << record.instrument_descriptor.unit_; + + for (const auto &pd : record.point_data_attr_) + { + if (!nostd::holds_alternative<sdk::metrics::DropPointData>(pd.point_data)) + { + printPointData(pd.point_data); + printPointAttributes(pd.attributes); + } + } + } + sout_ << "\n}\n"; +} + +void OStreamMetricExporter::printPointData(const opentelemetry::sdk::metrics::PointType &point_data) +{ + if (nostd::holds_alternative<sdk::metrics::SumPointData>(point_data)) + { + auto sum_point_data = nostd::get<sdk::metrics::SumPointData>(point_data); + sout_ << "\n type\t\t: SumPointData"; + sout_ << "\n value\t\t: "; + if (nostd::holds_alternative<double>(sum_point_data.value_)) + { + sout_ << nostd::get<double>(sum_point_data.value_); + } + else if (nostd::holds_alternative<long>(sum_point_data.value_)) + { + sout_ << nostd::get<long>(sum_point_data.value_); + } + } + else if (nostd::holds_alternative<sdk::metrics::HistogramPointData>(point_data)) + { + auto histogram_point_data = nostd::get<sdk::metrics::HistogramPointData>(point_data); + sout_ << "\n type : HistogramPointData"; + sout_ << "\n count : " << histogram_point_data.count_; + sout_ << "\n sum : "; + if (nostd::holds_alternative<double>(histogram_point_data.sum_)) + { + sout_ << nostd::get<double>(histogram_point_data.sum_); + } + else if (nostd::holds_alternative<long>(histogram_point_data.sum_)) + { + sout_ << nostd::get<long>(histogram_point_data.sum_); + } + + sout_ << "\n buckets : "; + if (nostd::holds_alternative<std::list<double>>(histogram_point_data.boundaries_)) + { + auto &double_boundaries = nostd::get<std::list<double>>(histogram_point_data.boundaries_); + printVec(sout_, double_boundaries); + } + else if (nostd::holds_alternative<std::list<long>>(histogram_point_data.boundaries_)) + { + auto &long_boundaries = nostd::get<std::list<long>>(histogram_point_data.boundaries_); + printVec(sout_, long_boundaries); + } + + sout_ << "\n counts : "; + printVec(sout_, histogram_point_data.counts_); + } + else if (nostd::holds_alternative<sdk::metrics::LastValuePointData>(point_data)) + { + auto last_point_data = nostd::get<sdk::metrics::LastValuePointData>(point_data); + sout_ << "\n type : LastValuePointData"; + sout_ << "\n timestamp : " + << std::to_string(last_point_data.sample_ts_.time_since_epoch().count()) << std::boolalpha + << "\n valid : " << last_point_data.is_lastvalue_valid_; + sout_ << "\n value : "; + if (nostd::holds_alternative<double>(last_point_data.value_)) + { + sout_ << nostd::get<double>(last_point_data.value_); + } + else if (nostd::holds_alternative<long>(last_point_data.value_)) + { + sout_ << nostd::get<long>(last_point_data.value_); + } + } +} + +void OStreamMetricExporter::printPointAttributes( + const opentelemetry::sdk::metrics::PointAttributes &point_attributes) +{ + sout_ << "\n attributes\t\t: "; + for (const auto &kv : point_attributes) + { + sout_ << "\n\t" << kv.first << ": "; + opentelemetry::exporter::ostream_common::print_value(kv.second, sout_); + } +} + +bool OStreamMetricExporter::ForceFlush(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return true; +} + +bool OStreamMetricExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; +} + +bool OStreamMetricExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} + +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/metrics_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/metrics_exporter.cc new file mode 100644 index 000000000..4f4bbfd06 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/metrics_exporter.cc @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include "opentelemetry/exporters/ostream/metrics_exporter.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ + +OStreamMetricsExporter::OStreamMetricsExporter(std::ostream &sout) noexcept : sout_(sout) {} + +sdk::common::ExportResult OStreamMetricsExporter::Export( + const std::vector<sdk::metrics::Record> &records) noexcept +{ + for (auto record : records) + { + sout_ << "{" + << "\n name : " << record.GetName() + << "\n description : " << record.GetDescription() + << "\n labels : " << record.GetLabels(); + + auto aggregator = record.GetAggregator(); + + /** + * Unpack the AggregatorVariant from the record so we can pass the data type to + * PrintAggregatorVariant to unpack the Aggregator from the variant. + */ + if (nostd::holds_alternative<std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<int>>>( + aggregator)) + { + PrintAggregatorVariant<int>(aggregator); + } + else if (nostd::holds_alternative< + std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<short>>>(aggregator)) + { + PrintAggregatorVariant<short>(aggregator); + } + else if (nostd::holds_alternative< + std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<double>>>(aggregator)) + { + PrintAggregatorVariant<double>(aggregator); + } + else if (nostd::holds_alternative< + std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<float>>>(aggregator)) + { + PrintAggregatorVariant<float>(aggregator); + } + sout_ << "\n}\n"; + } + return sdk::common::ExportResult::kSuccess; +} + +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/span_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/span_exporter.cc new file mode 100644 index 000000000..226f98737 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/src/span_exporter.cc @@ -0,0 +1,178 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/ostream/span_exporter.h" +#include "opentelemetry/exporters/ostream/common_utils.h" + +#include <iostream> +#include <mutex> +#include "opentelemetry/sdk_config.h" + +namespace nostd = opentelemetry::nostd; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace trace_api = opentelemetry::trace; +namespace sdkcommon = opentelemetry::sdk::common; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace trace +{ + +std::ostream &operator<<(std::ostream &os, trace_api::SpanKind span_kind) +{ + switch (span_kind) + { + case trace_api::SpanKind::kClient: + return os << "Client"; + case trace_api::SpanKind::kInternal: + return os << "Internal"; + case trace_api::SpanKind::kServer: + return os << "Server"; + case trace_api::SpanKind::kProducer: + return os << "Producer"; + case trace_api::SpanKind::kConsumer: + return os << "Consumer"; + }; + return os << ""; +} + +OStreamSpanExporter::OStreamSpanExporter(std::ostream &sout) noexcept : sout_(sout) {} + +std::unique_ptr<trace_sdk::Recordable> OStreamSpanExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<trace_sdk::Recordable>(new trace_sdk::SpanData); +} + +sdk::common::ExportResult OStreamSpanExporter::Export( + const nostd::span<std::unique_ptr<trace_sdk::Recordable>> &spans) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[Ostream Trace Exporter] Exporting " + << spans.size() << " span(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + + for (auto &recordable : spans) + { + auto span = std::unique_ptr<trace_sdk::SpanData>( + static_cast<trace_sdk::SpanData *>(recordable.release())); + + if (span != nullptr) + { + + char trace_id[32] = {0}; + char span_id[16] = {0}; + char parent_span_id[16] = {0}; + + span->GetTraceId().ToLowerBase16(trace_id); + span->GetSpanId().ToLowerBase16(span_id); + span->GetParentSpanId().ToLowerBase16(parent_span_id); + + sout_ << "{" + << "\n name : " << span->GetName() + << "\n trace_id : " << std::string(trace_id, 32) + << "\n span_id : " << std::string(span_id, 16) + << "\n tracestate : " << span->GetSpanContext().trace_state()->ToHeader() + << "\n parent_span_id: " << std::string(parent_span_id, 16) + << "\n start : " << span->GetStartTime().time_since_epoch().count() + << "\n duration : " << span->GetDuration().count() + << "\n description : " << span->GetDescription() + << "\n span kind : " << span->GetSpanKind() + << "\n status : " << statusMap[int(span->GetStatus())] + << "\n attributes : "; + printAttributes(span->GetAttributes()); + sout_ << "\n events : "; + printEvents(span->GetEvents()); + sout_ << "\n links : "; + printLinks(span->GetLinks()); + sout_ << "\n resources : "; + printResources(span->GetResource()); + sout_ << "\n instr-lib : "; + printInstrumentationLibrary(span->GetInstrumentationLibrary()); + sout_ << "\n}\n"; + } + } + + return sdk::common::ExportResult::kSuccess; +} + +bool OStreamSpanExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; +} + +bool OStreamSpanExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} +void OStreamSpanExporter::printAttributes( + const std::unordered_map<std::string, sdkcommon::OwnedAttributeValue> &map, + const std::string prefix) +{ + for (const auto &kv : map) + { + sout_ << prefix << kv.first << ": "; + opentelemetry::exporter::ostream_common::print_value(kv.second, sout_); + } +} + +void OStreamSpanExporter::printEvents(const std::vector<trace_sdk::SpanDataEvent> &events) +{ + for (const auto &event : events) + { + sout_ << "\n\t{" + << "\n\t name : " << event.GetName() + << "\n\t timestamp : " << event.GetTimestamp().time_since_epoch().count() + << "\n\t attributes : "; + printAttributes(event.GetAttributes(), "\n\t\t"); + sout_ << "\n\t}"; + } +} + +void OStreamSpanExporter::printLinks(const std::vector<trace_sdk::SpanDataLink> &links) +{ + for (const auto &link : links) + { + char trace_id[32] = {0}; + char span_id[16] = {0}; + link.GetSpanContext().trace_id().ToLowerBase16(trace_id); + link.GetSpanContext().span_id().ToLowerBase16(span_id); + sout_ << "\n\t{" + << "\n\t trace_id : " << std::string(trace_id, 32) + << "\n\t span_id : " << std::string(span_id, 16) + << "\n\t tracestate : " << link.GetSpanContext().trace_state()->ToHeader() + << "\n\t attributes : "; + printAttributes(link.GetAttributes(), "\n\t\t"); + sout_ << "\n\t}"; + } +} + +void OStreamSpanExporter::printResources(const opentelemetry::sdk::resource::Resource &resources) +{ + auto attributes = resources.GetAttributes(); + if (attributes.size()) + { + printAttributes(attributes, "\n\t"); + } +} + +void OStreamSpanExporter::printInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) +{ + sout_ << instrumentation_library.GetName(); + auto version = instrumentation_library.GetVersion(); + if (version.size()) + { + sout_ << "-" << version; + } +} + +} // namespace trace +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_capture.h b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_capture.h new file mode 100644 index 000000000..6c61c7152 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_capture.h @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include <iostream> +#include <sstream> +#include <string> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace ostream +{ +namespace test +{ +/** + * The OStreamCapture captures from the specified stream for its lifetime + */ +class OStreamCapture +{ +public: + /** + * Create a OStreamCapture which will capture the output of the ostream that it was constructed + * with for the lifetime of the instance. + */ + OStreamCapture(std::ostream &ostream) : stream_(ostream), buf_(ostream.rdbuf()) + { + stream_.rdbuf(captured_.rdbuf()); + } + + ~OStreamCapture() { stream_.rdbuf(buf_); } + + /** + * Returns the captured data from the stream. + */ + std::string GetCaptured() const { return captured_.str(); } + +private: + std::ostream &stream_; + std::streambuf *buf_; + std::stringstream captured_; +}; + +/** + * Helper method to invoke the passed func while recording the output of the specified stream and + * return the output afterwards. + */ +template <typename Func> +std::string WithOStreamCapture(std::ostream &stream, Func func) +{ + OStreamCapture capture(stream); + func(); + return capture.GetCaptured(); +} + +} // namespace test +} // namespace ostream +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_log_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_log_test.cc new file mode 100644 index 000000000..91d8fbf24 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_log_test.cc @@ -0,0 +1,330 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_LOGS_PREVIEW + +# include <array> +# include "opentelemetry/exporters/ostream/log_exporter.h" +# include "opentelemetry/logs/provider.h" +# include "opentelemetry/nostd/span.h" +# include "opentelemetry/sdk/logs/logger_provider.h" +# include "opentelemetry/sdk/logs/simple_log_processor.h" + +# include <gtest/gtest.h> +# include <iostream> + +namespace sdklogs = opentelemetry::sdk::logs; +namespace logs_api = opentelemetry::logs; +namespace nostd = opentelemetry::nostd; +namespace exporterlogs = opentelemetry::exporter::logs; +namespace common = opentelemetry::common; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ + +// Test that when OStream Log exporter is shutdown, no logs should be sent to stream +TEST(OStreamLogExporter, Shutdown) +{ + auto exporter = std::unique_ptr<sdklogs::LogExporter>(new exporterlogs::OStreamLogExporter); + + // Save cout's original buffer here + std::streambuf *original = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + + EXPECT_TRUE(exporter->Shutdown()); + + // After processor/exporter is shutdown, no logs should be sent to stream + auto record = exporter->MakeRecordable(); + record->SetBody("Log record not empty"); + exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1)); + + // Restore original stringstream buffer + std::cout.rdbuf(original); + std::string err_message = + "[Ostream Log Exporter] Exporting 1 log(s) failed, exporter is shutdown"; + EXPECT_TRUE(output.str().find(err_message) != std::string::npos); +} + +// ---------------------------------- Print to cout ------------------------- + +// Testing what a default log record that has no values changed will print out +// This function tests MakeRecordable() as well as Export(). +TEST(OstreamLogExporter, DefaultLogRecordToCout) +{ + auto exporter = + std::unique_ptr<sdklogs::LogExporter>(new exporterlogs::OStreamLogExporter(std::cout)); + + // Save cout's original buffer here + std::streambuf *original = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + + // Pass a default recordable created by the exporter to be exported + auto log_record = exporter->MakeRecordable(); + exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&log_record, 1)); + + // Restore cout's original stringstream + std::cout.rdbuf(original); + + std::vector<std::string> expected_output{ + "{\n" + " timestamp : 0\n" + " severity_num : 0\n" + " severity_text : INVALID\n" + " body : \n", + " resource : \n", + "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n", + "telemetry.sdk.name: opentelemetry\n", + "telemetry.sdk.language: cpp\n", + " attributes : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"}; + + for (auto &expected : expected_output) + { + ASSERT_NE(output.str().find(expected), std::string::npos); + } +} + +// Testing what a log record with only the "timestamp", "severity", "name" and "message" fields set, +// will print out +TEST(OStreamLogExporter, SimpleLogToCout) +{ + // Initialize an Ostream exporter to std::cout + auto exporter = + std::unique_ptr<sdklogs::LogExporter>(new exporterlogs::OStreamLogExporter(std::cout)); + + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cout.rdbuf(); + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + + // Pass a default recordable created by the exporter to be exported + // Create a log record and manually timestamp, severity, name, message + common::SystemTimestamp now(std::chrono::system_clock::now()); + + auto record = std::unique_ptr<sdklogs::Recordable>(new sdklogs::LogRecord()); + record->SetTimestamp(now); + record->SetSeverity(logs_api::Severity::kTrace); // kTrace has enum value of 1 + record->SetBody("Message"); + + // Log a record to cout + exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1)); + + // Reset cout's original stringstream buffer + std::cout.rdbuf(original); + + std::vector<std::string> expected_output{ + "{\n" + " timestamp : " + + std::to_string(now.time_since_epoch().count()) + + "\n" + " severity_num : 1\n" + " severity_text : TRACE\n" + " body : Message\n", + " resource : \n", + "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n", + "telemetry.sdk.name: opentelemetry\n", + "telemetry.sdk.language: cpp\n", + " attributes : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"}; + + for (auto &expected : expected_output) + { + ASSERT_NE(output.str().find(expected), std::string::npos); + } +} + +// ---------------------------------- Print to cerr -------------------------- + +// Testing what a log record with only the "resource" and "attributes" fields +// (i.e. KeyValueIterable types) set with primitive types, will print out +TEST(OStreamLogExporter, LogWithStringAttributesToCerr) +{ + // Initialize an Ostream exporter to cerr + auto exporter = + std::unique_ptr<sdklogs::LogExporter>(new exporterlogs::OStreamLogExporter(std::cerr)); + + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cerr.rdbuf(); + std::stringstream stdcerrOutput; + std::cerr.rdbuf(stdcerrOutput.rdbuf()); + + // Pass a recordable created by the exporter to be exported + auto record = exporter->MakeRecordable(); + + // Set resources for this log record only of type <string, string> + auto resource = opentelemetry::sdk::resource::Resource::Create({{"key1", "val1"}}); + record->SetResource(resource); + + // Set attributes to this log record of type <string, AttributeValue> + record->SetAttribute("a", true); + + // Log record to cerr + exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1)); + + // Reset cerr's original stringstream buffer + std::cerr.rdbuf(original); + + std::vector<std::string> expected_output{ + "{\n" + " timestamp : 0\n" + " severity_num : 0\n" + " severity_text : INVALID\n" + " body : \n", + " resource : \n", + "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n", + "telemetry.sdk.name: opentelemetry\n", + "telemetry.sdk.language: cpp\n", + "service.name: unknown_service\n", + "key1: val1\n", + " attributes : \n", + "\ta: 1\n", + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"}; + + for (auto &expected : expected_output) + { + ASSERT_NE(stdcerrOutput.str().find(expected), std::string::npos); + } +} + +// ---------------------------------- Print to clog ------------------------- + +// Testing what a log record with only the "resource", and "attributes" fields +// (i.e. KeyValueIterable types), set with 2D arrays as values, will print out +TEST(OStreamLogExporter, LogWithVariantTypesToClog) +{ + + // Initialize an Ostream exporter to cerr + auto exporter = + std::unique_ptr<sdklogs::LogExporter>(new exporterlogs::OStreamLogExporter(std::clog)); + + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::clog.rdbuf(); + std::stringstream stdclogOutput; + std::clog.rdbuf(stdclogOutput.rdbuf()); + + // Pass a recordable created by the exporter to be exported + auto record = exporter->MakeRecordable(); + + // Set resources for this log record of only integer types as the value + std::array<int, 3> array1 = {1, 2, 3}; + nostd::span<int> data1{array1.data(), array1.size()}; + + auto resource = opentelemetry::sdk::resource::Resource::Create({{"res1", data1}}); + record->SetResource(resource); + + // Set resources for this log record of bool types as the value + // e.g. key/value is a par of type <string, array of bools> + std::array<bool, 3> array = {false, true, false}; + record->SetAttribute("attr1", nostd::span<bool>{array.data(), array.size()}); + + // Log a record to clog + exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1)); + + // Reset clog's original stringstream buffer + std::clog.rdbuf(original); + + std::vector<std::string> expected_output{ + "{\n" + " timestamp : 0\n" + " severity_num : 0\n" + " severity_text : INVALID\n" + " body : \n", + " resource : \n", + "service.name: unknown_service\n", + "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n", + "telemetry.sdk.name: opentelemetry\n", + "telemetry.sdk.language: cpp\n", + "res1: [1,2,3]\n", + "attributes : \n", + "\tattr1: [0,1,0]\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"}; + + for (auto &expected : expected_output) + { + ASSERT_NE(stdclogOutput.str().find(expected), std::string::npos); + } +} + +// // ---------------------------------- Integration Tests ------------------------- + +// Test using the simple log processor and ostream exporter to cout +// and use the rest of the logging pipeline (Logger, LoggerProvider, Provider) as well +TEST(OStreamLogExporter, IntegrationTest) +{ + // Initialize a logger + auto exporter = std::unique_ptr<sdklogs::LogExporter>(new exporterlogs::OStreamLogExporter); + auto sdkProvider = std::shared_ptr<sdklogs::LoggerProvider>(new sdklogs::LoggerProvider()); + sdkProvider->AddProcessor( + std::unique_ptr<sdklogs::LogProcessor>(new sdklogs::SimpleLogProcessor(std::move(exporter)))); + auto apiProvider = nostd::shared_ptr<logs_api::LoggerProvider>(sdkProvider); + auto provider = nostd::shared_ptr<logs_api::LoggerProvider>(apiProvider); + logs_api::Provider::SetLoggerProvider(provider); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger = logs_api::Provider::GetLoggerProvider()->GetLogger( + "Logger", "", "opentelelemtry_library", "", schema_url); + + // Back up cout's streambuf + std::streambuf *original = std::cout.rdbuf(); + + // Redirect cout to our string stream + std::stringstream stdcoutOutput; + std::cout.rdbuf(stdcoutOutput.rdbuf()); + + // Write a log to ostream exporter + common::SystemTimestamp now(std::chrono::system_clock::now()); + logger->Log(logs_api::Severity::kDebug, "Hello", {}, {}, {}, {}, now); + + // Restore cout's original streambuf + std::cout.rdbuf(original); + + // Compare actual vs expected outputs + std::vector<std::string> expected_output{ + "{\n" + " timestamp : " + + std::to_string(now.time_since_epoch().count()) + + "\n" + " severity_num : 5\n" + " severity_text : DEBUG\n" + " body : Hello\n", + " resource : \n", + "telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n", + "service.name: unknown_service\n", + "telemetry.sdk.name: opentelemetry\n", + "telemetry.sdk.language: cpp\n", + " attributes : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"}; + + for (auto &expected : expected_output) + { + ASSERT_NE(stdcoutOutput.str().find(expected), std::string::npos); + } +} + +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_metric_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_metric_test.cc new file mode 100644 index 000000000..45d6d8f88 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_metric_test.cc @@ -0,0 +1,269 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +#ifndef ENABLE_METRICS_PREVIEW + +# include <gtest/gtest.h> +# include <memory> +# include <vector> +# include "opentelemetry/sdk/metrics/instruments.h" +# include "opentelemetry/sdk/resource/resource_detector.h" + +# include <iostream> +# include "opentelemetry/exporters/ostream/metric_exporter.h" +# include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" +# include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h" +# include "opentelemetry/sdk/metrics/data/metric_data.h" +# include "opentelemetry/sdk/resource/resource.h" + +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace nostd = opentelemetry::nostd; +namespace exportermetrics = opentelemetry::exporter::metrics; + +TEST(OStreamMetricsExporter, Shutdown) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricExporter>(new exportermetrics::OStreamMetricExporter); + ASSERT_TRUE(exporter->Shutdown()); + auto result = exporter->Export(metric_sdk::ResourceMetrics{}); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); +} + +TEST(OStreamMetricsExporter, ExportSumPointData) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricExporter>(new exportermetrics::OStreamMetricExporter); + + metric_sdk::SumPointData sum_point_data{}; + sum_point_data.value_ = 10.0; + metric_sdk::SumPointData sum_point_data2{}; + sum_point_data2.value_ = 20.0; + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector<metric_sdk::PointDataAttributes>{ + {metric_sdk::PointAttributes{{"a1", "b1"}}, sum_point_data}, + {metric_sdk::PointAttributes{{"a1", "b1"}}, sum_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector<metric_sdk::InstrumentationInfoMetrics>{ + {instrumentation_library.get(), std::vector<metric_sdk::MetricData>{metric_data}}}; + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expected_output = + "{" + "\n name\t\t: library_name" + "\n schema url\t: " + "\n version\t: 1.2.0" + "\n start time\t: Thu Jan 1 00:00:00 1970" + "\n end time\t: Thu Jan 1 00:00:00 1970" + "\n name\t\t: library_name" + "\n description\t: description" + "\n unit\t\t: unit" + "\n type\t\t: SumPointData" + "\n value\t\t: 10" + "\n attributes\t\t: " + "\n\ta1: b1" + "\n type\t\t: SumPointData" + "\n value\t\t: 20" + "\n attributes\t\t: " + "\n\ta1: b1" + "\n}\n"; + ASSERT_EQ(stdoutOutput.str(), expected_output); +} + +TEST(OStreamMetricsExporter, ExportHistogramPointData) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricExporter>(new exportermetrics::OStreamMetricExporter); + + metric_sdk::HistogramPointData histogram_point_data{}; + histogram_point_data.boundaries_ = std::list<double>{10.1, 20.2, 30.2}; + histogram_point_data.count_ = 3; + histogram_point_data.counts_ = {200, 300, 400, 500}; + histogram_point_data.sum_ = 900.5; + metric_sdk::HistogramPointData histogram_point_data2{}; + histogram_point_data2.boundaries_ = std::list<long>{10, 20, 30}; + histogram_point_data2.count_ = 3; + histogram_point_data2.counts_ = {200, 300, 400, 500}; + histogram_point_data2.sum_ = 900l; + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector<metric_sdk::PointDataAttributes>{ + {metric_sdk::PointAttributes{{"a1", "b1"}, {"a2", "b2"}}, histogram_point_data}, + {metric_sdk::PointAttributes{{"a1", "b1"}}, histogram_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector<metric_sdk::InstrumentationInfoMetrics>{ + {instrumentation_library.get(), std::vector<metric_sdk::MetricData>{metric_data}}}; + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expected_output = + "{" + "\n name\t\t: library_name" + "\n schema url\t: " + "\n version\t: 1.2.0" + "\n start time\t: Thu Jan 1 00:00:00 1970" + "\n end time\t: Thu Jan 1 00:00:00 1970" + "\n name\t\t: library_name" + "\n description\t: description" + "\n unit\t\t: unit" + "\n type : HistogramPointData" + "\n count : 3" + "\n sum : 900.5" + "\n buckets : [10.1, 20.2, 30.2, ]" + "\n counts : [200, 300, 400, 500, ]" + "\n attributes\t\t: " + "\n\ta1: b1" + "\n\ta2: b2" + "\n type : HistogramPointData" + "\n count : 3" + "\n sum : 900" + "\n buckets : [10, 20, 30, ]" + "\n counts : [200, 300, 400, 500, ]" + "\n attributes\t\t: " + "\n\ta1: b1" + "\n}\n"; + ASSERT_EQ(stdoutOutput.str(), expected_output); +} + +TEST(OStreamMetricsExporter, ExportLastValuePointData) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricExporter>(new exportermetrics::OStreamMetricExporter); + + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::LastValuePointData last_value_point_data{}; + last_value_point_data.value_ = 10.0; + last_value_point_data.is_lastvalue_valid_ = true; + last_value_point_data.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + metric_sdk::LastValuePointData last_value_point_data2{}; + last_value_point_data2.value_ = 20l; + last_value_point_data2.is_lastvalue_valid_ = true; + last_value_point_data2.sample_ts_ = opentelemetry::common::SystemTimestamp{}; + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector<metric_sdk::PointDataAttributes>{ + {metric_sdk::PointAttributes{}, last_value_point_data}, + {metric_sdk::PointAttributes{}, last_value_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector<metric_sdk::InstrumentationInfoMetrics>{ + {instrumentation_library.get(), std::vector<metric_sdk::MetricData>{metric_data}}}; + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expected_output = + "{" + "\n name\t\t: library_name" + "\n schema url\t: " + "\n version\t: 1.2.0" + "\n start time\t: Thu Jan 1 00:00:00 1970" + "\n end time\t: Thu Jan 1 00:00:00 1970" + "\n name\t\t: library_name" + "\n description\t: description" + "\n unit\t\t: unit" + "\n type : LastValuePointData" + "\n timestamp : 0" + "\n valid : true" + "\n value : 10" + "\n attributes\t\t: " + "\n type : LastValuePointData" + "\n timestamp : 0" + "\n valid : true" + "\n value : 20" + "\n attributes\t\t: " + "\n}\n"; + ASSERT_EQ(stdoutOutput.str(), expected_output); +} + +TEST(OStreamMetricsExporter, ExportDropPointData) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricExporter>(new exportermetrics::OStreamMetricExporter); + + metric_sdk::ResourceMetrics data; + auto resource = opentelemetry::sdk::resource::Resource::Create( + opentelemetry::sdk::resource::ResourceAttributes{}); + data.resource_ = &resource; + auto instrumentation_library = + opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary::Create("library_name", + "1.2.0"); + metric_sdk::DropPointData drop_point_data{}; + metric_sdk::DropPointData drop_point_data2{}; + metric_sdk::MetricData metric_data{ + metric_sdk::InstrumentDescriptor{"library_name", "description", "unit", + metric_sdk::InstrumentType::kCounter, + metric_sdk::InstrumentValueType::kDouble}, + opentelemetry::common::SystemTimestamp{}, opentelemetry::common::SystemTimestamp{}, + std::vector<metric_sdk::PointDataAttributes>{ + {metric_sdk::PointAttributes{}, drop_point_data}, + {metric_sdk::PointAttributes{}, drop_point_data2}}}; + data.instrumentation_info_metric_data_ = std::vector<metric_sdk::InstrumentationInfoMetrics>{ + {instrumentation_library.get(), std::vector<metric_sdk::MetricData>{metric_data}}}; + + std::stringstream stdoutOutput; + std::streambuf *sbuf = std::cout.rdbuf(); + std::cout.rdbuf(stdoutOutput.rdbuf()); + + auto result = exporter->Export(data); + EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kSuccess); + std::cout.rdbuf(sbuf); + + std::string expected_output = + "{" + "\n name\t\t: library_name" + "\n schema url\t: " + "\n version\t: 1.2.0" + "\n start time\t: Thu Jan 1 00:00:00 1970" + "\n end time\t: Thu Jan 1 00:00:00 1970" + "\n name\t\t: library_name" + "\n description\t: description" + "\n unit\t\t: unit" + "\n}\n"; + + ASSERT_EQ(stdoutOutput.str(), expected_output); +} + +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_metrics_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_metrics_test.cc new file mode 100644 index 000000000..f5539c3d4 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_metrics_test.cc @@ -0,0 +1,295 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include <gtest/gtest.h> +#ifdef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/exporters/ostream/metrics_exporter.h" +# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/exact_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/gauge_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/min_max_sum_count_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/sketch_aggregator.h" +# include "opentelemetry/sdk/_metrics/exporter.h" +# include "opentelemetry/sdk/_metrics/record.h" + +# include <iostream> + +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; +namespace exportermetrics = opentelemetry::exporter::metrics; + +TEST(OStreamMetricsExporter, PrintCounter) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricsExporter>(new exportermetrics::OStreamMetricsExporter); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(5.5); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + std::vector<metric_sdk::Record> records; + records.push_back(r); + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + exporter->Export(records); + + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : name\n" + " description : description\n" + " labels : labels\n" + " sum : 5.5\n" + "}\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, PrintMinMaxSumCount) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricsExporter>(new exportermetrics::OStreamMetricsExporter); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::MinMaxSumCountAggregator<int>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(1); + aggregator->update(2); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + std::vector<metric_sdk::Record> records; + records.push_back(r); + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + exporter->Export(records); + + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : name\n" + " description : description\n" + " labels : labels\n" + " min : 1\n" + " max : 2\n" + " sum : 3\n" + " count : 2\n" + "}\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, PrintGauge) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricsExporter>(new exportermetrics::OStreamMetricsExporter); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<short>>( + new metric_sdk::GaugeAggregator<short>(metrics_api::InstrumentKind::Counter)); + + aggregator->update(1); + aggregator->update(9); + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + std::vector<metric_sdk::Record> records; + records.push_back(r); + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + exporter->Export(records); + + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : name\n" + " description : description\n" + " labels : labels\n" + " last value : 9\n" + " timestamp : " + + std::to_string(aggregator->get_checkpoint_timestamp().time_since_epoch().count()) + + "\n" + "}\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, PrintExact) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricsExporter>(new exportermetrics::OStreamMetricsExporter); + + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<short>>( + new metric_sdk::ExactAggregator<short>(metrics_api::InstrumentKind::Counter, true)); + + auto aggregator2 = std::shared_ptr<metric_sdk::Aggregator<short>>( + new metric_sdk::ExactAggregator<short>(metrics_api::InstrumentKind::Counter, false)); + + for (int i = 0; i < 10; i++) + { + aggregator->update(i); + aggregator2->update(i); + } + aggregator->checkpoint(); + aggregator2->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + metric_sdk::Record r2("name", "description", "labels", aggregator2); + std::vector<metric_sdk::Record> records; + records.push_back(r); + records.push_back(r2); + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + exporter->Export(records); + + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : name\n" + " description : description\n" + " labels : labels\n" + " quantiles : [0: 0, .25: 3, .50: 5, .75: 7, 1: 9]\n" + "}\n" + "{\n" + " name : name\n" + " description : description\n" + " labels : labels\n" + " values : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" + "}\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, PrintHistogram) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricsExporter>(new exportermetrics::OStreamMetricsExporter); + + std::vector<double> boundaries{10, 20, 30, 40, 50}; + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<float>>( + new metric_sdk::HistogramAggregator<float>(metrics_api::InstrumentKind::Counter, boundaries)); + + for (float i = 0; i < 60; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + std::vector<metric_sdk::Record> records; + records.push_back(r); + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + exporter->Export(records); + + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : name\n" + " description : description\n" + " labels : labels\n" + " buckets : [10, 20, 30, 40, 50]\n" + " counts : [10, 10, 10, 10, 10, 10]\n" + "}\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} + +TEST(OStreamMetricsExporter, PrintSketch) +{ + auto exporter = + std::unique_ptr<metric_sdk::MetricsExporter>(new exportermetrics::OStreamMetricsExporter); + + std::vector<double> boundaries{1, 3, 5, 7, 9}; + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::SketchAggregator<int>(metrics_api::InstrumentKind::Counter, .000005)); + + for (int i = 0; i < 10; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + + metric_sdk::Record r("name", "description", "labels", aggregator); + std::vector<metric_sdk::Record> records; + records.push_back(r); + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + exporter->Export(records); + + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " name : name\n" + " description : description\n" + " labels : labels\n" + " buckets : [0, 0.999995, 2, 3.00001, 4, 4.99999, 5.99997, 7.00003, 8.00003, 9]\n" + " counts : [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n" + "}\n"; + + ASSERT_EQ(stdoutOutput.str(), expectedOutput); +} +#else +TEST(OStreamMetricsExporter, DummyTest) +{ + // empty +} +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_span_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_span_test.cc new file mode 100644 index 000000000..edfd66505 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/ostream/test/ostream_span_test.cc @@ -0,0 +1,398 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/exporters/ostream/span_exporter.h" + +#include "ostream_capture.h" + +#include <gtest/gtest.h> +#include <iostream> + +using namespace opentelemetry::exporter::ostream::test; + +namespace trace = opentelemetry::trace; +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace resource = opentelemetry::sdk::resource; +namespace exportertrace = opentelemetry::exporter::trace; + +using Attributes = std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>>; + +class TestResource : public resource::Resource +{ +public: + TestResource(resource::ResourceAttributes attributes = resource::ResourceAttributes()) + : resource::Resource(attributes) + {} +}; + +// Testing Shutdown functionality of OStreamSpanExporter, should expect no data to be sent to Stream +TEST(OStreamSpanExporter, Shutdown) +{ + auto exporter = std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + recordable->SetName("Test Span"); + + // Capture the output of cout + const auto captured = WithOStreamCapture(std::cout, [&]() { + EXPECT_TRUE(processor->Shutdown()); + processor->OnEnd(std::move(recordable)); + }); + std::string err_message = + "[Ostream Trace Exporter] Exporting 1 span(s) failed, exporter is shutdown"; + EXPECT_TRUE(captured.find(err_message) != std::string::npos); +} + +constexpr const char *kDefaultSpanPrinted = + "{\n" + " name : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " tracestate : \n" + " parent_span_id: 0000000000000000\n" + " start : 0\n" + " duration : 0\n" + " description : \n" + " span kind : Internal\n" + " status : Unset\n" + " attributes : \n" + " events : \n" + " links : \n" + " resources : \n" + " instr-lib : unknown_service\n" + "}\n"; + +// Testing what a default span that is not changed will print out, either all 0's or empty values +TEST(OStreamSpanExporter, PrintDefaultSpan) +{ + std::stringstream output; + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(output)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + processor->OnEnd(std::move(recordable)); + + EXPECT_EQ(output.str(), kDefaultSpanPrinted); +} + +TEST(OStreamSpanExporter, PrintSpanWithBasicFields) +{ + std::stringstream output; + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(output)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + 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::TraceId trace_id{trace_id_buf}; + trace::SpanId span_id{span_id_buf}; + trace::SpanId parent_span_id{parent_span_id_buf}; + const auto trace_state = trace::TraceState::GetDefault()->Set("state1", "value"); + const trace::SpanContext span_context{ + trace_id, span_id, trace::TraceFlags{trace::TraceFlags::kIsSampled}, true, trace_state}; + + recordable->SetIdentity(span_context, parent_span_id); + recordable->SetName("Test Span"); + common::SystemTimestamp now(std::chrono::system_clock::now()); + recordable->SetStartTime(now); + recordable->SetDuration(std::chrono::nanoseconds(100)); + recordable->SetStatus(trace::StatusCode::kOk, "Test Description"); + recordable->SetSpanKind(trace::SpanKind::kClient); + + TestResource resource1(resource::ResourceAttributes({{"key1", "val1"}})); + recordable->SetResource(resource1); + + processor->OnEnd(std::move(recordable)); + + std::string start = std::to_string(now.time_since_epoch().count()); + + std::string expectedOutput = + "{\n" + " name : Test Span\n" + " trace_id : 01020304050607080102030405060708\n" + " span_id : 0102030405060708\n" + " tracestate : state1=value\n" + " parent_span_id: 0807060504030201\n" + " start : " + + start + + "\n" + " duration : 100\n" + " description : Test Description\n" + " span kind : Client\n" + " status : Ok\n" + " attributes : \n" + " events : \n" + " links : \n" + " resources : \n" + "\tkey1: val1\n" + " instr-lib : unknown_service\n" + "}\n"; + EXPECT_EQ(output.str(), expectedOutput); +} + +TEST(OStreamSpanExporter, PrintSpanWithAttribute) +{ + std::stringstream output; + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(output)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + recordable->SetAttribute("attr1", "string"); + + processor->OnEnd(std::move(recordable)); + + std::string expectedOutput = + "{\n" + " name : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " tracestate : \n" + " parent_span_id: 0000000000000000\n" + " start : 0\n" + " duration : 0\n" + " description : \n" + " span kind : Internal\n" + " status : Unset\n" + " attributes : \n" + "\tattr1: string\n" + " events : \n" + " links : \n" + " resources : \n" + " instr-lib : unknown_service\n" + "}\n"; + EXPECT_EQ(output.str(), expectedOutput); +} + +TEST(OStreamSpanExporter, PrintSpanWithArrayAttribute) +{ + std::stringstream output; + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(output)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + std::array<int, 3> array1 = {1, 2, 3}; + nostd::span<int> span1{array1.data(), array1.size()}; + recordable->SetAttribute("array1", span1); + + processor->OnEnd(std::move(recordable)); + + std::string expectedOutput = + "{\n" + " name : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " tracestate : \n" + " parent_span_id: 0000000000000000\n" + " start : 0\n" + " duration : 0\n" + " description : \n" + " span kind : Internal\n" + " status : Unset\n" + " attributes : \n" + "\tarray1: [1,2,3]\n" + " events : \n" + " links : \n" + " resources : \n" + " instr-lib : unknown_service\n" + "}\n"; + EXPECT_EQ(output.str(), expectedOutput); +} + +TEST(OStreamSpanExporter, PrintSpanWithEvents) +{ + std::stringstream output; + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(output)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + common::SystemTimestamp now(std::chrono::system_clock::now()); + common::SystemTimestamp next(std::chrono::system_clock::now() + std::chrono::seconds(1)); + + std::string now_str = std::to_string(now.time_since_epoch().count()); + std::string next_str = std::to_string(next.time_since_epoch().count()); + + recordable->AddEvent("hello", now); + recordable->AddEvent("world", next, + common::KeyValueIterableView<Attributes>({{"attr1", "string"}})); + + processor->OnEnd(std::move(recordable)); + + std::string expectedOutput = + "{\n" + " name : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " tracestate : \n" + " parent_span_id: 0000000000000000\n" + " start : 0\n" + " duration : 0\n" + " description : \n" + " span kind : Internal\n" + " status : Unset\n" + " attributes : \n" + " events : \n" + "\t{\n" + "\t name : hello\n" + "\t timestamp : " + + now_str + + "\n" + "\t attributes : \n" + "\t}\n" + "\t{\n" + "\t name : world\n" + "\t timestamp : " + + next_str + + "\n" + "\t attributes : \n" + "\t\tattr1: string\n" + "\t}\n" + " links : \n" + " resources : \n" + " instr-lib : unknown_service\n" + "}\n"; + EXPECT_EQ(output.str(), expectedOutput); +} + +TEST(OStreamSpanExporter, PrintSpanWithLinks) +{ + std::stringstream output; + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(output)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + // produce valid SpanContext with pseudo span and trace Id. + uint8_t span_id_buf[trace::SpanId::kSize] = { + 1, + }; + trace::SpanId span_id{span_id_buf}; + uint8_t trace_id_buf[trace::TraceId::kSize] = { + 2, + }; + trace::TraceId trace_id{trace_id_buf}; + const auto span_context = + trace::SpanContext(trace_id, span_id, trace::TraceFlags{trace::TraceFlags::kIsSampled}, true); + + // and another to check preserving order. + uint8_t span_id_buf2[trace::SpanId::kSize] = { + 3, + }; + trace::SpanId span_id2{span_id_buf2}; + const auto span_context2 = + trace::SpanContext(trace_id, span_id2, trace::TraceFlags{trace::TraceFlags::kIsSampled}, true, + trace::TraceState::FromHeader("state1=value")); + + recordable->AddLink(span_context); + recordable->AddLink(span_context2, + common::KeyValueIterableView<Attributes>({{"attr1", "string"}})); + + processor->OnEnd(std::move(recordable)); + + std::string expectedOutput = + "{\n" + " name : \n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " tracestate : \n" + " parent_span_id: 0000000000000000\n" + " start : 0\n" + " duration : 0\n" + " description : \n" + " span kind : Internal\n" + " status : Unset\n" + " attributes : \n" + " events : \n" + " links : \n" + "\t{\n" + "\t trace_id : 02000000000000000000000000000000\n" + "\t span_id : 0100000000000000\n" + "\t tracestate : \n" + "\t attributes : \n" + "\t}\n" + "\t{\n" + "\t trace_id : 02000000000000000000000000000000\n" + "\t span_id : 0300000000000000\n" + "\t tracestate : state1=value\n" + "\t attributes : \n" + "\t\tattr1: string\n" + "\t}\n" + " resources : \n" + " instr-lib : unknown_service\n" + "}\n"; + EXPECT_EQ(output.str(), expectedOutput); +} + +// Test with the three common ostreams, tests are more of a sanity check and usage examples. +TEST(OStreamSpanExporter, PrintSpanToCout) +{ + auto exporter = std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + const auto captured = + WithOStreamCapture(std::cout, [&]() { processor->OnEnd(std::move(recordable)); }); + + EXPECT_EQ(captured, kDefaultSpanPrinted); +} + +TEST(OStreamSpanExporter, PrintSpanToCerr) +{ + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(std::cerr)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + const auto captured = + WithOStreamCapture(std::cerr, [&]() { processor->OnEnd(std::move(recordable)); }); + + EXPECT_EQ(captured, kDefaultSpanPrinted); +} + +TEST(OStreamSpanExporter, PrintSpanToClog) +{ + auto exporter = + std::unique_ptr<trace_sdk::SpanExporter>(new exportertrace::OStreamSpanExporter(std::clog)); + auto processor = std::shared_ptr<trace_sdk::SpanProcessor>( + new trace_sdk::SimpleSpanProcessor(std::move(exporter))); + + auto recordable = processor->MakeRecordable(); + + const auto captured = + WithOStreamCapture(std::clog, [&]() { processor->OnEnd(std::move(recordable)); }); + + EXPECT_EQ(captured, kDefaultSpanPrinted); +} 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 diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/BUILD new file mode 100644 index 000000000..47c9bbab1 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/BUILD @@ -0,0 +1,144 @@ +# 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"]) + +cc_library( + name = "prometheus_exporter_deprecated", + srcs = [ + "src/prometheus_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/prometheus/prometheus_exporter.h", + ], + strip_include_prefix = "include", + tags = ["prometheus"], + deps = [ + ":prometheus_collector_deprecated", + ":prometheus_exporter_utils_deprecated", + "//api", + "//sdk:headers", + "@com_github_jupp0r_prometheus_cpp//core", + "@com_github_jupp0r_prometheus_cpp//pull", + ], +) + +cc_library( + name = "prometheus_exporter_utils_deprecated", + srcs = [ + "src/prometheus_exporter_utils.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h", + ], + strip_include_prefix = "include", + tags = ["prometheus"], + deps = [ + "//api", + "//sdk:headers", + "@com_github_jupp0r_prometheus_cpp//core", + "@com_github_jupp0r_prometheus_cpp//pull", + ], +) + +cc_library( + name = "prometheus_collector_deprecated", + srcs = [ + "src/prometheus_collector.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/prometheus/prometheus_collector.h", + ], + strip_include_prefix = "include", + tags = ["prometheus"], + deps = [ + ":prometheus_exporter_utils_deprecated", + "//api", + "//sdk:headers", + "@com_github_jupp0r_prometheus_cpp//core", + "@com_github_jupp0r_prometheus_cpp//pull", + ], +) + +cc_test( + name = "prometheus_exporter_test_deprecated", + srcs = [ + "test/prometheus_exporter_test.cc", + ], + tags = [ + "prometheus", + "test", + ], + deps = [ + ":prometheus_exporter_deprecated", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "prometheus_exporter", + srcs = [ + "src/exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/prometheus/exporter.h", + ], + strip_include_prefix = "include", + tags = ["prometheus"], + deps = [ + ":prometheus_collector", + ":prometheus_exporter_utils", + "//api", + "//sdk:headers", + "@com_github_jupp0r_prometheus_cpp//core", + "@com_github_jupp0r_prometheus_cpp//pull", + ], +) + +cc_library( + name = "prometheus_exporter_utils", + srcs = [ + "src/exporter_utils.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/prometheus/exporter_utils.h", + ], + strip_include_prefix = "include", + tags = ["prometheus"], + deps = [ + "//api", + "//sdk:headers", + "@com_github_jupp0r_prometheus_cpp//core", + "@com_github_jupp0r_prometheus_cpp//pull", + ], +) + +cc_library( + name = "prometheus_collector", + srcs = [ + "src/collector.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/prometheus/collector.h", + ], + strip_include_prefix = "include", + tags = ["prometheus"], + deps = [ + ":prometheus_exporter_utils", + "//api", + "//sdk:headers", + "@com_github_jupp0r_prometheus_cpp//core", + "@com_github_jupp0r_prometheus_cpp//pull", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/CMakeLists.txt new file mode 100755 index 000000000..56523ec84 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/CMakeLists.txt @@ -0,0 +1,90 @@ +# 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. + +include_directories(include) +if(NOT TARGET prometheus-cpp::core) + find_package(prometheus-cpp CONFIG REQUIRED) +endif() +if(WITH_METRICS_PREVIEW) + add_library( + prometheus_exporter_deprecated + src/prometheus_exporter.cc src/prometheus_collector.cc + src/prometheus_exporter_utils.cc) + target_include_directories( + prometheus_exporter_deprecated + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + + set(PROMETHEUS_EXPORTER_TARGETS_DEPRECATED prometheus_exporter_deprecated) + if(TARGET pull) + list(APPEND PROMETHEUS_EXPORTER_TARGETS_DEPRECATED pull) + endif() + if(TARGET core) + list(APPEND PROMETHEUS_EXPORTER_TARGETS_DEPRECATED core) + endif() + target_link_libraries( + prometheus_exporter_deprecated + PUBLIC opentelemetry_metrics_deprecated prometheus-cpp::pull + prometheus-cpp::core) + install( + TARGETS ${PROMETHEUS_EXPORTER_TARGETS_DEPRECATED} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install( + DIRECTORY include/opentelemetry/exporters/prometheus + DESTINATION include/opentelemetry/exporters/ + FILES_MATCHING + PATTERN "*.h") + if(BUILD_TESTING) + add_subdirectory(test) + endif() +else() + + add_library(prometheus_exporter src/exporter.cc src/collector.cc + src/exporter_utils.cc) + target_include_directories( + prometheus_exporter + PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" + "$<INSTALL_INTERFACE:include>") + + set(PROMETHEUS_EXPORTER_TARGETS prometheus_exporter) + if(TARGET pull) + list(APPEND PROMETHEUS_EXPORTER_TARGETS pull) + endif() + if(TARGET core) + list(APPEND PROMETHEUS_EXPORTER_TARGETS core) + endif() + target_link_libraries( + prometheus_exporter PUBLIC opentelemetry_metrics prometheus-cpp::pull + prometheus-cpp::core) + install( + TARGETS ${PROMETHEUS_EXPORTER_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/prometheus + DESTINATION include/opentelemetry/exporters/ + FILES_MATCHING + PATTERN "*.h") + + if(BUILD_TESTING) + add_subdirectory(test) + endif() +endif() diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h new file mode 100644 index 000000000..68f50d29f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/collector.h @@ -0,0 +1,87 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifndef ENABLE_METRICS_PREVIEW + +# include <memory> +# include <mutex> +# include <vector> + +# include <prometheus/collectable.h> +# include <prometheus/metric_family.h> +# include "opentelemetry/exporters/prometheus/exporter_utils.h" + +namespace prometheus_client = ::prometheus; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ +/** + * The Prometheus Collector maintains the intermediate collection in Prometheus Exporter + */ +class PrometheusCollector : public prometheus_client::Collectable +{ +public: + /** + * Default Constructor. + * + * This constructor initializes the collection for metrics to export + * in this class with default capacity + */ + explicit PrometheusCollector(size_t max_collection_size = 2048); + + /** + * Collects all metrics data from metricsToCollect collection. + * + * @return all metrics in the metricsToCollect snapshot + */ + std::vector<prometheus_client::MetricFamily> Collect() const override; + + /** + * This function is called by export() function and add the collection of + * records to the metricsToCollect collection + * + * @param records a collection of records to add to the metricsToCollect collection + */ + void AddMetricData(const sdk::metrics::ResourceMetrics &data); + + /** + * Get the current collection in the collector. + * + * @return the current metricsToCollect collection + */ + std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &GetCollection(); + + /** + * Gets the maximum size of the collection. + * + * @return max collection size + */ + int GetMaxCollectionSize() const; + +private: + /** + * Collection of metrics data from the export() function, and to be export + * to user when they send a pull request. This collection is a pointer + * to a collection so Collect() is able to clear the collection, even + * though it is a const function. + */ + mutable std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> metrics_to_collect_; + + /** + * Maximum size of the metricsToCollect collection. + */ + size_t max_collection_size_; + + /* + * Lock when operating the metricsToCollect collection + */ + mutable std::mutex collection_lock_; +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h new file mode 100644 index 000000000..59ef1a11a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter.h @@ -0,0 +1,126 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifndef ENABLE_METRICS_PREVIEW +# include <memory> +# include <string> +# include <vector> + +# include <prometheus/exposer.h> +# include "opentelemetry/common/spin_lock_mutex.h" +# include "opentelemetry/exporters/prometheus/collector.h" +# include "opentelemetry/nostd/span.h" +# include "opentelemetry/sdk/common/env_variables.h" +# include "opentelemetry/sdk/metrics/metric_exporter.h" +# include "opentelemetry/version.h" + +/** + * This class is an implementation of the MetricsExporter interface and + * exports Prometheus metrics data. Functions in this class should be + * called by the Controller in our data pipeline. + */ + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporter +{ +namespace metrics +{ + +inline const std::string GetPrometheusDefaultHttpEndpoint() +{ + constexpr char kPrometheusEndpointEnv[] = "PROMETHEUS_EXPORTER_ENDPOINT"; + constexpr char kPrometheusEndpointDefault[] = "localhost:9464"; + + auto endpoint = opentelemetry::sdk::common::GetEnvironmentVariable(kPrometheusEndpointEnv); + return endpoint.size() ? endpoint : kPrometheusEndpointDefault; +} + +/** + * Struct to hold Prometheus exporter options. + */ +struct PrometheusExporterOptions +{ + // The endpoint the Prometheus backend can collect metrics from + std::string url = GetPrometheusDefaultHttpEndpoint(); +}; + +class PrometheusExporter : public sdk::metrics::MetricExporter +{ +public: + /** + * Constructor - binds an exposer and collector to the exporter + * @param options: options for an exposer that exposes + * an HTTP endpoint for the exporter to connect to + */ + PrometheusExporter(const PrometheusExporterOptions &options); + + /** + * Exports a batch of Metric Records. + * @param records: a collection of records to export + * @return: returns a ReturnCode detailing a success, or type of failure + */ + sdk::common::ExportResult Export(const sdk::metrics::ResourceMetrics &data) noexcept override; + + /** + * Force flush the exporter. + */ + bool ForceFlush( + std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + + /** + * Shuts down the exporter and does cleanup. + * Since Prometheus is a pull based interface, + * we cannot serve data remaining in the intermediate + * collection to to client an HTTP request being sent, + * so we flush the data. + */ + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + + /** + * @return: returns a shared_ptr to + * the PrometheusCollector instance + */ + std::shared_ptr<PrometheusCollector> &GetCollector(); + + /** + * @return: Gets the shutdown status of the exporter + */ + bool IsShutdown() const; + +private: + // The configuration options associated with this exporter. + const PrometheusExporterOptions options_; + /** + * exporter shutdown status + */ + bool is_shutdown_; + + /** + * Pointer to a + * PrometheusCollector instance + */ + std::shared_ptr<PrometheusCollector> collector_; + + /** + * Pointer to an + * Exposer instance + */ + std::unique_ptr<::prometheus::Exposer> exposer_; + + /** + * friend class for testing + */ + friend class PrometheusExporterTest; + + /** + * PrometheusExporter constructor with no parameters + * Used for testing only + */ + PrometheusExporter(); +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif // ENABLE_METRICS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h new file mode 100644 index 000000000..c8df4f6cf --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/exporter_utils.h @@ -0,0 +1,115 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifndef ENABLE_METRICS_PREVIEW + +# include <prometheus/metric_family.h> +# include <string> +# include <vector> +# include "opentelemetry/metrics/provider.h" +# include "opentelemetry/sdk/metrics/meter.h" +# include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ +/** + * The Prometheus Utils contains utility functions for Prometheus Exporter + */ +class PrometheusExporterUtils +{ +public: + /** + * Helper function to convert OpenTelemetry metrics data collection + * to Prometheus metrics data collection + * + * @param records a collection of metrics in OpenTelemetry + * @return a collection of translated metrics that is acceptable by Prometheus + */ + static std::vector<::prometheus::MetricFamily> TranslateToPrometheus( + const std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &data); + +private: + /** + * Sanitize the given metric name or label according to Prometheus rule. + * + * This function is needed because names in OpenTelemetry can contain + * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the + * name should only contain alphanumeric characters and '_'. + */ + static std::string SanitizeNames(std::string name); + + static opentelemetry::sdk::metrics::AggregationType getAggregationType( + const opentelemetry::sdk::metrics::PointType &point_type); + + /** + * Translate the OTel metric type to Prometheus metric type + */ + static ::prometheus::MetricType TranslateType(opentelemetry::sdk::metrics::AggregationType kind); + + /** + * Set metric data for: + * Counter => Prometheus Counter + */ + template <typename T> + static void SetData(std::vector<T> values, + const opentelemetry::sdk::metrics::PointAttributes &labels, + ::prometheus::MetricType type, + std::chrono::nanoseconds time, + ::prometheus::MetricFamily *metric_family); + + /** + * Set metric data for: + * Histogram => Prometheus Histogram + */ + template <typename T> + static void SetData(std::vector<T> values, + const opentelemetry::sdk::metrics::ListType &boundaries, + const std::vector<uint64_t> &counts, + const opentelemetry::sdk::metrics::PointAttributes &labels, + std::chrono::nanoseconds time, + ::prometheus::MetricFamily *metric_family); + + /** + * Set time and labels to metric data + */ + static void SetMetricBasic(::prometheus::ClientMetric &metric, + std::chrono::nanoseconds time, + const opentelemetry::sdk::metrics::PointAttributes &labels); + + /** + * Convert attribute value to string + */ + static std::string AttributeValueToString( + const opentelemetry::sdk::common::OwnedAttributeValue &value); + + /** + * Handle Counter and Gauge. + */ + template <typename T> + static void SetValue(std::vector<T> values, + ::prometheus::MetricType type, + ::prometheus::ClientMetric *metric); + + /** + * Handle Gauge from MinMaxSumCount + */ + static void SetValue(double value, ::prometheus::ClientMetric *metric); + + /** + * Handle Histogram + */ + template <typename T, typename U> + static void SetValue(std::vector<T> values, + const std::list<U> &boundaries, + const std::vector<uint64_t> &counts, + ::prometheus::ClientMetric *metric); +}; +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_collector.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_collector.h new file mode 100644 index 000000000..4be748385 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_collector.h @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_METRICS_PREVIEW + +# include <memory> +# include <mutex> +# include <vector> + +# include "opentelemetry/exporters/prometheus/prometheus_exporter_utils.h" +# include "opentelemetry/sdk/_metrics/record.h" +# include "prometheus/collectable.h" +# include "prometheus/metric_family.h" + +namespace prometheus_client = ::prometheus; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace prometheus +{ +/** + * The Prometheus Collector maintains the intermediate collection in Prometheus Exporter + */ +class PrometheusCollector : public prometheus_client::Collectable +{ +public: + /** + * Default Constructor. + * + * This constructor initializes the collection for metrics to export + * in this class with default capacity + */ + explicit PrometheusCollector(size_t max_collection_size = 2048); + + /** + * Collects all metrics data from metricsToCollect collection. + * + * @return all metrics in the metricsToCollect snapshot + */ + std::vector<prometheus_client::MetricFamily> Collect() const override; + + /** + * This function is called by export() function and add the collection of + * records to the metricsToCollect collection + * + * @param records a collection of records to add to the metricsToCollect collection + */ + void AddMetricData(const std::vector<opentelemetry::sdk::metrics::Record> &records); + + /** + * Get the current collection in the collector. + * + * @return the current metricsToCollect collection + */ + std::vector<opentelemetry::sdk::metrics::Record> GetCollection(); + + /** + * Gets the maximum size of the collection. + * + * @return max collection size + */ + int GetMaxCollectionSize() const; + +private: + /** + * Collection of metrics data from the export() function, and to be export + * to user when they send a pull request. This collection is a pointer + * to a collection so Collect() is able to clear the collection, even + * though it is a const function. + */ + std::unique_ptr<std::vector<opentelemetry::sdk::metrics::Record>> metrics_to_collect_; + + /** + * Maximum size of the metricsToCollect collection. + */ + size_t max_collection_size_; + + /* + * Lock when operating the metricsToCollect collection + */ + mutable std::mutex collection_lock_; +}; +} // namespace prometheus +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter.h new file mode 100644 index 000000000..7c1f99a75 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter.h @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_METRICS_PREVIEW +# include <memory> +# include <string> +# include <vector> + +# include "opentelemetry/exporters/prometheus/prometheus_collector.h" +# include "opentelemetry/sdk/_metrics/exporter.h" +# include "opentelemetry/sdk/_metrics/record.h" +# include "opentelemetry/version.h" +# include "prometheus/exposer.h" + +/** + * This class is an implementation of the MetricsExporter interface and + * exports Prometheus metrics data. Functions in this class should be + * called by the Controller in our data pipeline. + */ + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporter +{ +namespace prometheus +{ +class PrometheusExporter : public sdk::metrics::MetricsExporter +{ +public: + /** + * Constructor - binds an exposer and collector to the exporter + * @param address: an address for an exposer that exposes + * an HTTP endpoint for the exporter to connect to + */ + PrometheusExporter(std::string &address); + + /** + * Exports a batch of Metric Records. + * @param records: a collection of records to export + * @return: returns a ReturnCode detailing a success, or type of failure + */ + sdk::common::ExportResult Export( + const std::vector<sdk::metrics::Record> &records) noexcept override; + + /** + * Shuts down the exporter and does cleanup. + * Since Prometheus is a pull based interface, + * we cannot serve data remaining in the intermediate + * collection to to client an HTTP request being sent, + * so we flush the data. + */ + void Shutdown() noexcept; + + /** + * @return: returns a shared_ptr to + * the PrometheusCollector instance + */ + std::shared_ptr<PrometheusCollector> &GetCollector(); + + /** + * @return: Gets the shutdown status of the exporter + */ + bool IsShutdown() const; + +private: + /** + * exporter shutdown status + */ + bool is_shutdown_; + + /** + * Pointer to a + * PrometheusCollector instance + */ + std::shared_ptr<PrometheusCollector> collector_; + + /** + * Pointer to an + * Exposer instance + */ + std::unique_ptr<::prometheus::Exposer> exposer_; + + /** + * friend class for testing + */ + friend class PrometheusExporterTest; + + /** + * PrometheusExporter constructor with no parameters + * Used for testing only + */ + PrometheusExporter(); +}; +} // namespace prometheus +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif // ENABLE_METRICS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h new file mode 100644 index 000000000..7efea9d39 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/include/opentelemetry/exporters/prometheus/prometheus_exporter_utils.h @@ -0,0 +1,171 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#ifdef ENABLE_METRICS_PREVIEW + +# include <string> +# include <vector> + +# include "opentelemetry/sdk/_metrics/record.h" +# include "prometheus/metric_family.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace prometheus +{ +/** + * The Prometheus Utils contains utility functions for Prometheus Exporter + */ +class PrometheusExporterUtils +{ +public: + /** + * Helper function to convert OpenTelemetry metrics data collection + * to Prometheus metrics data collection + * + * @param records a collection of metrics in OpenTelemetry + * @return a collection of translated metrics that is acceptable by Prometheus + */ + static std::vector<::prometheus::MetricFamily> TranslateToPrometheus( + const std::vector<opentelemetry::sdk::metrics::Record> &records); + +private: + /** + * Set value to metric family according to record + */ + static void SetMetricFamily(opentelemetry::sdk::metrics::Record &record, + ::prometheus::MetricFamily *metric_family); + + /** + * Sanitize the given metric name or label according to Prometheus rule. + * + * This function is needed because names in OpenTelemetry can contain + * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the + * name should only contain alphanumeric characters and '_'. + */ + static std::string SanitizeNames(std::string name); + + /** + * Set value to metric family for different aggregator + */ + template <typename T> + static void SetMetricFamilyByAggregator( + std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<T>> aggregator, + std::string labels_str, + ::prometheus::MetricFamily *metric_family); + + /** + * Translate the OTel metric type to Prometheus metric type + */ + static ::prometheus::MetricType TranslateType(opentelemetry::sdk::metrics::AggregatorKind kind); + + /** + * Set metric data for: + * Counter => Prometheus Counter + * Gauge => Prometheus Gauge + */ + template <typename T> + static void SetData(std::vector<T> values, + const std::string &labels, + ::prometheus::MetricType type, + std::chrono::nanoseconds time, + ::prometheus::MetricFamily *metric_family); + + /** + * Set metric data for: + * Histogram => Prometheus Histogram + */ + template <typename T> + static void SetData(std::vector<T> values, + const std::vector<double> &boundaries, + const std::vector<int> &counts, + const std::string &labels, + std::chrono::nanoseconds time, + ::prometheus::MetricFamily *metric_family); + + /** + * Set metric data for: + * MinMaxSumCount => Prometheus Gauge + * Use Average (sum / count) as the gauge metric + */ + static void SetData(double value, + const std::string &labels, + std::chrono::nanoseconds time, + ::prometheus::MetricFamily *metric_family); + + /** + * Set metric data for: + * Exact => Prometheus Summary + * Sketch => Prometheus Summary + */ + template <typename T> + static void SetData(std::vector<T> values, + opentelemetry::sdk::metrics::AggregatorKind kind, + const std::vector<T> &quantiles, + const std::string &labels, + std::chrono::nanoseconds time, + ::prometheus::MetricFamily *metric_family, + bool do_quantile, + std::vector<double> quantile_points); + + /** + * Set time and labels to metric data + */ + static void SetMetricBasic(::prometheus::ClientMetric &metric, + std::chrono::nanoseconds time, + const std::string &labels); + + /** + * Parse a string of labels (key:value) into a vector of pairs + * {,} + * {l1:v1,l2:v2,...,} + */ + static std::vector<std::pair<std::string, std::string>> ParseLabel(std::string labels); + + /** + * Build a quantiles vector from aggregator + */ + template <typename T> + static std::vector<T> GetQuantilesVector( + std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<T>> aggregator, + const std::vector<double> &quantile_points); + + /** + * Handle Counter and Gauge. + */ + template <typename T> + static void SetValue(std::vector<T> values, + ::prometheus::MetricType type, + ::prometheus::ClientMetric *metric); + + /** + * Handle Gauge from MinMaxSumCount + */ + static void SetValue(double value, ::prometheus::ClientMetric *metric); + + /** + * Handle Histogram + */ + template <typename T> + static void SetValue(std::vector<T> values, + std::vector<double> boundaries, + std::vector<int> counts, + ::prometheus::ClientMetric *metric); + + /** + * Handle Exact and Sketch + */ + template <typename T> + static void SetValue(std::vector<T> values, + opentelemetry::sdk::metrics::AggregatorKind kind, + std::vector<T> quantiles, + ::prometheus::ClientMetric *metric, + bool do_quantile, + const std::vector<double> &quantile_points); +}; +} // namespace prometheus +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/collector.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/collector.cc new file mode 100644 index 000000000..03793a8ee --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/collector.cc @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW + +# include "opentelemetry/exporters/prometheus/collector.h" + +namespace metric_sdk = opentelemetry::sdk::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ +/** + * Default Constructor. + * + * This constructor initializes the collection for metrics to export + * in this class with default capacity + */ +PrometheusCollector::PrometheusCollector(size_t max_collection_size) + : max_collection_size_(max_collection_size) +{} + +/** + * Collects all metrics data from metricsToCollect collection. + * + * @return all metrics in the metricsToCollect snapshot + */ +std::vector<prometheus_client::MetricFamily> PrometheusCollector::Collect() const +{ + this->collection_lock_.lock(); + if (metrics_to_collect_.empty()) + { + this->collection_lock_.unlock(); + return {}; + } + + std::vector<prometheus_client::MetricFamily> result; + + // copy the intermediate collection, and then clear it + std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> copied_data; + copied_data.swap(metrics_to_collect_); + this->collection_lock_.unlock(); + + result = PrometheusExporterUtils::TranslateToPrometheus(copied_data); + return result; +} + +/** + * This function is called by export() function and add the collection of + * records to the metricsToCollect collection + * + * @param records a collection of records to add to the metricsToCollect collection + */ +void PrometheusCollector::AddMetricData(const sdk::metrics::ResourceMetrics &data) +{ + collection_lock_.lock(); + if (metrics_to_collect_.size() + 1 <= max_collection_size_) + { + metrics_to_collect_.emplace_back(new sdk::metrics::ResourceMetrics{data}); + } + collection_lock_.unlock(); +} + +/** + * Get the current collection in the collector. + * + * @return the current metrics_to_collect collection + */ +std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &PrometheusCollector::GetCollection() +{ + return metrics_to_collect_; +} + +/** + * Gets the maximum size of the collection. + * + * @return max collection size + */ +int PrometheusCollector::GetMaxCollectionSize() const +{ + return max_collection_size_; +} + +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter.cc new file mode 100644 index 000000000..a0bd9e27a --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter.cc @@ -0,0 +1,100 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include "opentelemetry/exporters/prometheus/exporter.h" + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporter +{ +namespace metrics +{ +/** + * Constructor - binds an exposer and collector to the exporter + * @param address: an address for an exposer that exposes + * an HTTP endpoint for the exporter to connect to + */ +PrometheusExporter::PrometheusExporter(const PrometheusExporterOptions &options) + : options_(options), is_shutdown_(false) +{ + exposer_ = std::unique_ptr<::prometheus::Exposer>(new ::prometheus::Exposer{options_.url}); + collector_ = std::shared_ptr<PrometheusCollector>(new PrometheusCollector); + + exposer_->RegisterCollectable(collector_); +} + +/** + * PrometheusExporter constructor with no parameters + * Used for testing only + */ +PrometheusExporter::PrometheusExporter() : is_shutdown_(false) +{ + collector_ = std::unique_ptr<PrometheusCollector>(new PrometheusCollector); +} + +/** + * Exports a batch of Metric Records. + * @param records: a collection of records to export + * @return: returns a ReturnCode detailing a success, or type of failure + */ +sdk::common::ExportResult PrometheusExporter::Export( + const sdk::metrics::ResourceMetrics &data) noexcept +{ + if (is_shutdown_) + { + return sdk::common::ExportResult::kFailure; + } + else if (collector_->GetCollection().size() + 1 > (size_t)collector_->GetMaxCollectionSize()) + { + return sdk::common::ExportResult::kFailureFull; + } + else + { + collector_->AddMetricData(data); + return sdk::common::ExportResult::kSuccess; + } + return sdk::common::ExportResult::kSuccess; +} + +bool PrometheusExporter::ForceFlush(std::chrono::microseconds timeout) noexcept +{ + return true; +} + +/** + * Shuts down the exporter and does cleanup. + * Since Prometheus is a pull based interface, + * we cannot serve data remaining in the intermediate + * collection to to client an HTTP request being sent, + * so we flush the data. + */ +bool PrometheusExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + is_shutdown_ = true; + return true; + + collector_->GetCollection().clear(); +} + +/** + * @return: returns a shared_ptr to + * the PrometheusCollector instance + */ +std::shared_ptr<PrometheusCollector> &PrometheusExporter::GetCollector() +{ + return collector_; +} + +/** + * @return: Gets the shutdown status of the exporter + */ +bool PrometheusExporter::IsShutdown() const +{ + return is_shutdown_; +} + +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif // ENABLE_METRICS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter_utils.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter_utils.cc new file mode 100644 index 000000000..383925f98 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/exporter_utils.cc @@ -0,0 +1,314 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ENABLE_METRICS_PREVIEW +# include <sstream> +# include <utility> +# include <vector> + +# include <prometheus/metric_type.h> +# include "opentelemetry/exporters/prometheus/exporter_utils.h" +# include "opentelemetry/sdk/metrics/export/metric_producer.h" + +# include "opentelemetry/sdk/common/global_log_handler.h" + +namespace prometheus_client = ::prometheus; +namespace metric_sdk = opentelemetry::sdk::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace metrics +{ +/** + * Helper function to convert OpenTelemetry metrics data collection + * to Prometheus metrics data collection + * + * @param records a collection of metrics in OpenTelemetry + * @return a collection of translated metrics that is acceptable by Prometheus + */ +std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateToPrometheus( + const std::vector<std::unique_ptr<sdk::metrics::ResourceMetrics>> &data) +{ + if (data.empty()) + { + return {}; + } + + // initialize output vector + std::vector<prometheus_client::MetricFamily> output; + + // iterate through the vector and set result data into it + for (const auto &r : data) + { + for (const auto &instrumentation_info : r->instrumentation_info_metric_data_) + { + for (const auto &metric_data : instrumentation_info.metric_data_) + { + auto origin_name = metric_data.instrument_descriptor.name_; + auto sanitized = SanitizeNames(origin_name); + prometheus_client::MetricFamily metric_family; + metric_family.name = sanitized; + metric_family.help = metric_data.instrument_descriptor.description_; + auto time = metric_data.start_ts.time_since_epoch(); + for (const auto &point_data_attr : metric_data.point_data_attr_) + { + auto kind = getAggregationType(point_data_attr.point_data); + const prometheus_client::MetricType type = TranslateType(kind); + metric_family.type = type; + if (type == prometheus_client::MetricType::Histogram) // Histogram + { + auto histogram_point_data = + nostd::get<sdk::metrics::HistogramPointData>(point_data_attr.point_data); + auto boundaries = histogram_point_data.boundaries_; + auto counts = histogram_point_data.counts_; + SetData(std::vector<double>{nostd::get<double>(histogram_point_data.sum_), + (double)histogram_point_data.count_}, + boundaries, counts, point_data_attr.attributes, time, &metric_family); + } + else // Counter, Untyped + { + auto sum_point_data = + nostd::get<sdk::metrics::SumPointData>(point_data_attr.point_data); + std::vector<metric_sdk::ValueType> values{sum_point_data.value_}; + SetData(values, point_data_attr.attributes, type, time, &metric_family); + } + } + output.emplace_back(metric_family); + } + } + } + return output; +} + +/** + * Sanitize the given metric name or label according to Prometheus rule. + * + * This function is needed because names in OpenTelemetry can contain + * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the + * name should only contain alphanumeric characters and '_'. + */ +std::string PrometheusExporterUtils::SanitizeNames(std::string name) +{ + // replace all '.' and '-' with '_' + std::replace(name.begin(), name.end(), '.', '_'); + std::replace(name.begin(), name.end(), '-', '_'); + + return name; +} + +metric_sdk::AggregationType PrometheusExporterUtils::getAggregationType( + const metric_sdk::PointType &point_type) +{ + + if (nostd::holds_alternative<sdk::metrics::SumPointData>(point_type)) + { + return metric_sdk::AggregationType::kSum; + } + else if (nostd::holds_alternative<sdk::metrics::DropPointData>(point_type)) + { + return metric_sdk::AggregationType::kDrop; + } + else if (nostd::holds_alternative<sdk::metrics::HistogramPointData>(point_type)) + { + return metric_sdk::AggregationType::kHistogram; + } + else if (nostd::holds_alternative<sdk::metrics::LastValuePointData>(point_type)) + { + return metric_sdk::AggregationType::kLastValue; + } + return metric_sdk::AggregationType::kDefault; +} + +/** + * Translate the OTel metric type to Prometheus metric type + */ +prometheus_client::MetricType PrometheusExporterUtils::TranslateType( + metric_sdk::AggregationType kind) +{ + switch (kind) + { + case metric_sdk::AggregationType::kSum: + return prometheus_client::MetricType::Counter; + case metric_sdk::AggregationType::kHistogram: + return prometheus_client::MetricType::Histogram; + default: + return prometheus_client::MetricType::Untyped; + } +} + +/** + * Set metric data for: + * sum => Prometheus Counter + */ +template <typename T> +void PrometheusExporterUtils::SetData(std::vector<T> values, + const metric_sdk::PointAttributes &labels, + prometheus_client::MetricType type, + std::chrono::nanoseconds time, + prometheus_client::MetricFamily *metric_family) +{ + metric_family->metric.emplace_back(); + prometheus_client::ClientMetric &metric = metric_family->metric.back(); + SetMetricBasic(metric, time, labels); + SetValue(values, type, &metric); +} + +/** + * Set metric data for: + * Histogram => Prometheus Histogram + */ +template <typename T> +void PrometheusExporterUtils::SetData(std::vector<T> values, + const opentelemetry::sdk::metrics::ListType &boundaries, + const std::vector<uint64_t> &counts, + const metric_sdk::PointAttributes &labels, + std::chrono::nanoseconds time, + prometheus_client::MetricFamily *metric_family) +{ + metric_family->metric.emplace_back(); + prometheus_client::ClientMetric &metric = metric_family->metric.back(); + SetMetricBasic(metric, time, labels); + if (nostd::holds_alternative<std::list<long>>(boundaries)) + { + SetValue(values, nostd::get<std::list<long>>(boundaries), counts, &metric); + } + else + { + SetValue(values, nostd::get<std::list<double>>(boundaries), counts, &metric); + } +} + +/** + * Set time and labels to metric data + */ +void PrometheusExporterUtils::SetMetricBasic(prometheus_client::ClientMetric &metric, + std::chrono::nanoseconds time, + const metric_sdk::PointAttributes &labels) +{ + metric.timestamp_ms = time.count() / 1000000; + + // auto label_pairs = ParseLabel(labels); + if (!labels.empty()) + { + metric.label.resize(labels.size()); + size_t i = 0; + for (auto const &label : labels) + { + auto sanitized = SanitizeNames(label.first); + metric.label[i].name = sanitized; + metric.label[i++].value = AttributeValueToString(label.second); + } + } +}; + +std::string PrometheusExporterUtils::AttributeValueToString( + const opentelemetry::sdk::common::OwnedAttributeValue &value) +{ + std::string result; + if (nostd::holds_alternative<bool>(value)) + { + result = nostd::get<bool>(value) ? "true" : "false"; + } + else if (nostd::holds_alternative<int>(value)) + { + result = std::to_string(nostd::get<int>(value)); + } + else if (nostd::holds_alternative<int64_t>(value)) + { + result = std::to_string(nostd::get<int64_t>(value)); + } + else if (nostd::holds_alternative<unsigned int>(value)) + { + result = std::to_string(nostd::get<unsigned int>(value)); + } + else if (nostd::holds_alternative<uint64_t>(value)) + { + result = std::to_string(nostd::get<uint64_t>(value)); + } + else if (nostd::holds_alternative<double>(value)) + { + result = std::to_string(nostd::get<double>(value)); + } + else if (nostd::holds_alternative<std::string>(value)) + { + result = nostd::get<std::string>(value); + } + else + { + OTEL_INTERNAL_LOG_WARN( + "[Prometheus Exporter] AttributeValueToString - " + " Nested attributes not supported - ignored"); + } + return result; +} + +/** + * Handle Counter. + */ +template <typename T> +void PrometheusExporterUtils::SetValue(std::vector<T> values, + prometheus_client::MetricType type, + prometheus_client::ClientMetric *metric) +{ + double value = 0.0; + const auto &value_var = values[0]; + if (nostd::holds_alternative<long>(value_var)) + { + value = nostd::get<long>(value_var); + } + else + { + value = nostd::get<double>(value_var); + } + + switch (type) + { + case prometheus_client::MetricType::Counter: { + metric->counter.value = value; + break; + } + case prometheus_client::MetricType::Untyped: { + metric->untyped.value = value; + break; + } + default: + return; + } +} + +/** + * Handle Histogram + */ +template <typename T, typename U> +void PrometheusExporterUtils::SetValue(std::vector<T> values, + const std::list<U> &boundaries, + const std::vector<uint64_t> &counts, + prometheus_client::ClientMetric *metric) +{ + metric->histogram.sample_sum = values[0]; + metric->histogram.sample_count = values[1]; + int cumulative = 0; + std::vector<prometheus_client::ClientMetric::Bucket> buckets; + uint32_t idx = 0; + for (const auto &boundary : boundaries) + { + prometheus_client::ClientMetric::Bucket bucket; + cumulative += counts[idx]; + bucket.cumulative_count = cumulative; + bucket.upper_bound = boundary; + buckets.emplace_back(bucket); + ++idx; + } + prometheus_client::ClientMetric::Bucket bucket; + cumulative += counts[idx]; + bucket.cumulative_count = cumulative; + bucket.upper_bound = std::numeric_limits<double>::infinity(); + buckets.emplace_back(bucket); + metric->histogram.bucket = buckets; +} + +} // namespace metrics +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_collector.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_collector.cc new file mode 100644 index 000000000..53b7913de --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_collector.cc @@ -0,0 +1,166 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <iostream> + +# include "opentelemetry/exporters/prometheus/prometheus_collector.h" + +namespace metric_sdk = opentelemetry::sdk::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace prometheus +{ +/** + * Default Constructor. + * + * This constructor initializes the collection for metrics to export + * in this class with default capacity + */ +PrometheusCollector::PrometheusCollector(size_t max_collection_size) + : max_collection_size_(max_collection_size) +{ + metrics_to_collect_ = + std::unique_ptr<std::vector<metric_sdk::Record>>(new std::vector<metric_sdk::Record>); +} + +/** + * Collects all metrics data from metricsToCollect collection. + * + * @return all metrics in the metricsToCollect snapshot + */ +std::vector<prometheus_client::MetricFamily> PrometheusCollector::Collect() const +{ + this->collection_lock_.lock(); + if (metrics_to_collect_->empty()) + { + this->collection_lock_.unlock(); + return {}; + } + this->collection_lock_.unlock(); + + std::vector<prometheus_client::MetricFamily> result; + + // copy the intermediate collection, and then clear it + std::vector<metric_sdk::Record> copied_data; + + this->collection_lock_.lock(); + copied_data = std::vector<metric_sdk::Record>(*metrics_to_collect_); + metrics_to_collect_->clear(); + this->collection_lock_.unlock(); + + result = PrometheusExporterUtils::TranslateToPrometheus(copied_data); + return result; +} + +/** + * This function is called by export() function and add the collection of + * records to the metricsToCollect collection + * + * @param records a collection of records to add to the metricsToCollect collection + */ +void PrometheusCollector::AddMetricData(const std::vector<sdk::metrics::Record> &records) +{ + if (records.empty()) + { + return; + } + + collection_lock_.lock(); + if (metrics_to_collect_->size() + records.size() <= max_collection_size_) + { + /** + * ValidAggregator is a lambda that checks a Record to see if its + * Aggregator is a valid nostd::shared_ptr and not a nullptr. + */ + auto ValidAggregator = [](sdk::metrics::Record record) { + auto aggregator_variant = record.GetAggregator(); + if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<int>>>( + aggregator_variant)) + { + auto aggregator = + nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(aggregator_variant); + if (!aggregator) + { + return false; + } + } + else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<short>>>( + aggregator_variant)) + { + auto aggregator = + nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(aggregator_variant); + if (!aggregator) + { + return false; + } + } + else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<float>>>( + aggregator_variant)) + { + auto aggregator = + nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(aggregator_variant); + if (!aggregator) + { + return false; + } + } + else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<double>>>( + aggregator_variant)) + { + auto aggregator = + nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(aggregator_variant); + if (!aggregator) + { + return false; + } + } + + return true; + }; + + for (auto &r : records) + { + if (ValidAggregator(r)) + { + metrics_to_collect_->emplace_back(r); + } + // Drop the record and write to std::cout + else + { + // Cannot call non const functions on const Record r + sdk::metrics::Record c = r; + std::cout << "Dropped Record containing invalid aggregator with name: " + c.GetName() + << std::endl; + } + } + } + collection_lock_.unlock(); +} + +/** + * Get the current collection in the collector. + * + * @return the current metrics_to_collect collection + */ +std::vector<sdk::metrics::Record> PrometheusCollector::GetCollection() +{ + return *metrics_to_collect_; +} + +/** + * Gets the maximum size of the collection. + * + * @return max collection size + */ +int PrometheusCollector::GetMaxCollectionSize() const +{ + return max_collection_size_; +} + +} // namespace prometheus +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter.cc new file mode 100644 index 000000000..b64af1e90 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter.cc @@ -0,0 +1,110 @@ +/* + * Copyright The 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. + */ + +#ifdef ENABLE_METRICS_PREVIEW +# include "opentelemetry/exporters/prometheus/prometheus_exporter.h" + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporter +{ +namespace prometheus +{ +/** + * Constructor - binds an exposer and collector to the exporter + * @param address: an address for an exposer that exposes + * an HTTP endpoint for the exporter to connect to + */ +PrometheusExporter::PrometheusExporter(std::string &address) : is_shutdown_(false) +{ + exposer_ = std::unique_ptr<::prometheus::Exposer>(new ::prometheus::Exposer{address}); + collector_ = std::shared_ptr<PrometheusCollector>(new PrometheusCollector); + + exposer_->RegisterCollectable(collector_); +} + +/** + * PrometheusExporter constructor with no parameters + * Used for testing only + */ +PrometheusExporter::PrometheusExporter() : is_shutdown_(false) +{ + collector_ = std::unique_ptr<PrometheusCollector>(new PrometheusCollector); +} + +/** + * Exports a batch of Metric Records. + * @param records: a collection of records to export + * @return: returns a ReturnCode detailing a success, or type of failure + */ +sdk::common::ExportResult PrometheusExporter::Export( + const std::vector<sdk::metrics::Record> &records) noexcept +{ + if (is_shutdown_) + { + return sdk::common::ExportResult::kFailure; + } + else if (records.empty()) + { + return sdk::common::ExportResult::kFailureInvalidArgument; + } + else if (collector_->GetCollection().size() + records.size() > + (size_t)collector_->GetMaxCollectionSize()) + { + return sdk::common::ExportResult::kFailureFull; + } + else + { + collector_->AddMetricData(records); + return sdk::common::ExportResult::kSuccess; + } +} + +/** + * Shuts down the exporter and does cleanup. + * Since Prometheus is a pull based interface, + * we cannot serve data remaining in the intermediate + * collection to to client an HTTP request being sent, + * so we flush the data. + */ +void PrometheusExporter::Shutdown() noexcept +{ + is_shutdown_ = true; + + collector_->GetCollection().clear(); +} + +/** + * @return: returns a shared_ptr to + * the PrometheusCollector instance + */ +std::shared_ptr<PrometheusCollector> &PrometheusExporter::GetCollector() +{ + return collector_; +} + +/** + * @return: Gets the shutdown status of the exporter + */ +bool PrometheusExporter::IsShutdown() const +{ + return is_shutdown_; +} + +} // namespace prometheus +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif // ENABLE_METRICS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter_utils.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter_utils.cc new file mode 100644 index 000000000..5eec9e3e8 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/src/prometheus_exporter_utils.cc @@ -0,0 +1,446 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <iostream> +# include <sstream> +# include <utility> +# include <vector> + +# include "opentelemetry/exporters/prometheus/prometheus_exporter_utils.h" +# include "opentelemetry/sdk/_metrics/aggregator/aggregator.h" +# include "prometheus/metric_type.h" + +namespace prometheus_client = ::prometheus; +namespace metric_sdk = opentelemetry::sdk::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace prometheus +{ +/** + * Helper function to convert OpenTelemetry metrics data collection + * to Prometheus metrics data collection + * + * @param records a collection of metrics in OpenTelemetry + * @return a collection of translated metrics that is acceptable by Prometheus + */ +std::vector<prometheus_client::MetricFamily> PrometheusExporterUtils::TranslateToPrometheus( + const std::vector<metric_sdk::Record> &records) +{ + if (records.empty()) + { + return {}; + } + + // initialize output vector + std::vector<prometheus_client::MetricFamily> output(records.size()); + + // iterate through the vector and set result data into it + int i = 0; + for (auto r : records) + { + SetMetricFamily(r, &output[i]); + i++; + } + + return output; +} + +// ======================= private helper functions ========================= +/** + * Set value to metric family according to record + */ +void PrometheusExporterUtils::SetMetricFamily(metric_sdk::Record &record, + prometheus_client::MetricFamily *metric_family) +{ + + auto origin_name = record.GetName(); + auto sanitized = SanitizeNames(origin_name); + metric_family->name = sanitized; + metric_family->help = record.GetDescription(); + + // unpack the variant and set the metric data to metric family struct + auto agg_var = record.GetAggregator(); + auto labels_str = record.GetLabels(); + if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<int>>>(agg_var)) + { + auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(agg_var); + SetMetricFamilyByAggregator(aggregator, labels_str, metric_family); + } + else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<short>>>(agg_var)) + { + auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(agg_var); + SetMetricFamilyByAggregator(aggregator, labels_str, metric_family); + } + else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<float>>>( + record.GetAggregator())) + { + auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(agg_var); + SetMetricFamilyByAggregator(aggregator, labels_str, metric_family); + } + else if (nostd::holds_alternative<std::shared_ptr<metric_sdk::Aggregator<double>>>( + record.GetAggregator())) + { + auto aggregator = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(agg_var); + SetMetricFamilyByAggregator(aggregator, labels_str, metric_family); + } +} + +/** + * Sanitize the given metric name or label according to Prometheus rule. + * + * This function is needed because names in OpenTelemetry can contain + * alphanumeric characters, '_', '.', and '-', whereas in Prometheus the + * name should only contain alphanumeric characters and '_'. + */ +std::string PrometheusExporterUtils::SanitizeNames(std::string name) +{ + // replace all '.' and '-' with '_' + std::replace(name.begin(), name.end(), '.', '_'); + std::replace(name.begin(), name.end(), '-', '_'); + + return name; +} + +/** + * Set value to metric family for different aggregator + */ +template <typename T> +void PrometheusExporterUtils::SetMetricFamilyByAggregator( + std::shared_ptr<metric_sdk::Aggregator<T>> aggregator, + std::string labels_str, + prometheus_client::MetricFamily *metric_family) +{ + // get aggregator kind and translate to Prometheus metric type + auto kind = aggregator->get_aggregator_kind(); + const prometheus_client::MetricType type = TranslateType(kind); + metric_family->type = type; + // get check-pointed values, label string and check-pointed time + auto checkpointed_values = aggregator->get_checkpoint(); + auto time = aggregator->get_checkpoint_timestamp().time_since_epoch(); + + if (type == prometheus_client::MetricType::Histogram) // Histogram + { + auto boundaries = aggregator->get_boundaries(); + auto counts = aggregator->get_counts(); + SetData(checkpointed_values, boundaries, counts, labels_str, time, metric_family); + } + else if (type == prometheus_client::MetricType::Summary) // Sketch, Exact + { + std::vector<double> quantile_points = {0, 0.5, 0.9, 0.95, 0.99, 1}; + if (kind == metric_sdk::AggregatorKind::Exact) + { + std::vector<T> quantiles; + bool do_quantile = aggregator->get_quant_estimation(); + if (do_quantile) + { + quantiles = GetQuantilesVector(aggregator, quantile_points); + } + SetData(checkpointed_values, kind, quantiles, labels_str, time, metric_family, do_quantile, + quantile_points); + } + else if (kind == metric_sdk::AggregatorKind::Sketch) + { + auto quantiles = GetQuantilesVector(aggregator, quantile_points); + SetData(checkpointed_values, kind, quantiles, labels_str, time, metric_family, true, + quantile_points); + } + } + else // Counter, Gauge, MinMaxSumCount, Untyped + { + // Handle MinMaxSumCount: https://github.com/open-telemetry/opentelemetry-cpp/issues/228 + // Use sum/count is ok. + if (kind == metric_sdk::AggregatorKind::MinMaxSumCount) + { + double avg = (double)checkpointed_values[2] / checkpointed_values[3]; + SetData(avg, labels_str, time, metric_family); + } + else + { + SetData(checkpointed_values, labels_str, type, time, metric_family); + } + } +} + +/** + * Translate the OTel metric type to Prometheus metric type + */ +prometheus_client::MetricType PrometheusExporterUtils::TranslateType( + metric_sdk::AggregatorKind kind) +{ + switch (kind) + { + case metric_sdk::AggregatorKind::Counter: + return prometheus_client::MetricType::Counter; + case metric_sdk::AggregatorKind::Gauge: + case metric_sdk::AggregatorKind::MinMaxSumCount: + return prometheus_client::MetricType::Gauge; + case metric_sdk::AggregatorKind::Histogram: + return prometheus_client::MetricType::Histogram; + case metric_sdk::AggregatorKind::Sketch: + case metric_sdk::AggregatorKind::Exact: + return prometheus_client::MetricType::Summary; + default: + return prometheus_client::MetricType::Untyped; + } +} + +/** + * Set metric data for: + * Counter => Prometheus Counter + * Gauge => Prometheus Gauge + */ +template <typename T> +void PrometheusExporterUtils::SetData(std::vector<T> values, + const std::string &labels, + prometheus_client::MetricType type, + std::chrono::nanoseconds time, + prometheus_client::MetricFamily *metric_family) +{ + metric_family->metric.emplace_back(); + prometheus_client::ClientMetric &metric = metric_family->metric.back(); + SetMetricBasic(metric, time, labels); + SetValue(values, type, &metric); +} + +/** + * Set metric data for: + * Histogram => Prometheus Histogram + */ +template <typename T> +void PrometheusExporterUtils::SetData(std::vector<T> values, + const std::vector<double> &boundaries, + const std::vector<int> &counts, + const std::string &labels, + std::chrono::nanoseconds time, + prometheus_client::MetricFamily *metric_family) +{ + metric_family->metric.emplace_back(); + prometheus_client::ClientMetric &metric = metric_family->metric.back(); + SetMetricBasic(metric, time, labels); + SetValue(values, boundaries, counts, &metric); +} + +/** + * Set metric data for: + * MinMaxSumCount => Prometheus Gauge + * Use Average (sum / count) as the gauge metric + */ +void PrometheusExporterUtils::SetData(double value, + const std::string &labels, + std::chrono::nanoseconds time, + prometheus_client::MetricFamily *metric_family) +{ + metric_family->metric.emplace_back(); + prometheus_client::ClientMetric &metric = metric_family->metric.back(); + SetMetricBasic(metric, time, labels); + SetValue(value, &metric); +} + +/** + * Set metric data for: + * Exact => Prometheus Summary + * Sketch => Prometheus Summary + */ +template <typename T> +void PrometheusExporterUtils::SetData(std::vector<T> values, + metric_sdk::AggregatorKind kind, + const std::vector<T> &quantiles, + const std::string &labels, + std::chrono::nanoseconds time, + prometheus_client::MetricFamily *metric_family, + bool do_quantile, + std::vector<double> quantile_points) +{ + metric_family->metric.emplace_back(); + prometheus_client::ClientMetric &metric = metric_family->metric.back(); + SetMetricBasic(metric, time, labels); + SetValue(values, kind, quantiles, &metric, do_quantile, quantile_points); +} + +/** + * Set time and labels to metric data + */ +void PrometheusExporterUtils::SetMetricBasic(prometheus_client::ClientMetric &metric, + std::chrono::nanoseconds time, + const std::string &labels) +{ + metric.timestamp_ms = time.count() / 1000000; + + auto label_pairs = ParseLabel(labels); + if (!label_pairs.empty()) + { + metric.label.resize(label_pairs.size()); + for (size_t i = 0; i < label_pairs.size(); ++i) + { + auto origin_name = label_pairs[i].first; + auto sanitized = SanitizeNames(origin_name); + metric.label[i].name = sanitized; + metric.label[i].value = label_pairs[i].second; + } + } +}; + +/** + * Parse a string of labels (key:value) into a vector of pairs + * {,} + * {l1:v1,l2:v2,...,} + */ +std::vector<std::pair<std::string, std::string>> PrometheusExporterUtils::ParseLabel( + std::string labels) +{ + labels = labels.substr(1, labels.size() - 2); + + std::vector<std::string> paired_labels; + std::stringstream s_stream(labels); + while (s_stream.good()) + { + std::string substr; + getline(s_stream, substr, ','); // get first string delimited by comma + if (!substr.empty()) + { + paired_labels.push_back(substr); + } + } + + std::vector<std::pair<std::string, std::string>> result; + for (auto &paired : paired_labels) + { + std::size_t split_index = paired.find(':'); + std::string label = paired.substr(0, split_index); + std::string value = paired.substr(split_index + 1); + result.emplace_back(std::pair<std::string, std::string>(label, value)); + } + + return result; +} + +/** + * Build a quantiles vector from aggregator + */ +template <typename T> +std::vector<T> PrometheusExporterUtils::GetQuantilesVector( + std::shared_ptr<metric_sdk::Aggregator<T>> aggregator, + const std::vector<double> &quantile_points) +{ + std::vector<T> quantiles; + for (double q : quantile_points) + { + T quantile = aggregator->get_quantiles(q); + quantiles.emplace_back(quantile); + } + return quantiles; +} + +/** + * Handle Counter and Gauge. + */ +template <typename T> +void PrometheusExporterUtils::SetValue(std::vector<T> values, + prometheus_client::MetricType type, + prometheus_client::ClientMetric *metric) +{ + switch (type) + { + case prometheus_client::MetricType::Counter: { + metric->counter.value = values[0]; + break; + } + case prometheus_client::MetricType::Gauge: { + metric->gauge.value = values[0]; + break; + } + case prometheus_client::MetricType::Untyped: { + metric->untyped.value = values[0]; + break; + } + default: + return; + } +} + +/** + * Handle Gauge from MinMaxSumCount + */ +void PrometheusExporterUtils::SetValue(double value, prometheus_client::ClientMetric *metric) +{ + metric->gauge.value = value; +} + +/** + * Handle Histogram + */ +template <typename T> +void PrometheusExporterUtils::SetValue(std::vector<T> values, + std::vector<double> boundaries, + std::vector<int> counts, + prometheus_client::ClientMetric *metric) +{ + metric->histogram.sample_sum = values[0]; + metric->histogram.sample_count = values[1]; + int cumulative = 0; + std::vector<prometheus_client::ClientMetric::Bucket> buckets; + for (size_t i = 0; i < boundaries.size() + 1; i++) + { + prometheus_client::ClientMetric::Bucket bucket; + cumulative += counts[i]; + bucket.cumulative_count = cumulative; + if (i != boundaries.size()) + { + bucket.upper_bound = boundaries[i]; + } + else + { + bucket.upper_bound = std::numeric_limits<double>::infinity(); + } + buckets.emplace_back(bucket); + } + metric->histogram.bucket = buckets; +} + +/** + * Handle Exact and Sketch + */ +template <typename T> +void PrometheusExporterUtils::SetValue(std::vector<T> values, + metric_sdk::AggregatorKind kind, + std::vector<T> quantiles, + prometheus_client::ClientMetric *metric, + bool do_quantile, + const std::vector<double> &quantile_points) +{ + if (kind == metric_sdk::AggregatorKind::Exact) + { + metric->summary.sample_count = values.size(); + auto sum = 0; + for (auto val : values) + { + sum += val; + } + metric->summary.sample_sum = sum; + } + else if (kind == metric_sdk::AggregatorKind::Sketch) + { + metric->summary.sample_sum = values[0]; + metric->summary.sample_count = values[1]; + } + + if (do_quantile) + { + std::vector<prometheus_client::ClientMetric::Quantile> prometheus_quantiles; + for (size_t i = 0; i < quantiles.size(); i++) + { + prometheus_client::ClientMetric::Quantile quantile; + quantile.quantile = quantile_points[i]; + quantile.value = quantiles[i]; + prometheus_quantiles.emplace_back(quantile); + } + metric->summary.quantile = prometheus_quantiles; + } +} +} // namespace prometheus +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/CMakeLists.txt new file mode 100644 index 000000000..1a2246979 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/CMakeLists.txt @@ -0,0 +1,13 @@ +if(WITH_METRICS_PREVIEW) + foreach(testname prometheus_exporter_test prometheus_collector_test + prometheus_exporter_utils_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + prometheus_exporter_deprecated prometheus-cpp::pull) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX exporter. + TEST_LIST ${testname}) + endforeach() +endif() diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_collector_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_collector_test.cc new file mode 100644 index 000000000..d1aff1b0f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_collector_test.cc @@ -0,0 +1,756 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include <future> +# include <map> +# include <thread> + +# include "opentelemetry/_metrics/instrument.h" +# include "opentelemetry/exporters/prometheus/prometheus_collector.h" +# include "opentelemetry/sdk/_metrics/aggregator/aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/exact_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/gauge_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/histogram_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/min_max_sum_count_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/sketch_aggregator.h" +# include "opentelemetry/sdk/_metrics/record.h" +# include "opentelemetry/version.h" + +using opentelemetry::exporter::prometheus::PrometheusCollector; +namespace metric_api = opentelemetry::metrics; +namespace metric_sdk = opentelemetry::sdk::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE + +/** + * CreateAgg() is a helper function that returns a + * nostd::shared_ptr given an AggregatorKind + */ +template <typename T> +std::shared_ptr<metric_sdk::Aggregator<T>> CreateAgg(metric_sdk::AggregatorKind kind, + bool exactMode = true) +{ + std::shared_ptr<metric_sdk::Aggregator<T>> aggregator; + switch (kind) + { + case metric_sdk::AggregatorKind::Counter: { + aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>( + new metric_sdk::CounterAggregator<T>(metric_api::InstrumentKind::Counter)); + break; + } + case metric_sdk::AggregatorKind::MinMaxSumCount: { + aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>( + new metric_sdk::MinMaxSumCountAggregator<T>(metric_api::InstrumentKind::Counter)); + break; + } + case metric_sdk::AggregatorKind::Gauge: { + aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>( + new metric_sdk::GaugeAggregator<T>(metric_api::InstrumentKind::Counter)); + break; + } + case metric_sdk::AggregatorKind::Sketch: { + aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>( + new metric_sdk::SketchAggregator<T>(metric_api::InstrumentKind::Counter, 0.000005)); + break; + } + case metric_sdk::AggregatorKind::Histogram: { + std::vector<double> boundaries{10, 20}; + aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>( + new metric_sdk::HistogramAggregator<T>(metric_api::InstrumentKind::Counter, boundaries)); + break; + } + case metric_sdk::AggregatorKind::Exact: { + aggregator = std::shared_ptr<metric_sdk::Aggregator<T>>( + new metric_sdk::ExactAggregator<T>(metric_api::InstrumentKind::Counter, exactMode)); + break; + } + default: + aggregator = nullptr; + } + return aggregator; +} + +/** + * Populate() updates the aggregator with values and checkpoints it based + * on what its AggregatorKind is + */ +template <typename T> +void Populate(std::shared_ptr<metric_sdk::Aggregator<T>> &aggregator) +{ + if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Counter) + { + aggregator->update(10.0); + aggregator->update(5.0); + aggregator->checkpoint(); + } + else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::MinMaxSumCount) + { + aggregator->update(10); + aggregator->update(2); + aggregator->update(5); + aggregator->checkpoint(); + } + else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Gauge) + { + aggregator->update(10); + aggregator->update(5); + aggregator->checkpoint(); + } + else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Sketch) + { + for (double i = 0; i < 10.0; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + } + else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Histogram) + { + for (float i = 0; i < 30.0; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + } + else if (aggregator->get_aggregator_kind() == metric_sdk::AggregatorKind::Exact) + { + for (double i = 0; i < 10.0; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + } +} + +/** + * Helper function to create a collection of records taken from + * a aggregator of specified AggregatorKind + */ +template <typename T> +std::vector<metric_sdk::Record> CreateRecords(int num, + metric_sdk::AggregatorKind kind, + bool exactMode = true) +{ + std::vector<metric_sdk::Record> records; + + for (int i = 0; i < num; i++) + { + std::string name = "record-" + std::to_string(i); + std::string description = "record " + std::to_string(i) + " for test purpose"; + std::string labels = "{label1:v1,label2:v2,}"; + std::shared_ptr<metric_sdk::Aggregator<T>> aggregator = CreateAgg<T>(kind, exactMode); + Populate(aggregator); + + metric_sdk::Record r{name, description, labels, aggregator}; + records.push_back(r); + } + return records; +} + +// ==================== Test for addMetricsData() function ====================== + +/** + * AddMetricData() should be able to successfully add a collection + * of Records with Counter Aggregators. It checks that the cumulative + * sum of updates to the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithCounterRecordsSuccessfully) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = 2; + + // construct a collection of records with CounterAggregators and double + std::vector<metric_sdk::Record> records = + CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Counter); + + // add records to collection + collector.AddMetricData(records); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), records.size()); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + for (int i = 0; i < num_records; i++) + { + metric_sdk::Record before = records[i]; + metric_sdk::Record after = collector.GetCollection()[i]; + + ASSERT_EQ(before.GetName(), after.GetName()); + + ASSERT_EQ(before.GetDescription(), after.GetDescription()); + + ASSERT_EQ(before.GetLabels(), after.GetLabels()); + + auto before_agg_var = before.GetAggregator(); + auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(before_agg_var); + + auto after_agg_var = after.GetAggregator(); + auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(after_agg_var); + + ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size()); + for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++) + { + ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of Records with MinMaxSumCount Aggregators. It checks that the min, max, + * sum, and count of updates to the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithMinMaxSumCountRecordsSuccessfully) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = 2; + + // construct a collection of records with MinMaxSumCountAggregators and short + std::vector<metric_sdk::Record> records = + CreateRecords<short>(num_records, metric_sdk::AggregatorKind::MinMaxSumCount); + + // add records to collection + collector.AddMetricData(records); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), records.size()); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + for (int i = 0; i < num_records; i++) + { + metric_sdk::Record before = records[i]; + metric_sdk::Record after = collector.GetCollection()[i]; + + ASSERT_EQ(before.GetName(), after.GetName()); + + ASSERT_EQ(before.GetDescription(), after.GetDescription()); + + ASSERT_EQ(before.GetLabels(), after.GetLabels()); + + auto before_agg_var = before.GetAggregator(); + auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(before_agg_var); + + auto after_agg_var = after.GetAggregator(); + auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<short>>>(after_agg_var); + + ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size()); + for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++) + { + ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of Records with Gauge Aggregators. It checks that the last update + * to the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithGaugeRecordsSuccessfully) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = 2; + + // construct a collection of records with GaugeAggregators and int + std::vector<metric_sdk::Record> records = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge); + + // add records to collection + collector.AddMetricData(records); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), records.size()); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + for (int i = 0; i < num_records; i++) + { + metric_sdk::Record before = records[i]; + metric_sdk::Record after = collector.GetCollection()[i]; + + ASSERT_EQ(before.GetName(), after.GetName()); + + ASSERT_EQ(before.GetDescription(), after.GetDescription()); + + ASSERT_EQ(before.GetLabels(), after.GetLabels()); + + auto before_agg_var = before.GetAggregator(); + auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(before_agg_var); + + auto after_agg_var = after.GetAggregator(); + auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<int>>>(after_agg_var); + + ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size()); + for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++) + { + ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of Records with Sketch Aggregators. It checks that the sum of updates + * and count of values added for a record before and after being added are + * equal using get_checkpoint(). It also checks the same for buckets, in + * get_boundaries(), and counts for buckets, in get_counts(). + */ +TEST(PrometheusCollector, AddMetricDataWithSketchRecordsSuccessfully) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = 2; + + // construct a collection of records with SketchAggregators and double + std::vector<metric_sdk::Record> records = + CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Sketch); + + // add records to collection + collector.AddMetricData(records); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), records.size()); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + for (int i = 0; i < num_records; i++) + { + metric_sdk::Record before = records[i]; + metric_sdk::Record after = collector.GetCollection()[i]; + + ASSERT_EQ(before.GetName(), after.GetName()); + + ASSERT_EQ(before.GetDescription(), after.GetDescription()); + + ASSERT_EQ(before.GetLabels(), after.GetLabels()); + + auto before_agg_var = before.GetAggregator(); + auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(before_agg_var); + + auto after_agg_var = after.GetAggregator(); + auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(after_agg_var); + + ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size()); + for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++) + { + ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]); + } + for (size_t i = 0; i < before_agg->get_boundaries().size(); i++) + { + ASSERT_EQ(before_agg->get_boundaries()[i], after_agg->get_boundaries()[i]); + } + for (size_t i = 0; i < before_agg->get_counts().size(); i++) + { + ASSERT_EQ(before_agg->get_counts()[i], after_agg->get_counts()[i]); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of Records with Histogram Aggregators. It checks that the sum of + * updates, number of updates, boundaries, and counts for each bucket + * for the aggregator of a record before and after AddMetricData() + * is called are equal. + */ +TEST(PrometheusCollector, AddMetricDataWithHistogramRecordsSuccessfully) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = 2; + + // construct a collection of records with HistogramAggregators and float + std::vector<metric_sdk::Record> records = + CreateRecords<float>(num_records, metric_sdk::AggregatorKind::Histogram); + + // add records to collection + collector.AddMetricData(records); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), records.size()); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + for (int i = 0; i < num_records; i++) + { + metric_sdk::Record before = records[i]; + metric_sdk::Record after = collector.GetCollection()[i]; + + ASSERT_EQ(before.GetName(), after.GetName()); + + ASSERT_EQ(before.GetDescription(), after.GetDescription()); + + ASSERT_EQ(before.GetLabels(), after.GetLabels()); + + auto before_agg_var = before.GetAggregator(); + auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(before_agg_var); + + auto after_agg_var = after.GetAggregator(); + auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<float>>>(after_agg_var); + + ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size()); + for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++) + { + ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]); + } + for (size_t i = 0; i < before_agg->get_boundaries().size(); i++) + { + ASSERT_EQ(before_agg->get_boundaries()[i], after_agg->get_boundaries()[i]); + } + for (size_t i = 0; i < before_agg->get_counts().size(); i++) + { + ASSERT_EQ(before_agg->get_counts()[i], after_agg->get_counts()[i]); + } + } +} + +/** + * AddMetricData() should be able to successfully add a collection + * of Records with Exact Aggregators. If the Exact Aggregator is in + * quantile mode, it will check quantiles at selected values of 0, 0.25, + * 0.5, 0.75, and 1. If not, it will check the vector of checkpointed + * values in get_checkpoint(). + */ +TEST(PrometheusCollector, AddMetricDataWithExactRecordsSuccessfully) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = 1; + + // construct a collection of a single record with a quantile + // estimation ExactAggregator and double + std::vector<metric_sdk::Record> records = + CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Exact, true); + + // add records to collection + collector.AddMetricData(records); + + // construct a collection of a single record with an in-order + // ExactAggregator and double + records = CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Exact, false); + + // add records to collection + collector.AddMetricData(records); + + // Collection size should be the same as the size + // of the records collection passed to addMetricData() + ASSERT_EQ(collector.GetCollection().size(), records.size() * 2); + + // check values of records created vs records from metricsToCollect, + // accessed by getCollection() + + for (int i = 0; i < num_records; i++) + { + metric_sdk::Record before = records[i]; + metric_sdk::Record after = collector.GetCollection()[i]; + + ASSERT_EQ(before.GetName(), after.GetName()); + + ASSERT_EQ(before.GetDescription(), after.GetDescription()); + + ASSERT_EQ(before.GetLabels(), after.GetLabels()); + + auto before_agg_var = before.GetAggregator(); + auto before_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(before_agg_var); + + auto after_agg_var = after.GetAggregator(); + auto after_agg = nostd::get<std::shared_ptr<metric_sdk::Aggregator<double>>>(after_agg_var); + + if (before_agg->get_quant_estimation() && after_agg->get_quant_estimation()) + { + for (double i = 0; i <= 1;) + { + ASSERT_EQ(before_agg->get_quantiles(i), after_agg->get_quantiles(i)); + i += 0.25; + } + } + else + { + ASSERT_EQ(before_agg->get_checkpoint().size(), after_agg->get_checkpoint().size()); + for (size_t i = 0; i < before_agg->get_checkpoint().size(); i++) + { + ASSERT_EQ(before_agg->get_checkpoint()[i], after_agg->get_checkpoint()[i]); + } + } + } +} + +TEST(PrometheusCollector, AddMetricDataDoesNotAddWithInsufficentSpace) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = collector.GetMaxCollectionSize() - 5; + + // construct a collection close to max capacity + std::vector<metric_sdk::Record> records = + CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Counter); + + collector.AddMetricData(records); + + // Check if all the records have been added + ASSERT_EQ(collector.GetCollection().size(), num_records); + + // Try adding the same collection of records again to + // metricsToCollect. + collector.AddMetricData(records); + + // Check that the number of records in metricsToCollect + // has not changed. + ASSERT_EQ(collector.GetCollection().size(), num_records); +} + +TEST(PrometheusCollector, AddMetricDataDoesNotAddBadIndividualRecords) +{ + PrometheusCollector collector; + + // number of records to create + int num_records = 5; + + // construct a collection with the specified number of records + std::vector<metric_sdk::Record> records = + CreateRecords<double>(num_records, metric_sdk::AggregatorKind::Counter); + + // add records to collection + collector.AddMetricData(records); + + // Check if all the records have been added + ASSERT_EQ(collector.GetCollection().size(), num_records); + + // Creates a bad record, with a nullptr aggregator and adds + // it to the colelction of records + std::string name = "bad_record"; + std::string description = "nullptr_agg"; + std::string labels = "{label1:v1}"; + std::shared_ptr<metric_sdk::Aggregator<int>> aggregator; + metric_sdk::Record bad_record{name, description, labels, aggregator}; + + records.push_back(bad_record); + + // add records to collection + collector.AddMetricData(records); + + // Check if all the records except the bad + // record have been added; the number of records added + // should be twice the original number of records + // epecified to be created + ASSERT_EQ(collector.GetCollection().size(), num_records * 2); +} + +// ==================== Test for Constructor ====================== +TEST(PrometheusCollector, ConstructorInitializesCollector) +{ + PrometheusCollector collector; + + // current size should be 0, capacity should be set to default + ASSERT_EQ(collector.GetCollection().size(), 0); +} + +// ==================== Tests for collect() function ====================== + +/** + * When collector is initialized, the collection inside is should also be initialized + */ +TEST(PrometheusCollector, CollectInitializesMetricFamilyCollection) +{ + PrometheusCollector collector; + auto c1 = collector.Collect(); + ASSERT_EQ(c1.size(), 0); +} + +/** + * Collect function should collect all data and clear the intermediate collection + */ +TEST(PrometheusCollector, CollectClearsTheCollection) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + int num_records = 2; + auto records = CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter); + collector.AddMetricData(records); + + // the collection should not be empty now + ASSERT_EQ(collector.GetCollection().size(), num_records); + + // don't care the collected result in this test + collector.Collect(); + + // after the collect() call, the collection should be empty + ASSERT_EQ(collector.GetCollection().size(), 0); +} + +/** + * Collected data should be already be parsed to Prometheus Metric format + */ +TEST(PrometheusCollector, CollectParsesDataToMetricFamily) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + int num_records = 1; + auto records = CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter); + collector.AddMetricData(records); + + // the collection should not be empty now + ASSERT_EQ(collector.GetCollection().size(), num_records); + auto collected = collector.Collect(); + + ASSERT_EQ(collected.size(), num_records); + + auto metric_family = collected[0]; + + // Collect function really collects a vector of MetricFamily + ASSERT_EQ(metric_family.name, "record_0"); + ASSERT_EQ(metric_family.help, "record 0 for test purpose"); + ASSERT_EQ(metric_family.type, prometheus_client::MetricType::Counter); + ASSERT_EQ(metric_family.metric.size(), 1); + ASSERT_DOUBLE_EQ(metric_family.metric[0].counter.value, 15); +} + +/** + * Concurrency Test 1: After adding data concurrently, the intermediate collection should + * contain all data from all threads. + */ +TEST(PrometheusCollector, ConcurrencyAddingRecords) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + int num_records = 2; + std::vector<metric_sdk::Record> records1 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter); + + std::vector<metric_sdk::Record> records2 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge); + + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1)); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2)); + + first.join(); + second.join(); + + ASSERT_EQ(collector.GetCollection().size(), 4); +} + +/** + * Concurrency Test 2: After adding data concurrently and collecting, the intermediate collection + * should be empty, and all data are collected in the result vector. + */ +TEST(PrometheusCollector, ConcurrentlyAddingAndThenCollecting) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + int num_records = 2; + std::vector<metric_sdk::Record> records1 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter); + + std::vector<metric_sdk::Record> records2 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge); + + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1)); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2)); + first.join(); + second.join(); + + auto collect_future = std::async(&PrometheusCollector::Collect, std::ref(collector)); + auto res = collect_future.get(); + + ASSERT_EQ(collector.GetCollection().size(), 0); + ASSERT_EQ(res.size(), 4); +} + +/** + * Concurrency Test 3: Concurrently adding and collecting. We don't know when the collect function + * is called, but all data entries are either collected or left in the collection. + */ +TEST(PrometheusCollector, ConcurrentlyAddingAndCollecting) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + int num_records = 2; + std::vector<metric_sdk::Record> records1 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter); + + std::vector<metric_sdk::Record> records2 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge); + + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1)); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2)); + auto collect_future = std::async(&PrometheusCollector::Collect, std::ref(collector)); + + first.join(); + second.join(); + + auto res = collect_future.get(); + + // the size of collection can be 0, 2, 4, because we don't know when the collect() + // is really called. However, we claim that if the data in the collection is collected, + // they must be in the res. So res.size() + collection.size() must be the total number + // of data records we generated. + ASSERT_EQ(res.size() + collector.GetCollection().size(), 4); +} + +/** + * Concurrency Test 4: Concurrently adding then concurrently collecting. We don't know which + * collecting thread fetches all data, but either one should succeed. + */ +TEST(PrometheusCollector, ConcurrentlyAddingAndConcurrentlyCollecting) +{ + PrometheusCollector collector; + + // construct a collection to add metric records + int num_records = 2; + std::vector<metric_sdk::Record> records1 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Counter); + + std::vector<metric_sdk::Record> records2 = + CreateRecords<int>(num_records, metric_sdk::AggregatorKind::Gauge); + + // concurrently adding + std::thread first(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records1)); + std::thread second(&PrometheusCollector::AddMetricData, std::ref(collector), std::ref(records2)); + first.join(); + second.join(); + + // after adding, then concurrently consuming + auto collect_future1 = std::async(&PrometheusCollector::Collect, std::ref(collector)); + auto collect_future2 = std::async(&PrometheusCollector::Collect, std::ref(collector)); + auto res1 = collect_future1.get(); + auto res2 = collect_future2.get(); + + // all added data must be collected in either res1 or res2 + ASSERT_EQ(res1.size() + res2.size(), 4); +} + +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_test.cc new file mode 100644 index 000000000..564880c26 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_test.cc @@ -0,0 +1,218 @@ +/* + * Copyright The 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. + */ + +#ifdef ENABLE_METRICS_PREVIEW + +# include <gtest/gtest.h> +# include <typeinfo> + +# include "opentelemetry/exporters/prometheus/prometheus_collector.h" +# include "opentelemetry/exporters/prometheus/prometheus_exporter.h" +# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h" +# include "opentelemetry/version.h" + +/** + * PrometheusExporterTest is a friend class of PrometheusExporter. + * It has access to a private constructor that does not take in + * an exposer as an argument, and instead takes no arguments; this + * private constructor is only to be used here for testing + */ +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace prometheus +{ +class PrometheusExporterTest // : public ::testing::Test +{ +public: + PrometheusExporter GetExporter() { return PrometheusExporter(); } +}; +} // namespace prometheus +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +using opentelemetry::exporter::prometheus::PrometheusCollector; +using opentelemetry::exporter::prometheus::PrometheusExporter; +using opentelemetry::exporter::prometheus::PrometheusExporterTest; +using opentelemetry::sdk::common::ExportResult; +using opentelemetry::sdk::metrics::CounterAggregator; +using opentelemetry::sdk::metrics::Record; + +/** + * Helper function to create a collection of records taken from + * a counter aggregator + */ +std::vector<Record> CreateRecords(int num) +{ + + std::vector<Record> records; + + for (int i = 0; i < num; i++) + { + std::string name = "record-" + std::to_string(i); + std::string description = "record-" + std::to_string(i); + std::string labels = "record-" + std::to_string(i) + "-label-1.0"; + auto aggregator = std::shared_ptr<opentelemetry::sdk::metrics::Aggregator<int>>( + new opentelemetry::sdk::metrics::CounterAggregator<int>( + opentelemetry::metrics::InstrumentKind::Counter)); + aggregator->update(10); + aggregator->checkpoint(); + + Record r{name, description, labels, aggregator}; + records.push_back(r); + } + return records; +} + +/** + * When a PrometheusExporter is initialized, + * isShutdown should be false. + */ +TEST(PrometheusExporter, InitializeConstructorIsNotShutdown) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + // // Asserts that the exporter is not shutdown. + ASSERT_TRUE(!exporter.IsShutdown()); +} + +/** + * The shutdown() function should set the isShutdown field to true. + */ +TEST(PrometheusExporter, ShutdownSetsIsShutdownToTrue) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + // exporter shuold not be shutdown by default + ASSERT_TRUE(!exporter.IsShutdown()); + + exporter.Shutdown(); + + // the exporter shuold be shutdown + ASSERT_TRUE(exporter.IsShutdown()); + + // shutdown function should be idempotent + exporter.Shutdown(); + ASSERT_TRUE(exporter.IsShutdown()); +} + +/** + * The Export() function should return kSuccess = 0 + * when data is exported successfully. + */ +TEST(PrometheusExporter, ExportSuccessfully) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + int num_records = 2; + + std::vector<Record> records = CreateRecords(num_records); + + auto res = exporter.Export(records); + + // result should be kSuccess = 0 + ExportResult code = ExportResult::kSuccess; + ASSERT_EQ(res, code); +} + +/** + * If the exporter is shutdown, it cannot process + * any more export requests and returns kFailure = 1. + */ +TEST(PrometheusExporter, ExporterIsShutdown) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + int num_records = 1; + + std::vector<Record> records = CreateRecords(num_records); + + exporter.Shutdown(); + + // send export request after shutdown + auto res = exporter.Export(records); + + // result code should be kFailure = 1 + ExportResult code = ExportResult::kFailure; + ASSERT_EQ(res, code); +} + +/** + * The Export() function should return + * kFailureFull = 2 when the collection is full, + * or when the collection is not full but does not have enough + * space to hold the batch data. + */ +TEST(PrometheusExporter, CollectionNotEnoughSpace) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + int num_records = 2; + + // prepare two collections of records to export, + // one close to max size and another one that, when added + // to the first, will exceed the size of the collection + + int max_collection_size = exporter.GetCollector()->GetMaxCollectionSize(); + + std::vector<Record> full_records = CreateRecords(max_collection_size - 1); + std::vector<Record> records = CreateRecords(num_records); + + // send export request to fill the + // collection in the collector + auto res = exporter.Export(full_records); + + // the result code should be kSuccess = 0 + ExportResult code = ExportResult::kSuccess; + ASSERT_EQ(res, code); + + // send export request that does not complete + // due to not enough space in the collection + res = exporter.Export(records); + + // the result code should be kFailureFull = 2 + code = ExportResult::kFailureFull; + ASSERT_EQ(res, code); +} + +/** + * The Export() function should return + * kFailureInvalidArgument = 3 when an empty collection + * of records is passed to the Export() function. + */ +TEST(PrometheusExporter, InvalidArgumentWhenPassedEmptyRecordCollection) +{ + PrometheusExporterTest p; + PrometheusExporter exporter = p.GetExporter(); + + // Initializes an empty colelction of records + std::vector<Record> records; + + // send export request to fill the + // collection in the collector + auto res = exporter.Export(records); + + // the result code should be kFailureInvalidArgument = 3 + ExportResult code = ExportResult::kFailureInvalidArgument; + ASSERT_EQ(res, code); +} + +#endif // ENABLE_METRICS_PREVIEW diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_utils_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_utils_test.cc new file mode 100644 index 000000000..22ce3f5fd --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/prometheus/test/prometheus_exporter_utils_test.cc @@ -0,0 +1,460 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_PREVIEW +# include <gtest/gtest.h> +# include <map> +# include <numeric> +# include <string> +# include <typeinfo> + +# include <opentelemetry/version.h> +# include "opentelemetry/exporters/prometheus/prometheus_exporter_utils.h" +# include "opentelemetry/sdk/_metrics/aggregator/counter_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/exact_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/gauge_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/histogram_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/min_max_sum_count_aggregator.h" +# include "opentelemetry/sdk/_metrics/aggregator/sketch_aggregator.h" + +using opentelemetry::exporter::prometheus::PrometheusExporterUtils; +namespace metric_sdk = opentelemetry::sdk::metrics; +namespace metric_api = opentelemetry::metrics; +namespace prometheus_client = ::prometheus; + +OPENTELEMETRY_BEGIN_NAMESPACE +template <typename T> +void assert_basic(prometheus_client::MetricFamily &metric, + const std::string &sanitized_name, + const std::string &description, + prometheus_client::MetricType type, + int label_num, + std::vector<T> vals) +{ + ASSERT_EQ(metric.name, sanitized_name); // name sanitized + ASSERT_EQ(metric.help, description); // description not changed + ASSERT_EQ(metric.type, type); // type translated + + auto metric_data = metric.metric[0]; + ASSERT_EQ(metric_data.label.size(), label_num); + + switch (type) + { + case prometheus_client::MetricType::Counter: { + ASSERT_DOUBLE_EQ(metric_data.counter.value, vals[0]); + break; + } + case prometheus_client::MetricType::Gauge: { + ASSERT_EQ(metric_data.gauge.value, vals[0]); + break; + } + case prometheus_client::MetricType::Histogram: { + ASSERT_DOUBLE_EQ(metric_data.histogram.sample_count, vals[0]); + ASSERT_DOUBLE_EQ(metric_data.histogram.sample_sum, vals[1]); + auto buckets = metric_data.histogram.bucket; + ASSERT_EQ(buckets.size(), vals[2]); + break; + } + case prometheus_client::MetricType::Summary: { + ASSERT_DOUBLE_EQ(metric_data.summary.sample_count, vals[0]); + ASSERT_DOUBLE_EQ(metric_data.summary.sample_sum, vals[1]); + break; + } + case prometheus::MetricType::Untyped: + break; + } +} + +void assert_histogram(prometheus_client::MetricFamily &metric, + std::vector<double> boundaries, + std::vector<int> correct) +{ + int cumulative_count = 0; + auto buckets = metric.metric[0].histogram.bucket; + for (size_t i = 0; i < buckets.size(); i++) + { + auto bucket = buckets[i]; + if (i != buckets.size() - 1) + { + ASSERT_DOUBLE_EQ(boundaries[i], bucket.upper_bound); + } + else + { + ASSERT_DOUBLE_EQ(std::numeric_limits<double>::infinity(), bucket.upper_bound); + } + cumulative_count += correct[i]; + ASSERT_EQ(cumulative_count, bucket.cumulative_count); + } +} + +template <typename T> +metric_sdk::Record get_record(const std::string &type, + int version, + const std::string &label, + std::shared_ptr<metric_sdk::Aggregator<T>> aggregator) +{ + std::string name = "test-" + type + "-metric-record-v_" + std::to_string(version) + ".0"; + std::string desc = "this is a test " + type + " metric record"; + metric_sdk::Record record(name, desc, label, aggregator); + return record; +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusEmptyInputReturnsEmptyCollection) +{ + std::vector<metric_sdk::Record> collection; + auto translated2 = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated2.size(), 0); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusIntegerCounter) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::CounterAggregator<int>(metric_api::InstrumentKind::Counter)); + + std::vector<metric_sdk::Record> collection; + + auto record1 = get_record("int-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + aggregator->update(10); + aggregator->checkpoint(); + collection.emplace_back(record1); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric1 = translated[0]; + std::vector<int> vals = {10}; + assert_basic(metric1, "test_int_counter_metric_record_v_1_0", record1.GetDescription(), + prometheus_client::MetricType::Counter, 3, vals); + + auto record2 = get_record("int-counter", 2, "{,}", aggregator); + aggregator->update(20); + aggregator->update(30); + aggregator->checkpoint(); + collection.emplace_back(record2); + + translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric2 = translated[1]; + vals = {50}; + assert_basic(metric2, "test_int_counter_metric_record_v_2_0", record2.GetDescription(), + prometheus_client::MetricType::Counter, 0, vals); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusDoubleCounter) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metric_api::InstrumentKind::Counter)); + + std::vector<metric_sdk::Record> collection; + aggregator->update(10.5); + aggregator->checkpoint(); + auto record1 = get_record("double-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + aggregator->update(22.4); + aggregator->update(31.2); + aggregator->checkpoint(); + auto record2 = get_record("double-counter", 2, "{,}", aggregator); + collection.emplace_back(record1); + collection.emplace_back(record2); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric1 = translated[0]; + std::vector<double> vals = {53.6}; + assert_basic(metric1, "test_double_counter_metric_record_v_1_0", record1.GetDescription(), + prometheus_client::MetricType::Counter, 3, vals); + auto metric2 = translated[1]; + assert_basic(metric2, "test_double_counter_metric_record_v_2_0", record2.GetDescription(), + prometheus_client::MetricType::Counter, 0, vals); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusShortCounter) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<short>>( + new metric_sdk::CounterAggregator<short>(metric_api::InstrumentKind::Counter)); + + std::vector<metric_sdk::Record> collection; + aggregator->update(10); + aggregator->checkpoint(); + auto record1 = get_record("short-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + aggregator->update(20); + aggregator->update(30); + aggregator->checkpoint(); + auto record2 = get_record("short-counter", 2, "{,}", aggregator); + collection.emplace_back(record1); + collection.emplace_back(record2); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric1 = translated[0]; + std::vector<short> vals = {50}; + assert_basic(metric1, "test_short_counter_metric_record_v_1_0", record1.GetDescription(), + prometheus_client::MetricType::Counter, 3, vals); + auto metric2 = translated[1]; + assert_basic(metric2, "test_short_counter_metric_record_v_2_0", record2.GetDescription(), + prometheus_client::MetricType::Counter, 0, vals); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusFloatCounter) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<float>>( + new metric_sdk::CounterAggregator<float>(metric_api::InstrumentKind::Counter)); + + std::vector<metric_sdk::Record> collection; + aggregator->update(10.5f); + aggregator->checkpoint(); + auto record1 = get_record("float-counter", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + aggregator->update(22.4f); + aggregator->update(31.2f); + aggregator->checkpoint(); + auto record2 = get_record("float-counter", 2, "{,}", aggregator); + collection.emplace_back(record1); + collection.emplace_back(record2); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric1 = translated[0]; + std::vector<float> vals = {53.6f}; + assert_basic(metric1, "test_float_counter_metric_record_v_1_0", record1.GetDescription(), + prometheus_client::MetricType::Counter, 3, vals); + auto metric2 = translated[1]; + assert_basic(metric2, "test_float_counter_metric_record_v_2_0", record2.GetDescription(), + prometheus_client::MetricType::Counter, 0, vals); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusGauge) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::GaugeAggregator<int>(metric_api::InstrumentKind::Counter)); + + std::vector<metric_sdk::Record> collection; + aggregator->update(10); + aggregator->checkpoint(); + auto record1 = get_record("gauge", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + aggregator->update(20); + aggregator->update(30); + aggregator->checkpoint(); + auto record2 = get_record("gauge", 2, "{,}", aggregator); + collection.emplace_back(record1); + collection.emplace_back(record2); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric1 = translated[0]; + std::vector<int> vals = {30}; + assert_basic(metric1, "test_gauge_metric_record_v_1_0", record1.GetDescription(), + prometheus_client::MetricType::Gauge, 3, vals); + auto metric2 = translated[1]; + assert_basic(metric2, "test_gauge_metric_record_v_2_0", record2.GetDescription(), + prometheus_client::MetricType::Gauge, 0, vals); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusHistogramUniform) +{ + std::vector<double> boundaries{10, 20, 30, 40, 50}; + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::HistogramAggregator<int>(metric_api::InstrumentKind::Counter, boundaries)); + + std::vector<metric_sdk::Record> collection; + auto record = get_record("histogram-uniform", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + int count_num = 60; + for (int i = 0; i < count_num; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + collection.emplace_back(record); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric = translated[0]; + std::vector<int> vals = {aggregator->get_checkpoint()[1], aggregator->get_checkpoint()[0], + (int)boundaries.size() + 1}; + assert_basic(metric, "test_histogram_uniform_metric_record_v_1_0", record.GetDescription(), + prometheus_client::MetricType::Histogram, 3, vals); + std::vector<int> correct = aggregator->get_counts(); + assert_histogram(metric, boundaries, correct); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusHistogramNormal) +{ + std::vector<double> boundaries{2, 4, 6, 8, 10, 12}; + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::HistogramAggregator<int>(metric_api::InstrumentKind::Counter, boundaries)); + + std::vector<metric_sdk::Record> collection; + auto record = get_record("histogram-normal", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + std::vector<int> values{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : values) + { + aggregator->update(i); + } + aggregator->checkpoint(); + collection.emplace_back(record); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric = translated[0]; + std::vector<int> vals = {aggregator->get_checkpoint()[1], aggregator->get_checkpoint()[0], + (int)boundaries.size() + 1}; + assert_basic(metric, "test_histogram_normal_metric_record_v_1_0", record.GetDescription(), + prometheus_client::MetricType::Histogram, 3, vals); + std::vector<int> correct = aggregator->get_counts(); + assert_histogram(metric, boundaries, correct); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusExact) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::ExactAggregator<int>(metric_api::InstrumentKind::Counter, true)); + + std::vector<metric_sdk::Record> collection; + int count_num = 100; + for (int i = 0; i <= count_num; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + auto record = get_record("exact", 1, "{label-1:v1,label_2:v2,label3:v3,}", aggregator); + collection.emplace_back(record); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric = translated[0]; + std::vector<int> vals = {101, 5050}; + assert_basic(metric, "test_exact_metric_record_v_1_0", record.GetDescription(), + prometheus_client::MetricType::Summary, 3, vals); + auto quantile = metric.metric[0].summary.quantile; + ASSERT_EQ(quantile.size(), 6); + ASSERT_DOUBLE_EQ(quantile[0].value, 0); + ASSERT_DOUBLE_EQ(quantile[1].value, 50); + ASSERT_DOUBLE_EQ(quantile[2].value, 90); + ASSERT_DOUBLE_EQ(quantile[3].value, 95); + ASSERT_DOUBLE_EQ(quantile[4].value, 99); + ASSERT_DOUBLE_EQ(quantile[5].value, 100); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusExactNoQuantile) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::ExactAggregator<int>(metric_api::InstrumentKind::Counter, false)); + + std::vector<metric_sdk::Record> collection; + int count_num = 10; + for (int i = 0; i < count_num; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + auto record = get_record("exact-no-quantile", 1, "{label1:v1,label2:v2,}", aggregator); + collection.emplace_back(record); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric = translated[0]; + std::vector<int> vals = {count_num, 45}; + assert_basic(metric, "test_exact_no_quantile_metric_record_v_1_0", record.GetDescription(), + prometheus_client::MetricType::Summary, 2, vals); + auto quantile = metric.metric[0].summary.quantile; + ASSERT_EQ(quantile.size(), 0); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusMinMaxSumCount) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::MinMaxSumCountAggregator<int>(metric_api::InstrumentKind::Counter)); + + std::vector<metric_sdk::Record> collection; + // min: 1, max: 10, sum: 55, count: 10 + for (int i = 1; i <= 10; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + auto record = get_record("mmsc", 1, "{label1:v1,label2:v2,label3:v3,}", aggregator); + collection.emplace_back(record); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric = translated[0]; + // in this version of implementation, we use the sum/count as a gauge + std::vector<double> vals = {5.5}; + assert_basic(metric, "test_mmsc_metric_record_v_1_0", record.GetDescription(), + prometheus_client::MetricType::Gauge, 3, vals); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusSketch) +{ + auto aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::SketchAggregator<int>(metric_api::InstrumentKind::Counter, 0.0005)); + + std::vector<metric_sdk::Record> collection; + for (int i = 0; i <= 100; i++) + { + aggregator->update(i); + } + aggregator->checkpoint(); + auto record = get_record("sketch", 1, "{label1:v1,label2:v2,}", aggregator); + collection.emplace_back(record); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric = translated[0]; + std::vector<int> vals = {aggregator->get_checkpoint()[1], aggregator->get_checkpoint()[0]}; + assert_basic(metric, "test_sketch_metric_record_v_1_0", record.GetDescription(), + prometheus_client::MetricType::Summary, 2, vals); + + auto quantile = metric.metric[0].summary.quantile; + ASSERT_EQ(quantile.size(), 6); + ASSERT_DOUBLE_EQ(quantile[0].value, 0); + ASSERT_DOUBLE_EQ(quantile[1].value, 49); + ASSERT_DOUBLE_EQ(quantile[2].value, 89); + ASSERT_DOUBLE_EQ(quantile[3].value, 94); + ASSERT_DOUBLE_EQ(quantile[4].value, 98); + ASSERT_DOUBLE_EQ(quantile[5].value, 99); +} + +TEST(PrometheusExporterUtils, TranslateToPrometheusMultipleAggregators) +{ + auto counter_aggregator = std::shared_ptr<metric_sdk::Aggregator<double>>( + new metric_sdk::CounterAggregator<double>(metric_api::InstrumentKind::Counter)); + auto gauge_aggregator = std::shared_ptr<metric_sdk::Aggregator<int>>( + new metric_sdk::GaugeAggregator<int>(metric_api::InstrumentKind::Counter)); + + std::vector<metric_sdk::Record> collection; + counter_aggregator->update(10); + counter_aggregator->update(20); + counter_aggregator->checkpoint(); + auto record1 = get_record("counter", 1, "{label1:v1,label2:v2,label3:v3,}", counter_aggregator); + gauge_aggregator->update(10); + gauge_aggregator->update(30); + gauge_aggregator->update(20); + gauge_aggregator->checkpoint(); + auto record2 = get_record("gauge", 1, "{label1:v1,}", gauge_aggregator); + collection.emplace_back(record1); + collection.emplace_back(record2); + + auto translated = PrometheusExporterUtils::TranslateToPrometheus(collection); + ASSERT_EQ(translated.size(), collection.size()); + + auto metric1 = translated[0]; + std::vector<int> vals = {30}; + assert_basic(metric1, "test_counter_metric_record_v_1_0", record1.GetDescription(), + prometheus_client::MetricType::Counter, 3, vals); + auto metric2 = translated[1]; + vals = {20}; + assert_basic(metric2, "test_gauge_metric_record_v_1_0", record2.GetDescription(), + prometheus_client::MetricType::Gauge, 1, vals); +} +OPENTELEMETRY_END_NAMESPACE +#endif diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/BUILD b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/BUILD new file mode 100644 index 000000000..6cd52b2d0 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/BUILD @@ -0,0 +1,64 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "zipkin_recordable", + srcs = [ + "src/recordable.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/zipkin/recordable.h", + ], + strip_include_prefix = "include", + tags = ["zipkin"], + deps = [ + "//sdk/src/resource", + "//sdk/src/trace", + "@github_nlohmann_json//:json", + ], +) + +cc_library( + name = "zipkin_exporter", + srcs = [ + "src/zipkin_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/zipkin/zipkin_exporter.h", + ], + copts = [ + "-DCURL_STATICLIB", + ], + strip_include_prefix = "include", + tags = ["zipkin"], + deps = [ + ":zipkin_recordable", + "//ext/src/http/client/curl:http_client_curl", + ], +) + +cc_test( + name = "zipkin_recordable_test", + srcs = ["test/zipkin_recordable_test.cc"], + tags = [ + "test", + "zipkin", + ], + deps = [ + ":zipkin_recordable", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "zipkin_exporter_test", + srcs = ["test/zipkin_exporter_test.cc"], + tags = [ + "test", + "zipkin", + ], + deps = [ + ":zipkin_exporter", + ":zipkin_recordable", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/CMakeLists.txt b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/CMakeLists.txt new file mode 100644 index 000000000..559e8d500 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/CMakeLists.txt @@ -0,0 +1,75 @@ +# Copyright 2021, 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. + +include_directories(include) +find_package(CURL REQUIRED) +add_definitions(-DWITH_CURL) +add_library(opentelemetry_exporter_zipkin_trace src/zipkin_exporter.cc + src/recordable.cc) + +target_link_libraries( + opentelemetry_exporter_zipkin_trace + PUBLIC opentelemetry_trace opentelemetry_http_client_curl + nlohmann_json::nlohmann_json) + +install( + TARGETS opentelemetry_exporter_zipkin_trace + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/exporters/zipkin + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h" + PATTERN "recordable.h" EXCLUDE) + +if(BUILD_TESTING) + add_definitions(-DGTEST_LINKED_AS_SHARED_LIBRARY=1) + + add_executable(zipkin_recordable_test test/zipkin_recordable_test.cc) + + target_link_libraries( + zipkin_recordable_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_zipkin_trace opentelemetry_resources) + + gtest_add_tests( + TARGET zipkin_recordable_test + TEST_PREFIX exporter. + TEST_LIST zipkin_recordable_test) + + if(MSVC) + if(GMOCK_LIB) + 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() + + add_executable(zipkin_exporter_test test/zipkin_exporter_test.cc) + + target_link_libraries( + zipkin_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + ${GMOCK_LIB} opentelemetry_exporter_zipkin_trace opentelemetry_resources) + + gtest_add_tests( + TARGET zipkin_exporter_test + TEST_PREFIX exporter. + TEST_LIST zipkin_exporter_test) +endif() # BUILD_TESTING diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/README.md b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/README.md new file mode 100644 index 000000000..40bb1f061 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/README.md @@ -0,0 +1,56 @@ +# Zipkin Exporter for OpenTelemetry C++ + +## Prerequisite + +* [Get Zipkin](https://zipkin.io/pages/quickstart.html) + +## Installation + +### CMake Install Instructions + +Refer to install instructions [INSTALL.md](../../INSTALL.md#building-as-standalone-cmake-project). +Modify step 2 to create `cmake` build configuration for compiling Zipkin as below: + +```console + $ cmake -DWITH_ZIPKIN=ON .. + -- The C compiler identification is GNU 9.3.0 + -- The CXX compiler identification is GNU 9.3.0 + ... + -- Configuring done + -- Generating done + -- Build files have been written to: /home/<user>/source/opentelemetry-cpp/build + $ +``` + +### Bazel Install Instructions + +TODO + +## Usage + +Install the exporter on your application and pass the options. `service_name` +is an optional string. If omitted, the exporter will first try to get the +service name from the Resource. If no service name can be detected on the +Resource, a fallback name of "unknown_service" will be used. + +```cpp + +opentelemetry::exporter::zipkin::ZipkinExporterOptions options; +options.endpoint = "http://localhost:9411/api/v2/spans"; +options.service_name = "my_service"; + +auto exporter = std::unique_ptr<opentelemetry::sdk::trace::SpanExporter>( + new opentelemetry::exporter::zipkin::ZipkinExporter(options)); +auto processor = std::shared_ptr<sdktrace::SpanProcessor>( + new sdktrace::SimpleSpanProcessor(std::move(exporter))); +auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>( + new sdktrace::TracerProvider(processor, opentelemetry::sdk::resource::Resource::Create({}), + std::make_shared<opentelemetry::sdk::trace::AlwaysOnSampler>())); + +// Set the global trace provider +opentelemetry::trace::Provider::SetTracerProvider(provider); +``` + +## Viewing your traces + +Please visit the Zipkin UI endpoint <http://localhost:9411> diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/recordable.h b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/recordable.h new file mode 100644 index 000000000..51f83211f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/recordable.h @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "nlohmann/json.hpp" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace zipkin +{ +using ZipkinSpan = nlohmann::json; + +class Recordable final : public sdk::trace::Recordable +{ +public: + const ZipkinSpan &span() const noexcept { return span_; } + + const std::string &GetServiceName() const noexcept { return service_name_; } + + 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(opentelemetry::trace::StatusCode code, + nostd::string_view description) noexcept override; + + void SetName(nostd::string_view name) noexcept override; + + void SetStartTime(opentelemetry::common::SystemTimestamp start_time) noexcept override; + + void SetSpanKind(opentelemetry::trace::SpanKind span_kind) noexcept override; + + void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override; + + void SetDuration(std::chrono::nanoseconds duration) noexcept override; + + void SetInstrumentationLibrary( + const opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary + &instrumentation_library) noexcept override; + +private: + ZipkinSpan span_; + std::string service_name_; +}; +} // namespace zipkin +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/zipkin_exporter.h b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/zipkin_exporter.h new file mode 100644 index 000000000..ae0e8173f --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/zipkin_exporter.h @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/ext/http/client/http_client_factory.h" +#include "opentelemetry/ext/http/common/url_parser.h" +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include "nlohmann/json.hpp" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace zipkin +{ + +inline const std::string GetDefaultZipkinEndpoint() +{ + const char *otel_exporter_zipkin_endpoint_env = "OTEL_EXPORTER_ZIPKIN_ENDPOINT"; + const char *kZipkinEndpointDefault = "http://localhost:9411/api/v2/spans"; + + auto endpoint = + opentelemetry::sdk::common::GetEnvironmentVariable(otel_exporter_zipkin_endpoint_env); + return endpoint.size() ? endpoint : kZipkinEndpointDefault; +} + +enum class TransportFormat +{ + kJson, + kProtobuf +}; + +/** + * Struct to hold Zipkin exporter options. + */ +struct ZipkinExporterOptions +{ + // The endpoint to export to. By default the OpenTelemetry Collector's default endpoint. + std::string endpoint = GetDefaultZipkinEndpoint(); + TransportFormat format = TransportFormat::kJson; + std::string service_name = "default-service"; + std::string ipv4; + std::string ipv6; + ext::http::client::Headers headers = {{"content-type", "application/json"}}; +}; + +/** + * The Zipkin exporter exports span data in JSON format as expected by Zipkin + */ +class ZipkinExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * Create a ZipkinExporter using all default options. + */ + ZipkinExporter(); + + /** + * Create a ZipkinExporter using the given options. + */ + explicit ZipkinExporter(const ZipkinExporterOptions &options); + + /** + * Create a span recordable. + * @return a newly initialized Recordable object + */ + std::unique_ptr<opentelemetry::sdk::trace::Recordable> MakeRecordable() noexcept override; + + /** + * Export a batch of span recordables in JSON format. + * @param spans a span of unique pointers to span recordables + */ + 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, default to max. + */ + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + +private: + void InitializeLocalEndpoint(); + +private: + // The configuration options associated with this exporter. + bool is_shutdown_ = false; + ZipkinExporterOptions options_; + std::shared_ptr<opentelemetry::ext::http::client::HttpClientSync> http_client_; + opentelemetry::ext::http::common::UrlParser url_parser_; + nlohmann::json local_end_point_; + + // For testing + friend class ZipkinExporterTestPeer; + /** + * Create an ZipkinExporter using the specified thrift sender. + * Only tests can call this constructor directly. + * @param http_client the http client to be used for exporting + */ + ZipkinExporter(std::shared_ptr<opentelemetry::ext::http::client::HttpClientSync> http_client); + + mutable opentelemetry::common::SpinLockMutex lock_; + bool isShutdown() const noexcept; +}; +} // namespace zipkin +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/recordable.cc b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/recordable.cc new file mode 100644 index 000000000..700d6a964 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/recordable.cc @@ -0,0 +1,254 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/exporters/zipkin/recordable.h" +#include "opentelemetry/sdk/resource/experimental_semantic_conventions.h" + +#include <map> +#include <string> + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace zipkin +{ + +using namespace opentelemetry::sdk::resource; +namespace trace_api = opentelemetry::trace; +namespace common = opentelemetry::common; +namespace sdk = opentelemetry::sdk; + +// constexpr needs keys to be constexpr, const is next best to use. +static const std::map<trace_api::SpanKind, std::string> kSpanKindMap = { + {trace_api::SpanKind::kClient, "CLIENT"}, + {trace_api::SpanKind::kServer, "SERVER"}, + {trace_api::SpanKind::kConsumer, "CONSUMER"}, + {trace_api::SpanKind::kProducer, "PRODUCER"}, +}; + +// +// See `attribute_value.h` for details. +// +const int kAttributeValueSize = 16; + +void Recordable::SetIdentity(const trace_api::SpanContext &span_context, + trace_api::SpanId parent_span_id) noexcept +{ + char trace_id_lower_base16[trace::TraceId::kSize * 2] = {0}; + span_context.trace_id().ToLowerBase16(trace_id_lower_base16); + char span_id_lower_base16[trace::SpanId::kSize * 2] = {0}; + span_context.span_id().ToLowerBase16(span_id_lower_base16); + if (parent_span_id.IsValid()) + { + char parent_span_id_lower_base16[trace::SpanId::kSize * 2] = {0}; + parent_span_id.ToLowerBase16(parent_span_id_lower_base16); + span_["parentId"] = std::string(parent_span_id_lower_base16, 16); + } + + span_["id"] = std::string(span_id_lower_base16, 16); + span_["traceId"] = std::string(trace_id_lower_base16, 32); +} + +void PopulateAttribute(nlohmann::json &attribute, + nostd::string_view key, + const common::AttributeValue &value) +{ + // Assert size of variant to ensure that this method gets updated if the variant + // definition changes + static_assert(nostd::variant_size<common::AttributeValue>::value == kAttributeValueSize, + "AttributeValue contains unknown type"); + + if (nostd::holds_alternative<bool>(value)) + { + attribute[key.data()] = nostd::get<bool>(value); + } + else if (nostd::holds_alternative<int>(value)) + { + attribute[key.data()] = nostd::get<int>(value); + } + else if (nostd::holds_alternative<int64_t>(value)) + { + attribute[key.data()] = nostd::get<int64_t>(value); + } + else if (nostd::holds_alternative<unsigned int>(value)) + { + attribute[key.data()] = nostd::get<unsigned int>(value); + } + else if (nostd::holds_alternative<uint64_t>(value)) + { + attribute[key.data()] = nostd::get<uint64_t>(value); + } + else if (nostd::holds_alternative<double>(value)) + { + attribute[key.data()] = nostd::get<double>(value); + } + else if (nostd::holds_alternative<const char *>(value)) + { + attribute[key.data()] = nostd::get<const char *>(value); + } + else if (nostd::holds_alternative<nostd::string_view>(value)) + { + attribute[key.data()] = nostd::string_view(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)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const uint8_t>>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative<nostd::span<const bool>>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const bool>>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative<nostd::span<const int>>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const int>>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative<nostd::span<const int64_t>>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const int64_t>>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative<nostd::span<const unsigned int>>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const unsigned int>>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative<nostd::span<const uint64_t>>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const uint64_t>>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative<nostd::span<const double>>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const double>>(value)) + { + attribute[key.data()].push_back(val); + } + } + else if (nostd::holds_alternative<nostd::span<const nostd::string_view>>(value)) + { + attribute[key.data()] = {}; + for (const auto &val : nostd::get<nostd::span<const nostd::string_view>>(value)) + { + attribute[key.data()].push_back(std::string(val.data(), val.size())); + } + } +} + +void Recordable::SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept +{ + if (!span_.contains("tags")) + { + span_["tags"] = nlohmann::json::object(); + } + PopulateAttribute(span_["tags"], key, value); +} + +void Recordable::AddEvent(nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept +{ + nlohmann::json attrs = nlohmann::json::object(); // empty object + attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + PopulateAttribute(attrs, key, value); + return true; + }); + + nlohmann::json annotation = {{"value", nlohmann::json::object({{name.data(), attrs}}).dump()}, + {"timestamp", std::chrono::duration_cast<std::chrono::microseconds>( + timestamp.time_since_epoch()) + .count()}}; + + if (!span_.contains("annotations")) + { + span_["annotations"] = nlohmann::json::array(); + } + span_["annotations"].push_back(annotation); +} + +void Recordable::AddLink(const trace_api::SpanContext &span_context, + const common::KeyValueIterable &attributes) noexcept +{ + // TODO: Currently not supported by specs: + // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk_exporters/zipkin.md +} + +void Recordable::SetStatus(trace::StatusCode code, nostd::string_view description) noexcept +{ + if (code != trace::StatusCode::kUnset) + { + span_["tags"]["otel.status_code"] = code; + if (code == trace::StatusCode::kError) + { + span_["tags"]["error"] = description; + } + } +} + +void Recordable::SetName(nostd::string_view name) noexcept +{ + span_["name"] = name.data(); +} + +void Recordable::SetResource(const sdk::resource::Resource &resource) noexcept +{ + // only service.name attribute is supported by specs as of now. + auto attributes = resource.GetAttributes(); + if (attributes.find(OTEL_GET_RESOURCE_ATTR(AttrServiceName)) != attributes.end()) + { + service_name_ = nostd::get<std::string>(attributes[OTEL_GET_RESOURCE_ATTR(AttrServiceName)]); + } +} + +void Recordable::SetStartTime(common::SystemTimestamp start_time) noexcept +{ + span_["timestamp"] = + std::chrono::duration_cast<std::chrono::microseconds>(start_time.time_since_epoch()).count(); +} + +void Recordable::SetDuration(std::chrono::nanoseconds duration) noexcept +{ + span_["duration"] = std::chrono::duration_cast<std::chrono::microseconds>(duration).count(); +} + +void Recordable::SetSpanKind(trace_api::SpanKind span_kind) noexcept +{ + auto span_iter = kSpanKindMap.find(span_kind); + if (span_iter != kSpanKindMap.end()) + { + span_["kind"] = span_iter->second; + } +} + +void Recordable::SetInstrumentationLibrary( + const sdk::instrumentationlibrary::InstrumentationLibrary &instrumentation_library) noexcept +{ + span_["tags"]["otel.library.name"] = instrumentation_library.GetName(); + span_["tags"]["otel.library.version"] = instrumentation_library.GetVersion(); +} + +} // namespace zipkin +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/zipkin_exporter.cc b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/zipkin_exporter.cc new file mode 100644 index 000000000..240144599 --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/zipkin_exporter.cc @@ -0,0 +1,128 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#define _WINSOCKAPI_ // stops including winsock.h +#include "opentelemetry/exporters/zipkin/zipkin_exporter.h" +#include <mutex> +#include "opentelemetry/exporters/zipkin/recordable.h" +#include "opentelemetry/ext/http/client/http_client_factory.h" +#include "opentelemetry/ext/http/common/url_parser.h" +#include "opentelemetry/sdk_config.h" + +namespace http_client = opentelemetry::ext::http::client; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace zipkin +{ + +// -------------------------------- Constructors -------------------------------- + +ZipkinExporter::ZipkinExporter(const ZipkinExporterOptions &options) + : options_(options), url_parser_(options_.endpoint) +{ + http_client_ = ext::http::client::HttpClientFactory::CreateSync(); + InitializeLocalEndpoint(); +} + +ZipkinExporter::ZipkinExporter() : options_(ZipkinExporterOptions()), url_parser_(options_.endpoint) +{ + http_client_ = ext::http::client::HttpClientFactory::CreateSync(); + InitializeLocalEndpoint(); +} + +ZipkinExporter::ZipkinExporter( + std::shared_ptr<opentelemetry::ext::http::client::HttpClientSync> http_client) + : options_(ZipkinExporterOptions()), url_parser_(options_.endpoint) +{ + http_client_ = http_client; + InitializeLocalEndpoint(); +} + +// ----------------------------- Exporter methods ------------------------------ + +std::unique_ptr<sdk::trace::Recordable> ZipkinExporter::MakeRecordable() noexcept +{ + return std::unique_ptr<sdk::trace::Recordable>(new Recordable); +} + +sdk::common::ExportResult ZipkinExporter::Export( + const nostd::span<std::unique_ptr<sdk::trace::Recordable>> &spans) noexcept +{ + if (isShutdown()) + { + OTEL_INTERNAL_LOG_ERROR("[Zipkin Trace Exporter] Exporting " + << spans.size() << " span(s) failed, exporter is shutdown"); + return sdk::common::ExportResult::kFailure; + } + exporter::zipkin::ZipkinSpan json_spans = {}; + for (auto &recordable : spans) + { + auto rec = std::unique_ptr<Recordable>(static_cast<Recordable *>(recordable.release())); + if (rec != nullptr) + { + auto json_span = rec->span(); + // add localEndPoint + json_span["localEndpoint"] = local_end_point_; + // check service.name + auto service_name = rec->GetServiceName(); + if (service_name.size()) + { + json_span["localEndpoint"]["serviceName"] = service_name; + } + json_spans.push_back(json_span); + } + } + auto body_s = json_spans.dump(); + http_client::Body body_v(body_s.begin(), body_s.end()); + auto result = http_client_->Post(url_parser_.url_, body_v, options_.headers); + if (result && + (result.GetResponse().GetStatusCode() == 200 || result.GetResponse().GetStatusCode() == 202)) + { + return sdk::common::ExportResult::kSuccess; + } + else + { + if (result.GetSessionState() == http_client::SessionState::ConnectFailed) + { + OTEL_INTERNAL_LOG_ERROR("ZIPKIN EXPORTER] Zipkin Exporter: Connection failed"); + } + return sdk::common::ExportResult::kFailure; + } + return sdk::common::ExportResult::kSuccess; +} + +void ZipkinExporter::InitializeLocalEndpoint() +{ + if (options_.service_name.length()) + { + local_end_point_["serviceName"] = options_.service_name; + } + if (options_.ipv4.length()) + { + local_end_point_["ipv4"] = options_.ipv4; + } + if (options_.ipv6.length()) + { + local_end_point_["ipv6"] = options_.ipv6; + } + local_end_point_["port"] = url_parser_.port_; +} + +bool ZipkinExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + is_shutdown_ = true; + return true; +} + +bool ZipkinExporter::isShutdown() const noexcept +{ + const std::lock_guard<opentelemetry::common::SpinLockMutex> locked(lock_); + return is_shutdown_; +} + +} // namespace zipkin +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_exporter_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_exporter_test.cc new file mode 100644 index 000000000..eec71f43d --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_exporter_test.cc @@ -0,0 +1,215 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifndef HAVE_CPP_STDLIB + +# include "opentelemetry/exporters/zipkin/zipkin_exporter.h" +# include <string> +# include "opentelemetry/ext/http/client/curl/http_client_curl.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 +namespace sdk_common = opentelemetry::sdk::common; +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace zipkin +{ + +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); +} + +class ZipkinExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr<sdk::trace::SpanExporter> GetExporter( + std::shared_ptr<opentelemetry::ext::http::client::HttpClientSync> http_client) + { + return std::unique_ptr<sdk::trace::SpanExporter>(new ZipkinExporter(http_client)); + } + + // Get the options associated with the given exporter. + const ZipkinExporterOptions &GetOptions(std::unique_ptr<ZipkinExporter> &exporter) + { + return exporter->options_; + } +}; + +class MockHttpClient : public opentelemetry::ext::http::client::HttpClientSync +{ +public: + MOCK_METHOD(ext::http::client::Result, + Post, + (const nostd::string_view &, + const ext::http::client::Body &, + const ext::http::client::Headers &), + (noexcept, override)); + MOCK_METHOD(ext::http::client::Result, + Get, + (const nostd::string_view &, const ext::http::client::Headers &), + (noexcept, override)); +}; + +class IsValidMessageMatcher +{ +public: + IsValidMessageMatcher(const std::string &trace_id) : trace_id_(trace_id) {} + template <typename T> + bool MatchAndExplain(const T &p, MatchResultListener * /* listener */) const + { + auto body = std::string(p.begin(), p.end()); + nlohmann::json check_json = nlohmann::json::parse(body); + auto trace_id_kv = check_json.at(0).find("traceId"); + auto received_trace_id = trace_id_kv.value().get<std::string>(); + return trace_id_ == received_trace_id; + } + + void DescribeTo(std::ostream *os) const { *os << "received trace_id matches"; } + + void DescribeNegationTo(std::ostream *os) const { *os << "received trace_id does not matche"; } + +private: + std::string trace_id_; +}; + +PolymorphicMatcher<IsValidMessageMatcher> IsValidMessage(const std::string &trace_id) +{ + return MakePolymorphicMatcher(IsValidMessageMatcher(trace_id)); +} + +// Create spans, let processor call Export() +TEST_F(ZipkinExporterTestPeer, ExportJsonIntegrationTest) +{ + auto mock_http_client = new MockHttpClient; + // Leave a comment line here or different version of clang-format has a different result here + auto exporter = GetExporter( + std::shared_ptr<opentelemetry::ext::http::client::HttpClientSync>{mock_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 expected_url = nostd::string_view{"http://localhost:9411/api/v2/spans"}; + EXPECT_CALL(*mock_http_client, Post(expected_url, IsValidMessage(report_trace_id), _)) + .Times(Exactly(1)) + .WillOnce(Return(ByMove(std::move(ext::http::client::Result{ + std::unique_ptr<ext::http::client::Response>{new ext::http::client::curl::Response()}, + ext::http::client::SessionState::Response})))); + + child_span->End(); + parent_span->End(); +} + +// Create spans, let processor call Export() +TEST_F(ZipkinExporterTestPeer, ShutdownTest) +{ + auto mock_http_client = new MockHttpClient; + // Leave a comment line here or different version of clang-format has a different result here + auto exporter = GetExporter( + std::shared_ptr<opentelemetry::ext::http::client::HttpClientSync>{mock_http_client}); + 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_http_client, Post(_, _, _)) + .Times(Exactly(1)) + .WillOnce(Return(ByMove(std::move(ext::http::client::Result{ + std::unique_ptr<ext::http::client::Response>{new ext::http::client::curl::Response()}, + ext::http::client::SessionState::Response})))); + 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); +} + +// Test exporter configuration options +TEST_F(ZipkinExporterTestPeer, ConfigTest) +{ + ZipkinExporterOptions opts; + opts.endpoint = "http://localhost:45455/v1/traces"; + std::unique_ptr<ZipkinExporter> exporter(new ZipkinExporter(opts)); + EXPECT_EQ(GetOptions(exporter).endpoint, "http://localhost:45455/v1/traces"); +} + +# ifndef NO_GETENV +// Test exporter configuration options from env +TEST_F(ZipkinExporterTestPeer, ConfigFromEnv) +{ + const std::string endpoint = "http://localhost:9999/v1/traces"; + setenv("OTEL_EXPORTER_ZIPKIN_ENDPOINT", endpoint.c_str(), 1); + + std::unique_ptr<ZipkinExporter> exporter(new ZipkinExporter()); + EXPECT_EQ(GetOptions(exporter).endpoint, endpoint); + + unsetenv("OTEL_EXPORTER_ZIPKIN_ENDPOINT"); +} + +# endif // NO_GETENV + +} // namespace zipkin +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE +#endif // HAVE_CPP_STDLIB diff --git a/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_recordable_test.cc b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_recordable_test.cc new file mode 100644 index 000000000..967aebaab --- /dev/null +++ b/src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_recordable_test.cc @@ -0,0 +1,285 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +#include "opentelemetry/sdk/trace/exporter.h" + +#include "opentelemetry/common/timestamp.h" +#include "opentelemetry/exporters/zipkin/recordable.h" + +#include <gtest/gtest.h> + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; +namespace common = opentelemetry::common; +namespace zipkin = opentelemetry::exporter::zipkin; +using json = nlohmann::json; + +// Testing Shutdown functionality of OStreamSpanExporter, should expect no data to be sent to Stream +TEST(ZipkinSpanRecordable, SetIdentity) +{ + json j_span = {{"id", "0000000000000002"}, + {"parentId", "0000000000000003"}, + {"traceId", "00000000000000000000000000000001"}}; + zipkin::Recordable rec; + const trace::TraceId trace_id(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 span_id( + std::array<const uint8_t, trace::SpanId::kSize>({0, 0, 0, 0, 0, 0, 0, 2})); + + const trace::SpanId parent_span_id( + std::array<const uint8_t, trace::SpanId::kSize>({0, 0, 0, 0, 0, 0, 0, 3})); + + const trace::SpanContext span_context{trace_id, span_id, + trace::TraceFlags{trace::TraceFlags::kIsSampled}, true}; + + rec.SetIdentity(span_context, parent_span_id); + EXPECT_EQ(rec.span(), j_span); +} + +// according to https://zipkin.io/zipkin-api/#/ in case root span is created +// the parentId filed should be absent. +TEST(ZipkinSpanRecordable, SetIdentityEmptyParent) +{ + json j_span = {{"id", "0000000000000002"}, {"traceId", "00000000000000000000000000000001"}}; + zipkin::Recordable rec; + const trace::TraceId trace_id(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 span_id( + std::array<const uint8_t, trace::SpanId::kSize>({0, 0, 0, 0, 0, 0, 0, 2})); + + const trace::SpanId parent_span_id( + std::array<const uint8_t, trace::SpanId::kSize>({0, 0, 0, 0, 0, 0, 0, 0})); + + const trace::SpanContext span_context{trace_id, span_id, + trace::TraceFlags{trace::TraceFlags::kIsSampled}, true}; + + rec.SetIdentity(span_context, parent_span_id); + EXPECT_EQ(rec.span(), j_span); +} + +TEST(ZipkinSpanRecordable, SetName) +{ + nostd::string_view name = "Test Span"; + json j_span = {{"name", name}}; + zipkin::Recordable rec; + rec.SetName(name); + EXPECT_EQ(rec.span(), j_span); +} + +TEST(ZipkinSpanRecordable, SetStartTime) +{ + zipkin::Recordable 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::microseconds>(start_time.time_since_epoch()).count(); + json j_span = {{"timestamp", unix_start}}; + rec.SetStartTime(start_timestamp); + EXPECT_EQ(rec.span(), j_span); +} + +TEST(ZipkinSpanRecordable, SetDuration) +{ + std::chrono::nanoseconds durationNS(1000000000); // in ns + std::chrono::microseconds durationMS = + std::chrono::duration_cast<std::chrono::microseconds>(durationNS); // in ms + json j_span = {{"duration", durationMS.count()}, {"timestamp", 0}}; + zipkin::Recordable rec; + // Start time is 0 + common::SystemTimestamp start_timestamp; + + rec.SetStartTime(start_timestamp); + rec.SetDuration(durationNS); + EXPECT_EQ(rec.span(), j_span); +} + +TEST(ZipkinSpanRecordable, SetInstrumentationLibrary) +{ + using InstrumentationLibrary = opentelemetry::sdk::instrumentationlibrary::InstrumentationLibrary; + + const char *library_name = "otel-cpp"; + const char *library_version = "0.5.0"; + json j_span = { + {"tags", {{"otel.library.name", library_name}, {"otel.library.version", library_version}}}}; + zipkin::Recordable rec; + + rec.SetInstrumentationLibrary(*InstrumentationLibrary::Create(library_name, library_version)); + + EXPECT_EQ(rec.span(), j_span); +} + +TEST(ZipkinSpanRecordable, SetStatus) +{ + std::string description = "Error description"; + std::vector<trace::StatusCode> status_codes = {trace::StatusCode::kError, trace::StatusCode::kOk}; + for (auto &status_code : status_codes) + { + zipkin::Recordable rec; + trace::StatusCode code(status_code); + json j_span; + if (status_code == trace::StatusCode::kError) + j_span = {{"tags", {{"otel.status_code", status_code}, {"error", description}}}}; + else + j_span = {{"tags", {{"otel.status_code", status_code}}}}; + + rec.SetStatus(code, description); + EXPECT_EQ(rec.span(), j_span); + } +} + +TEST(ZipkinSpanRecordable, SetSpanKind) +{ + json j_json_client = {{"kind", "CLIENT"}}; + zipkin::Recordable rec; + rec.SetSpanKind(trace::SpanKind::kClient); + EXPECT_EQ(rec.span(), j_json_client); +} + +TEST(ZipkinSpanRecordable, AddEventDefault) +{ + zipkin::Recordable 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.sdktrace::Recordable::AddEvent(name, event_timestamp); + + uint64_t unix_event_time = + std::chrono::duration_cast<std::chrono::microseconds>(event_time.time_since_epoch()).count(); + + json j_span = { + {"annotations", + {{{"value", json({{name, json::object()}}).dump()}, {"timestamp", unix_event_time}}}}}; + EXPECT_EQ(rec.span(), j_span); +} + +TEST(ZipkinSpanRecordable, AddEventWithAttributes) +{ + zipkin::Recordable rec; + + std::chrono::system_clock::time_point event_time = std::chrono::system_clock::now(); + common::SystemTimestamp event_timestamp(event_time); + uint64_t unix_event_time = + std::chrono::duration_cast<std::chrono::microseconds>(event_time.time_since_epoch()).count(); + + 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", event_timestamp, + common::KeyValueIterableView<std::map<std::string, int>>(attributes)); + + nlohmann::json j_span = { + {"annotations", + {{{"value", json({{"Test Event", {{"attr1", 4}, {"attr2", 7}, {"attr3", 23}}}}).dump()}, + {"timestamp", unix_event_time}}}}}; + EXPECT_EQ(rec.span(), j_span); +} + +// Test non-int single types. Int single types are tested using templates (see IntAttributeTest) +TEST(ZipkinSpanRecordable, SetSingleAtrribute) +{ + zipkin::Recordable 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); + nlohmann::json j_span = { + {"tags", {{"bool_attr", true}, {"double_attr", 3.3}, {"str_attr", "Test"}}}}; + + EXPECT_EQ(rec.span(), j_span); +} + +// Test non-int array types. Int array types are tested using templates (see IntAttributeTest) +TEST(ZipkinSpanRecordable, SetArrayAtrribute) +{ + zipkin::Recordable rec; + nlohmann::json j_span = {{"tags", + {{"bool_arr_attr", {true, false, true}}, + {"double_arr_attr", {22.3, 33.4, 44.5}}, + {"str_arr_attr", {"Hello", "World", "Test"}}}}}; + 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); + + EXPECT_EQ(rec.span(), j_span); +} + +TEST(ZipkinSpanRecordable, SetResource) +{ + zipkin::Recordable rec; + std::string service_name = "test"; + auto resource = opentelemetry::sdk::resource::Resource::Create({{"service.name", service_name}}); + rec.SetResource(resource); + EXPECT_EQ(rec.GetServiceName(), service_name); +} + +/** + * 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 ZipkinIntAttributeTest : public testing::Test +{ + using IntParamType = T; +}; + +using IntTypes = testing::Types<int, int64_t, unsigned int, uint64_t>; +TYPED_TEST_SUITE(ZipkinIntAttributeTest, IntTypes); + +TYPED_TEST(ZipkinIntAttributeTest, SetIntSingleAttribute) +{ + using IntType = typename TestFixture::IntParamType; + IntType i = 2; + common::AttributeValue int_val(i); + + zipkin::Recordable rec; + rec.SetAttribute("int_attr", int_val); + nlohmann::json j_span = {{"tags", {{"int_attr", 2}}}}; + EXPECT_EQ(rec.span(), j_span); +} + +TYPED_TEST(ZipkinIntAttributeTest, 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); + + zipkin::Recordable rec; + rec.SetAttribute("int_arr_attr", int_span); + nlohmann::json j_span = {{"tags", {{"int_arr_attr", {4, 5, 6}}}}}; + EXPECT_EQ(rec.span(), j_span); +} |