summaryrefslogtreecommitdiffstats
path: root/src/jaegertracing/opentelemetry-cpp/exporters/zipkin
diff options
context:
space:
mode:
Diffstat (limited to 'src/jaegertracing/opentelemetry-cpp/exporters/zipkin')
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/BUILD64
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/CMakeLists.txt75
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/README.md56
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/recordable.h60
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/include/opentelemetry/exporters/zipkin/zipkin_exporter.h113
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/recordable.cc254
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/src/zipkin_exporter.cc128
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_exporter_test.cc215
-rw-r--r--src/jaegertracing/opentelemetry-cpp/exporters/zipkin/test/zipkin_recordable_test.cc285
9 files changed, 1250 insertions, 0 deletions
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);
+}