diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 360d01ffa8..0c0a060044 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,6 +131,20 @@ jobs: sudo ./ci/install_abseil.sh ./ci/do_ci.sh cmake.abseil.test + cmake_opentracing_shim_test: + name: CMake test (with opentracing-shim) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + - name: run cmake tests (enable opentracing-shim) + run: ./ci/do_ci.sh cmake.opentracing_shim.test + cmake_gcc_48_test: name: CMake gcc 4.8 (without otlp exporter) runs-on: ubuntu-18.04 diff --git a/.gitmodules b/.gitmodules index c422e39e27..88a78e5f94 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,8 @@ branch = main path = third_party/nlohmann-json url = https://github.com/nlohmann/json branch = master + +[submodule "third_party/opentracing-cpp"] +path = third_party/opentracing-cpp +url = https://github.com/opentracing/opentracing-cpp.git +branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index 59c609368d..72b71201ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,8 @@ option(BUILD_W3CTRACECONTEXT_TEST "Whether to build w3c trace context" OFF) option(OTELCPP_MAINTAINER_MODE "Build in maintainer mode (-Wall -Werror)" OFF) +option(WITH_OPENTRACING "Whether to include the Opentracing shim" OFF) + set(OTELCPP_PROTO_PATH "" CACHE PATH "Path to opentelemetry-proto") @@ -536,6 +538,29 @@ include_directories(api/include) add_subdirectory(api) +if(WITH_OPENTRACING) + find_package(OpenTracing CONFIG QUIET) + if(NOT OpenTracing_FOUND) + set(OPENTRACING_DIR "third_party/opentracing-cpp") + message("Trying to use local ${OPENTRACING_DIR} from submodule") + if(EXISTS "${PROJECT_SOURCE_DIR}/${OPENTRACING_DIR}/.git") + set(SAVED_BUILD_TESTING ${BUILD_TESTING}) + set(BUILD_TESTING OFF) + add_subdirectory(${OPENTRACING_DIR}) + set(BUILD_TESTING ${SAVED_BUILD_TESTING}) + else() + message( + FATAL_ERROR + "\nopentracing-cpp package was not found. Please either provide it manually or clone with submodules. " + "To initialize, fetch and checkout any nested submodules, you can use the following command:\n" + "git submodule update --init --recursive") + endif() + else() + message("Using external opentracing-cpp") + endif() + add_subdirectory(opentracing-shim) +endif() + if(NOT WITH_API_ONLY) set(BUILD_TESTING ${BUILD_TESTING}) include_directories(sdk/include) @@ -545,6 +570,7 @@ if(NOT WITH_API_ONLY) add_subdirectory(sdk) add_subdirectory(ext) add_subdirectory(exporters) + if(BUILD_TESTING) add_subdirectory(test_common) endif() diff --git a/bazel/repository.bzl b/bazel/repository.bzl index 4579ef4614..e03e48ae5a 100644 --- a/bazel/repository.bzl +++ b/bazel/repository.bzl @@ -182,6 +182,17 @@ def opentelemetry_cpp_deps(): ], ) + # Opentracing + maybe( + http_archive, + name = "com_github_opentracing", + sha256 = "5b170042da4d1c4c231df6594da120875429d5231e9baa5179822ee8d1054ac3", + strip_prefix = "opentracing-cpp-1.6.0", + urls = [ + "https://github.com/opentracing/opentracing-cpp/archive/refs/tags/v1.6.0.tar.gz", + ], + ) + # boost headers from vcpkg maybe( native.new_local_repository, diff --git a/ci/do_ci.ps1 b/ci/do_ci.ps1 index e2143088f0..637e63defc 100644 --- a/ci/do_ci.ps1 +++ b/ci/do_ci.ps1 @@ -25,7 +25,7 @@ $VCPKG_DIR = Join-Path "$SRC_DIR" "tools" "vcpkg" switch ($action) { "bazel.build" { - bazel build --copt=-DENABLE_TEST $BAZEL_OPTIONS --action_env=VCPKG_DIR=$VCPKG_DIR -- //... + bazel build --copt=-DENABLE_TEST $BAZEL_OPTIONS --action_env=VCPKG_DIR=$VCPKG_DIR --deleted_packages=opentracing-shim -- //... $exit = $LASTEXITCODE if ($exit -ne 0) { exit $exit diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 00f77a581b..faa3fdaf88 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -141,6 +141,16 @@ elif [[ "$1" == "cmake.abseil.test" ]]; then make make test exit 0 +elif [[ "$1" == "cmake.opentracing_shim.test" ]]; then + cd "${BUILD_DIR}" + rm -rf * + cmake -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_FLAGS="-Werror -Wno-error=redundant-move $CXXFLAGS" \ + -DWITH_OPENTRACING=ON \ + "${SRC_DIR}" + make + make test + exit 0 elif [[ "$1" == "cmake.c++20.test" ]]; then cd "${BUILD_DIR}" rm -rf * @@ -282,13 +292,14 @@ elif [[ "$1" == "bazel.legacy.test" ]]; then exit 0 elif [[ "$1" == "bazel.noexcept" ]]; then # there are some exceptions and error handling code from the Prometheus and Jaeger Clients - # that make this test always fail. ignore Prometheus and Jaeger exporters in the noexcept here. - bazel $BAZEL_STARTUP_OPTIONS build --copt=-fno-exceptions --build_tag_filters=-jaeger $BAZEL_OPTIONS_ASYNC -- //... -//exporters/prometheus/... -//exporters/jaeger/... -//examples/prometheus/... -//sdk/test/metrics:attributes_hashmap_test - bazel $BAZEL_STARTUP_OPTIONS test --copt=-fno-exceptions --build_tag_filters=-jaeger $BAZEL_TEST_OPTIONS_ASYNC -- //... -//exporters/prometheus/... -//exporters/jaeger/... -//examples/prometheus/... -//sdk/test/metrics:attributes_hashmap_test + # as well as Opentracing shim (due to some third party code in its Opentracing dependency) + # that make this test always fail. Ignore these packages in the noexcept test here. + bazel $BAZEL_STARTUP_OPTIONS build --copt=-fno-exceptions --build_tag_filters=-jaeger $BAZEL_OPTIONS_ASYNC -- //... -//exporters/prometheus/... -//exporters/jaeger/... -//examples/prometheus/... -//sdk/test/metrics:attributes_hashmap_test -//opentracing-shim/... + bazel $BAZEL_STARTUP_OPTIONS test --copt=-fno-exceptions --build_tag_filters=-jaeger $BAZEL_TEST_OPTIONS_ASYNC -- //... -//exporters/prometheus/... -//exporters/jaeger/... -//examples/prometheus/... -//sdk/test/metrics:attributes_hashmap_test -//opentracing-shim/... exit 0 elif [[ "$1" == "bazel.nortti" ]]; then # there are some exceptions and error handling code from the Prometheus and Jaeger Clients - # that make this test always fail. ignore Prometheus and Jaeger exporters in the noexcept here. + # that make this test always fail. Ignore these packages in the nortti test here. bazel $BAZEL_STARTUP_OPTIONS build --cxxopt=-fno-rtti --build_tag_filters=-jaeger $BAZEL_OPTIONS_ASYNC -- //... -//exporters/prometheus/... -//exporters/jaeger/... bazel $BAZEL_STARTUP_OPTIONS test --cxxopt=-fno-rtti --build_tag_filters=-jaeger $BAZEL_TEST_OPTIONS_ASYNC -- //... -//exporters/prometheus/... -//exporters/jaeger/... exit 0 diff --git a/cmake/opentelemetry-cpp-config.cmake.in b/cmake/opentelemetry-cpp-config.cmake.in index bb66dd8005..adae58dd1b 100644 --- a/cmake/opentelemetry-cpp-config.cmake.in +++ b/cmake/opentelemetry-cpp-config.cmake.in @@ -49,6 +49,7 @@ # opentelemetry-cpp::jaeger_trace_exporter - Imported target of opentelemetry-cpp::jaeger_trace_exporter # opentelemetry-cpp::zpages - Imported target of opentelemetry-cpp::zpages # opentelemetry-cpp::http_client_curl - Imported target of opentelemetry-cpp::http_client_curl +# opentelemetry-cpp::opentracing_shim - Imported target of opentelemetry-cpp::opentracing_shim # # ============================================================================= @@ -102,7 +103,8 @@ set(_OPENTELEMETRY_CPP_LIBRARIES_TEST_TARGETS etw_exporter jaeger_trace_exporter zpages - http_client_curl) + http_client_curl + opentracing_shim) foreach(_TEST_TARGET IN LISTS _OPENTELEMETRY_CPP_LIBRARIES_TEST_TARGETS) if(TARGET opentelemetry-cpp::${_TEST_TARGET}) list(APPEND OPENTELEMETRY_CPP_LIBRARIES opentelemetry-cpp::${_TEST_TARGET}) diff --git a/docs/dependencies.md b/docs/dependencies.md index 7094eeae52..a1724eb76c 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -104,3 +104,10 @@ Both these dependencies are listed here: - [Zpages](/ext/src/zpages): - None + +- [Opentracing](/opentracing-shim) + shim: + - [`opentracing-cpp`](https://github.com/opentracing/opentracing-cpp) + OpenTracing API for C++ + - A bridge layer implementing the OpenTracing API using the OpenTelemetry API + - License: `Apache License 2.0` diff --git a/opentracing-shim/BUILD b/opentracing-shim/BUILD new file mode 100644 index 0000000000..e7d90a4b4d --- /dev/null +++ b/opentracing-shim/BUILD @@ -0,0 +1,106 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "opentracing_shim", + srcs = [ + "src/shim_utils.cc", + "src/span_context_shim.cc", + "src/span_shim.cc", + "src/tracer_shim.cc", + ], + hdrs = [ + "include/opentelemetry/opentracingshim/propagation.h", + "include/opentelemetry/opentracingshim/shim_utils.h", + "include/opentelemetry/opentracingshim/span_context_shim.h", + "include/opentelemetry/opentracingshim/span_shim.h", + "include/opentelemetry/opentracingshim/tracer_shim.h", + ], + strip_include_prefix = "include", + tags = ["opentracing"], + deps = [ + "//api", + "@com_github_opentracing//:opentracing", + ], +) + +cc_test( + name = "propagation_test", + srcs = [ + "test/propagation_test.cc", + "test/shim_mocks.h", + ], + tags = [ + "opentracing_shim", + "test", + ], + deps = [ + ":opentracing_shim", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "shim_utils_test", + srcs = [ + "test/shim_mocks.h", + "test/shim_utils_test.cc", + ], + tags = [ + "opentracing_shim", + "test", + ], + deps = [ + ":opentracing_shim", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "span_shim_test", + srcs = [ + "test/shim_mocks.h", + "test/span_shim_test.cc", + ], + tags = [ + "opentracing_shim", + "test", + ], + deps = [ + ":opentracing_shim", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "span_context_shim_test", + srcs = [ + "test/span_context_shim_test.cc", + ], + tags = [ + "opentracing_shim", + "test", + ], + deps = [ + ":opentracing_shim", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "tracer_shim_test", + srcs = [ + "test/shim_mocks.h", + "test/tracer_shim_test.cc", + ], + tags = [ + "opentracing_shim", + "test", + ], + deps = [ + ":opentracing_shim", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/opentracing-shim/CMakeLists.txt b/opentracing-shim/CMakeLists.txt new file mode 100644 index 0000000000..2e89fca540 --- /dev/null +++ b/opentracing-shim/CMakeLists.txt @@ -0,0 +1,61 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +set(this_target opentelemetry_opentracing_shim) + +add_library(${this_target} src/shim_utils.cc src/span_shim.cc + src/span_context_shim.cc src/tracer_shim.cc) + +set_target_properties(${this_target} PROPERTIES EXPORT_NAME opentracing_shim) + +target_include_directories( + ${this_target} PUBLIC "$" + "$") + +if(OPENTRACING_DIR) + include_directories( + "${CMAKE_BINARY_DIR}/${OPENTRACING_DIR}/include" + "${CMAKE_SOURCE_DIR}/${OPENTRACING_DIR}/include" + "${CMAKE_SOURCE_DIR}/${OPENTRACING_DIR}/3rd_party/include") + target_link_libraries(${this_target} opentelemetry_api opentracing) +else() + target_link_libraries(${this_target} opentelemetry_api + OpenTracing::opentracing) +endif() + +install( + TARGETS ${this_target} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY include/opentelemetry/opentracingshim + DESTINATION include/opentelemetry + FILES_MATCHING + PATTERN "*.h") + +if(BUILD_TESTING) + foreach(testname propagation_test shim_utils_test span_shim_test + span_context_shim_test tracer_shim_test) + + add_executable(${testname} "test/${testname}.cc") + + if(OPENTRACING_DIR) + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_api opentelemetry_opentracing_shim opentracing) + else() + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_api opentelemetry_opentracing_shim + OpenTracing::opentracing) + endif() + + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX opentracing_shim. + TEST_LIST ${testname}) + endforeach() +endif() # BUILD_TESTING diff --git a/opentracing-shim/README.md b/opentracing-shim/README.md new file mode 100644 index 0000000000..826ce483d6 --- /dev/null +++ b/opentracing-shim/README.md @@ -0,0 +1,51 @@ +# OpenTracing Shim + +[![Apache License][license-image]][license-image] + +The OpenTracing shim is a bridge layer from OpenTelemetry to the OpenTracing API. +It takes an OpenTelemetry Tracer and exposes it as an implementation compatible with +that of an OpenTracing Tracer. + +## Usage + +Use the TracerShim wherever you initialize your OpenTracing tracers. +There are 2 ways to expose an OpenTracing Tracer: + +1. From the global OpenTelemetry configuration: + + ```cpp + auto tracer_shim = TracerShim::createTracerShim(); + ``` + +1. From a provided OpenTelemetry Tracer instance: + + ```cpp + auto tracer_shim = TracerShim::createTracerShim(tracer); + ``` + +Optionally, one can also specify the propagators to be used for the OpenTracing `TextMap` +and `HttpHeaders` formats: + +```cpp +OpenTracingPropagators propagators{ + .text_map = nostd::shared_ptr(new CustomTextMap()), + .http_headers = nostd::shared_ptr(new trace::propagation::HttpTraceContext()) +}; + +auto tracer_shim = TracerShim::createTracerShim(tracer, propagators); +``` + +If propagators are not specified, OpenTelemetry's global propagator will be used. + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +## Useful links + +- For more information on OpenTelemetry, visit: +- For help or feedback on this project, join us in [GitHub Discussions][discussions-url] + +[discussions-url]: https://github.com/open-telemetry/opentelemetry-cpp/discussions +[license-url]: https://github.com/open-telemetry/opentelemetry-cpp/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat diff --git a/opentracing-shim/include/opentelemetry/opentracingshim/propagation.h b/opentracing-shim/include/opentelemetry/opentracingshim/propagation.h new file mode 100644 index 0000000000..c226d9da83 --- /dev/null +++ b/opentracing-shim/include/opentelemetry/opentracingshim/propagation.h @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "opentelemetry/baggage/baggage.h" +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/context/propagation/text_map_propagator.h" +#include "opentelemetry/nostd/type_traits.h" +#include "opentracing/propagation.h" +#include "opentracing/value.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +class CarrierWriterShim : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + CarrierWriterShim(const opentracing::TextMapWriter &writer) : writer_(writer) {} + + virtual nostd::string_view Get(nostd::string_view) const noexcept override + { + return {}; // Not required for Opentracing writer + } + + virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override + { + writer_.Set(key.data(), value.data()); + } + +private: + const opentracing::TextMapWriter &writer_; +}; + +class CarrierReaderShim : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + CarrierReaderShim(const opentracing::TextMapReader &reader) : reader_(reader) {} + + virtual nostd::string_view Get(nostd::string_view key) const noexcept override + { + nostd::string_view value; + + // First try carrier.LookupKey since that can potentially be the fastest approach. + if (auto result = reader_.LookupKey(key.data())) + { + value = result.value().data(); + } + else // Fall back to iterating through all of the keys. + { + reader_.ForeachKey([key, &value](opentracing::string_view k, + opentracing::string_view v) -> opentracing::expected { + if (k == key.data()) + { + value = v.data(); + // Found key, so bail out of the loop with a success error code. + return opentracing::make_unexpected(std::error_code{}); + } + return opentracing::make_expected(); + }); + } + + return value; + } + + virtual void Set(nostd::string_view, nostd::string_view) noexcept override + { + // Not required for Opentracing reader + } + + virtual bool Keys(nostd::function_ref callback) const noexcept override + { + return reader_ + .ForeachKey([&callback](opentracing::string_view key, + opentracing::string_view) -> opentracing::expected { + return callback(key.data()) ? opentracing::make_expected() + : opentracing::make_unexpected(std::error_code{}); + }) + .has_value(); + } + +private: + const opentracing::TextMapReader &reader_; +}; + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/include/opentelemetry/opentracingshim/shim_utils.h b/opentracing-shim/include/opentelemetry/opentracingshim/shim_utils.h new file mode 100644 index 0000000000..871bed590f --- /dev/null +++ b/opentracing-shim/include/opentelemetry/opentracingshim/shim_utils.h @@ -0,0 +1,165 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "opentelemetry/opentracingshim/span_context_shim.h" + +#include "opentelemetry/trace/semantic_conventions.h" +#include "opentelemetry/trace/tracer.h" +#include "opentracing/tracer.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ +namespace utils +{ + +static inline opentelemetry::common::AttributeValue attributeFromValue( + const opentracing::Value &value) +{ + using opentelemetry::common::AttributeValue; + + static struct + { + AttributeValue operator()(bool v) { return v; } + AttributeValue operator()(double v) { return v; } + AttributeValue operator()(int64_t v) { return v; } + AttributeValue operator()(uint64_t v) { return v; } + AttributeValue operator()(const std::string &v) { return nostd::string_view{v}; } + AttributeValue operator()(opentracing::string_view v) { return nostd::string_view{v.data()}; } + AttributeValue operator()(std::nullptr_t) { return nostd::string_view{}; } + AttributeValue operator()(const char *v) { return v; } + AttributeValue operator()(opentracing::util::recursive_wrapper) + { + return nostd::string_view{}; + } + AttributeValue operator()(opentracing::util::recursive_wrapper) + { + return nostd::string_view{}; + } + } AttributeMapper; + + return opentracing::Value::visit(value, AttributeMapper); +} + +static inline std::string stringFromValue(const opentracing::Value &value) +{ + static struct + { + std::string operator()(bool v) { return v ? "true" : "false"; } + std::string operator()(double v) { return std::to_string(v); } + std::string operator()(int64_t v) { return std::to_string(v); } + std::string operator()(uint64_t v) { return std::to_string(v); } + std::string operator()(const std::string &v) { return v; } + std::string operator()(opentracing::string_view v) { return std::string{v.data()}; } + std::string operator()(std::nullptr_t) { return std::string{}; } + std::string operator()(const char *v) { return std::string{v}; } + std::string operator()(opentracing::util::recursive_wrapper) + { + return std::string{}; + } + std::string operator()(opentracing::util::recursive_wrapper) + { + return std::string{}; + } + } StringMapper; + + return opentracing::Value::visit(value, StringMapper); +} + +static inline bool isBaggageEmpty(const nostd::shared_ptr &baggage) +{ + if (baggage) + { + return baggage->GetAllEntries([](nostd::string_view, nostd::string_view) { return false; }); + } + + return true; +} + +class LinksIterable final : public opentelemetry::trace::SpanContextKeyValueIterable +{ +public: + using RefsList = + std::vector>; + + explicit LinksIterable(const RefsList &refs) noexcept : refs_(refs) {} + + bool ForEachKeyValue(nostd::function_ref + callback) const noexcept override + { + using opentracing::SpanReferenceType; + using namespace opentelemetry::trace::SemanticConventions; + using LinksList = std::initializer_list>; + + for (const auto &entry : refs_) + { + nostd::string_view span_kind; + + if (entry.first == SpanReferenceType::ChildOfRef) + { + span_kind = OpentracingRefTypeValues::kChildOf; + } + else if (entry.first == SpanReferenceType::FollowsFromRef) + { + span_kind = OpentracingRefTypeValues::kFollowsFrom; + } + + auto context_shim = SpanContextShim::extractFrom(entry.second); + + if (context_shim && !span_kind.empty() && + !callback(context_shim->context(), opentelemetry::common::KeyValueIterableView( + {{kOpentracingRefType, span_kind}}))) + { + return false; + } + } + + return true; + } + + size_t size() const noexcept override { return refs_.size(); } + +private: + const RefsList &refs_; +}; + +class TagsIterable final : public opentelemetry::common::KeyValueIterable +{ +public: + explicit TagsIterable( + const std::vector> &tags) noexcept + : tags_(tags) + {} + + bool ForEachKeyValue(nostd::function_ref + callback) const noexcept override + { + for (const auto &entry : tags_) + { + if (!callback(entry.first, utils::attributeFromValue(entry.second))) + return false; + } + return true; + } + + size_t size() const noexcept override { return tags_.size(); } + +private: + const std::vector> &tags_; +}; + +opentelemetry::trace::StartSpanOptions makeOptionsShim( + const opentracing::StartSpanOptions &options) noexcept; +LinksIterable makeIterableLinks(const opentracing::StartSpanOptions &options) noexcept; +TagsIterable makeIterableTags(const opentracing::StartSpanOptions &options) noexcept; +nostd::shared_ptr makeBaggage( + const opentracing::StartSpanOptions &options) noexcept; + +} // namespace utils +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/include/opentelemetry/opentracingshim/span_context_shim.h b/opentracing-shim/include/opentelemetry/opentracingshim/span_context_shim.h new file mode 100644 index 0000000000..62fec23c1a --- /dev/null +++ b/opentracing-shim/include/opentelemetry/opentracingshim/span_context_shim.h @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "opentelemetry/baggage/baggage.h" +#include "opentelemetry/trace/span_context.h" +#include "opentracing/span.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +using BaggagePtr = nostd::shared_ptr; + +class SpanContextShim final : public opentracing::SpanContext +{ +public: + explicit SpanContextShim(const opentelemetry::trace::SpanContext &context, + const BaggagePtr &baggage) + : context_(context), baggage_(baggage) + {} + + inline const opentelemetry::trace::SpanContext &context() const { return context_; } + inline const BaggagePtr baggage() const { return baggage_; } + SpanContextShim newWithKeyValue(nostd::string_view key, nostd::string_view value) const noexcept; + bool BaggageItem(nostd::string_view key, std::string &value) const noexcept; + + using VisitBaggageItem = std::function; + void ForeachBaggageItem(VisitBaggageItem f) const override; + std::unique_ptr Clone() const noexcept override; + std::string ToTraceID() const noexcept override; + std::string ToSpanID() const noexcept override; + + static inline const SpanContextShim *extractFrom(const opentracing::SpanContext *span_context) + { +#ifndef OPENTELEMETRY_RTTI_ENABLED + auto result = static_cast(span_context); + return result && result->provides_context_and_baggage_ == kUniqueTag ? result : nullptr; +#else + return dynamic_cast(span_context); +#endif + } + +private: + template + static std::string toHexString(const T &id_item) + { + char buf[T::kSize * 2]; + id_item.ToLowerBase16(buf); + return std::string(buf, sizeof(buf)); + } + + opentelemetry::trace::SpanContext context_; + BaggagePtr baggage_; + +#ifndef OPENTELEMETRY_RTTI_ENABLED + static constexpr uint64_t kUniqueTag = 0x07e1ca578e57a71c; + uint64_t provides_context_and_baggage_ = kUniqueTag; +#endif +}; + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/include/opentelemetry/opentracingshim/span_shim.h b/opentracing-shim/include/opentelemetry/opentracingshim/span_shim.h new file mode 100644 index 0000000000..4fe48852c1 --- /dev/null +++ b/opentracing-shim/include/opentelemetry/opentracingshim/span_shim.h @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "opentelemetry/opentracingshim/span_context_shim.h" +#include "opentelemetry/opentracingshim/tracer_shim.h" + +#include "opentelemetry/baggage/baggage.h" +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/trace/span.h" +#include "opentracing/span.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +using SpanPtr = nostd::shared_ptr; +using EventEntry = std::pair; + +class SpanShim : public opentracing::Span +{ +public: + explicit SpanShim(const TracerShim &tracer, const SpanPtr &span, const BaggagePtr &baggage) + : tracer_(tracer), span_(span), context_(span->GetContext(), baggage) + {} + + void handleError(const opentracing::Value &value) noexcept; + + void FinishWithOptions( + const opentracing::FinishSpanOptions &finish_span_options) noexcept override; + void SetOperationName(opentracing::string_view name) noexcept override; + void SetTag(opentracing::string_view key, const opentracing::Value &value) noexcept override; + void SetBaggageItem(opentracing::string_view restricted_key, + opentracing::string_view value) noexcept override; + std::string BaggageItem(opentracing::string_view restricted_key) const noexcept override; + void Log(std::initializer_list fields) noexcept override; + void Log(opentracing::SystemTime timestamp, + std::initializer_list fields) noexcept override; + void Log(opentracing::SystemTime timestamp, + const std::vector &fields) noexcept override; + inline const opentracing::SpanContext &context() const noexcept override { return context_; } + inline const opentracing::Tracer &tracer() const noexcept override { return tracer_; } + +private: + void logImpl(nostd::span fields, + const opentracing::SystemTime *const timestamp) noexcept; + + const TracerShim &tracer_; + SpanPtr span_; + SpanContextShim context_; + mutable opentelemetry::common::SpinLockMutex context_lock_; +}; + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/include/opentelemetry/opentracingshim/tracer_shim.h b/opentracing-shim/include/opentelemetry/opentracingshim/tracer_shim.h new file mode 100644 index 0000000000..7f2d492efd --- /dev/null +++ b/opentracing-shim/include/opentelemetry/opentracingshim/tracer_shim.h @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "opentelemetry/context/propagation/text_map_propagator.h" +#include "opentelemetry/trace/provider.h" +#include "opentelemetry/trace/tracer.h" +#include "opentracing/tracer.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +using TracerPtr = nostd::shared_ptr; +using TracerProviderPtr = nostd::shared_ptr; +using PropagatorPtr = nostd::shared_ptr; + +struct OpenTracingPropagators +{ + PropagatorPtr text_map; + PropagatorPtr http_headers; +}; + +class TracerShim : public opentracing::Tracer +{ +public: + static inline std::shared_ptr createTracerShim( + const TracerProviderPtr &provider = opentelemetry::trace::Provider::GetTracerProvider(), + const OpenTracingPropagators &propagators = {}) noexcept + { + // This operation MUST accept the following parameters: + // - An OpenTelemetry TracerProvider. This operation MUST use this TracerProvider to obtain a + // Tracer with the name opentracing-shim along with the current shim library version. + // - OpenTelemetry Propagators to be used to perform injection and extraction for the + // OpenTracing TextMap and HTTPHeaders formats. If not specified, no Propagator values will + // be stored in the Shim, and the global OpenTelemetry TextMap propagator will be used for + // both OpenTracing TextMap and HTTPHeaders formats. + return std::shared_ptr( + new (std::nothrow) TracerShim(provider->GetTracer("opentracing-shim"), propagators)); + } + + std::unique_ptr StartSpanWithOptions( + opentracing::string_view operation_name, + const opentracing::StartSpanOptions &options) const noexcept override; + opentracing::expected Inject(const opentracing::SpanContext &sc, + std::ostream &writer) const override; + opentracing::expected Inject(const opentracing::SpanContext &sc, + const opentracing::TextMapWriter &writer) const override; + opentracing::expected Inject(const opentracing::SpanContext &sc, + const opentracing::HTTPHeadersWriter &writer) const override; + opentracing::expected> Extract( + std::istream &reader) const override; + opentracing::expected> Extract( + const opentracing::TextMapReader &reader) const override; + opentracing::expected> Extract( + const opentracing::HTTPHeadersReader &reader) const override; + inline void Close() noexcept override { is_closed_ = true; } + +private: + explicit TracerShim(const TracerPtr &tracer, const OpenTracingPropagators &propagators) + : tracer_(tracer), propagators_(propagators) + {} + + opentracing::expected injectImpl(const opentracing::SpanContext &sc, + const opentracing::TextMapWriter &writer, + const PropagatorPtr &propagator) const; + opentracing::expected> extractImpl( + const opentracing::TextMapReader &reader, + const PropagatorPtr &propagator) const; + + TracerPtr tracer_; + OpenTracingPropagators propagators_; + bool is_closed_ = false; +}; + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/src/shim_utils.cc b/opentracing-shim/src/shim_utils.cc new file mode 100644 index 0000000000..8829eafdf3 --- /dev/null +++ b/opentracing-shim/src/shim_utils.cc @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "opentelemetry/opentracingshim/shim_utils.h" + +#include "opentelemetry/baggage/baggage_context.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ +namespace utils +{ + +opentelemetry::trace::StartSpanOptions makeOptionsShim( + const opentracing::StartSpanOptions &options) noexcept +{ + using opentracing::SpanReferenceType; + // If an explicit start timestamp is specified, a conversion MUST + // be done to match the OpenTracing and OpenTelemetry units. + opentelemetry::trace::StartSpanOptions options_shim; + options_shim.start_system_time = + opentelemetry::common::SystemTimestamp{options.start_system_timestamp}; + options_shim.start_steady_time = + opentelemetry::common::SteadyTimestamp{options.start_steady_timestamp}; + + const auto &refs = options.references; + // If a list of Span references is specified... + if (!refs.empty()) + { + const auto &first_child_of = std::find_if( + refs.cbegin(), refs.cend(), + [](const std::pair &entry) { + return entry.first == SpanReferenceType::ChildOfRef; + }); + // The first SpanContext with Child Of type in the entire list is used as parent, + // else the first SpanContext is used as parent + auto context = (first_child_of != refs.cend()) ? first_child_of->second : refs.cbegin()->second; + + if (auto context_shim = SpanContextShim::extractFrom(context)) + { + options_shim.parent = context_shim->context(); + } + } + + return options_shim; +} + +LinksIterable makeIterableLinks(const opentracing::StartSpanOptions &options) noexcept +{ + return LinksIterable(options.references); +} + +TagsIterable makeIterableTags(const opentracing::StartSpanOptions &options) noexcept +{ + return TagsIterable(options.tags); +} + +nostd::shared_ptr makeBaggage( + const opentracing::StartSpanOptions &options) noexcept +{ + using namespace opentelemetry::baggage; + + std::unordered_map baggage_items; + // If a list of Span references is specified... + for (const auto &entry : options.references) + { + // The union of their Baggage values MUST be used + // as the initial Baggage of the newly created Span. + entry.second->ForeachBaggageItem( + [&baggage_items](const std::string &key, const std::string &value) { + // It is unspecified which Baggage value is used in the case of repeated keys. + if (baggage_items.find(key) == baggage_items.end()) + { + baggage_items.emplace(key, value); // Here, only insert if key not already present + } + return true; + }); + } + // If no such list of references is specified, the current Baggage + // MUST be used as the initial value of the newly created Span. + return baggage_items.empty() ? GetBaggage(opentelemetry::context::RuntimeContext::GetCurrent()) + : nostd::shared_ptr(new Baggage(baggage_items)); +} + +} // namespace utils +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/src/span_context_shim.cc b/opentracing-shim/src/span_context_shim.cc new file mode 100644 index 0000000000..54af4da9a3 --- /dev/null +++ b/opentracing-shim/src/span_context_shim.cc @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "opentelemetry/opentracingshim/span_context_shim.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +SpanContextShim SpanContextShim::newWithKeyValue(nostd::string_view key, + nostd::string_view value) const noexcept +{ + return SpanContextShim{context_, baggage_->Set(key, value)}; +} + +bool SpanContextShim::BaggageItem(nostd::string_view key, std::string &value) const noexcept +{ + return baggage_->GetValue(key, value); +} + +void SpanContextShim::ForeachBaggageItem(VisitBaggageItem f) const +{ + baggage_->GetAllEntries([&f](nostd::string_view key, nostd::string_view value) { + return f(key.data(), value.data()); + }); +} + +std::unique_ptr SpanContextShim::Clone() const noexcept +{ + return std::unique_ptr(new (std::nothrow) SpanContextShim(context_, baggage_)); +} + +std::string SpanContextShim::ToTraceID() const noexcept +{ + return toHexString(context_.trace_id()); +} + +std::string SpanContextShim::ToSpanID() const noexcept +{ + return toHexString(context_.span_id()); +} + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/src/span_shim.cc b/opentracing-shim/src/span_shim.cc new file mode 100644 index 0000000000..808b6b9e74 --- /dev/null +++ b/opentracing-shim/src/span_shim.cc @@ -0,0 +1,164 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "opentelemetry/opentracingshim/span_shim.h" +#include "opentelemetry/opentracingshim/shim_utils.h" +#include "opentelemetry/opentracingshim/span_context_shim.h" +#include "opentelemetry/opentracingshim/tracer_shim.h" + +#include "opentelemetry/trace/semantic_conventions.h" +#include "opentelemetry/trace/span_metadata.h" +#include "opentracing/ext/tags.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +void SpanShim::handleError(const opentracing::Value &value) noexcept +{ + using opentelemetry::trace::StatusCode; + // The error tag MUST be mapped to StatusCode: + const auto &error_tag = utils::stringFromValue(value); + // - no value being set maps to Unset. + auto code = StatusCode::kUnset; + + if (error_tag == "true") // - true maps to Error. + { + code = StatusCode::kError; + } + else if (error_tag == "false") // - false maps to Ok. + { + code = StatusCode::kOk; + } + + span_->SetStatus(code); +} + +void SpanShim::FinishWithOptions(const opentracing::FinishSpanOptions &finish_span_options) noexcept +{ + // If an explicit timestamp is specified, a conversion MUST + // be done to match the OpenTracing and OpenTelemetry units. + span_->End({{finish_span_options.finish_steady_timestamp}}); +} + +void SpanShim::SetOperationName(opentracing::string_view name) noexcept +{ + span_->UpdateName(name.data()); +} + +void SpanShim::SetTag(opentracing::string_view key, const opentracing::Value &value) noexcept +{ + // Calls Set Attribute on the underlying OpenTelemetry Span with the specified key/value pair. + if (key == opentracing::ext::error) + { + handleError(value); + } + else + { + span_->SetAttribute(key.data(), utils::attributeFromValue(value)); + } +} + +void SpanShim::SetBaggageItem(opentracing::string_view restricted_key, + opentracing::string_view value) noexcept +{ + // Creates a new SpanContext Shim with a new OpenTelemetry Baggage containing the specified + // Baggage key/value pair, and sets it as the current instance for this Span Shim. + if (restricted_key.empty() || value.empty()) + return; + // This operation MUST be safe to be called concurrently. + const std::lock_guard guard(context_lock_); + context_ = context_.newWithKeyValue(restricted_key.data(), value.data()); +} + +std::string SpanShim::BaggageItem(opentracing::string_view restricted_key) const noexcept +{ + // Returns the value for the specified key in the OpenTelemetry Baggage + // of the current SpanContext Shim, or null if none exists. + if (restricted_key.empty()) + return ""; + // This operation MUST be safe to be called concurrently. + const std::lock_guard guard(context_lock_); + std::string value; + return context_.BaggageItem(restricted_key.data(), value) ? value : ""; +} + +void SpanShim::Log(std::initializer_list fields) noexcept +{ + // If an explicit timestamp is specified, a conversion MUST + // be done to match the OpenTracing and OpenTelemetry units. + logImpl(fields, nullptr); +} + +void SpanShim::Log(opentracing::SystemTime timestamp, + std::initializer_list fields) noexcept +{ + // If an explicit timestamp is specified, a conversion MUST + // be done to match the OpenTracing and OpenTelemetry units. + logImpl(fields, ×tamp); +} + +void SpanShim::Log(opentracing::SystemTime timestamp, + const std::vector &fields) noexcept +{ + // If an explicit timestamp is specified, a conversion MUST + // be done to match the OpenTracing and OpenTelemetry units. + logImpl(fields, ×tamp); +} + +void SpanShim::logImpl(nostd::span fields, + const opentracing::SystemTime *const timestamp) noexcept +{ + // The Add Event’s name parameter MUST be the value with the event key + // in the pair set, or else fallback to use the log literal string. + const auto &event = std::find_if(fields.begin(), fields.end(), + [](const EventEntry &item) { return item.first == "event"; }); + auto name = (event != fields.end()) ? utils::stringFromValue(event->second) : std::string{"log"}; + // If pair set contains an event=error entry, the values MUST be mapped to an Event + // with the conventions outlined in the Exception semantic conventions document: + bool is_error = (name == opentracing::ext::error); + // A call to AddEvent is performed with name being set to exception + if (is_error) + name = "exception"; + // Along the specified key/value pair set as additional event attributes... + std::vector> attributes; + attributes.reserve(fields.size()); + + for (const auto &entry : fields) + { + nostd::string_view key = entry.first.data(); + // ... including mapping of the following key/value pairs: + if (is_error) + { + if (key == "error.kind") // - error.kind maps to exception.type. + { + key = opentelemetry::trace::SemanticConventions::kExceptionType; + } + else if (key == "message") // - message maps to exception.message. + { + key = opentelemetry::trace::SemanticConventions::kExceptionMessage; + } + else if (key == "stack") // - stack maps to exception.stacktrace. + { + key = opentelemetry::trace::SemanticConventions::kExceptionStacktrace; + } + } + + attributes.emplace_back(key, utils::attributeFromValue(entry.second)); + } + + // Calls Add Events on the underlying OpenTelemetry Span with the specified key/value pair set. + if (timestamp) + { + span_->AddEvent(name, *timestamp, attributes); + } + else + { + span_->AddEvent(name, attributes); + } +} + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/src/tracer_shim.cc b/opentracing-shim/src/tracer_shim.cc new file mode 100644 index 0000000000..c8805d15ef --- /dev/null +++ b/opentracing-shim/src/tracer_shim.cc @@ -0,0 +1,162 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "opentelemetry/opentracingshim/tracer_shim.h" +#include "opentelemetry/opentracingshim/propagation.h" +#include "opentelemetry/opentracingshim/shim_utils.h" +#include "opentelemetry/opentracingshim/span_shim.h" + +#include "opentelemetry/baggage/baggage_context.h" +#include "opentelemetry/context/propagation/global_propagator.h" +#include "opentelemetry/trace/context.h" +#include "opentracing/ext/tags.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +std::unique_ptr TracerShim::StartSpanWithOptions( + opentracing::string_view operation_name, + const opentracing::StartSpanOptions &options) const noexcept +{ + if (is_closed_) + return nullptr; + + const auto &opts = utils::makeOptionsShim(options); + const auto &links = utils::makeIterableLinks(options); + const auto &attributes = utils::makeIterableTags(options); + const auto &baggage = utils::makeBaggage(options); + auto span = tracer_->StartSpan(operation_name.data(), attributes, links, opts); + auto span_shim = new (std::nothrow) SpanShim(*this, span, baggage); + + // If an initial set of tags is specified and the OpenTracing error tag + // is included after the OpenTelemetry Span was created. + const auto &error_entry = + std::find_if(options.tags.cbegin(), options.tags.cend(), + [](const std::pair &entry) { + return entry.first == opentracing::ext::error; + }); + // The Shim layer MUST perform the same error handling as described in the Set Tag operation + if (span_shim && error_entry != options.tags.cend()) + { + span_shim->handleError(error_entry->second); + } + + return std::unique_ptr(span_shim); +} + +opentracing::expected TracerShim::Inject(const opentracing::SpanContext &, + std::ostream &) const +{ + // Errors MAY be raised if the specified Format is not recognized, + // depending on the specific OpenTracing Language API. + return opentracing::make_unexpected(opentracing::invalid_carrier_error); +} + +opentracing::expected TracerShim::Inject(const opentracing::SpanContext &sc, + const opentracing::TextMapWriter &writer) const +{ + // TextMap and HttpHeaders formats MUST use their explicitly specified TextMapPropagator, + // if any, or else use the global TextMapPropagator. + const auto &propagator = + propagators_.text_map + ? propagators_.text_map + : opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + + return injectImpl(sc, writer, propagator); +} + +opentracing::expected TracerShim::Inject(const opentracing::SpanContext &sc, + const opentracing::HTTPHeadersWriter &writer) const +{ + // TextMap and HttpHeaders formats MUST use their explicitly specified TextMapPropagator, + // if any, or else use the global TextMapPropagator. + const auto &propagator = + propagators_.http_headers + ? propagators_.http_headers + : opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + + return injectImpl(sc, writer, propagator); +} + +opentracing::expected> TracerShim::Extract( + std::istream &) const +{ + // Errors MAY be raised if either the Format is not recognized or no value + // could be extracted, depending on the specific OpenTracing Language API. + return opentracing::make_unexpected(opentracing::invalid_carrier_error); +} + +opentracing::expected> TracerShim::Extract( + const opentracing::TextMapReader &reader) const +{ + // TextMap and HttpHeaders formats MUST use their explicitly specified TextMapPropagator, + // if any, or else use the global TextMapPropagator. + const auto &propagator = + propagators_.text_map + ? propagators_.text_map + : opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + + return extractImpl(reader, propagator); +} + +opentracing::expected> TracerShim::Extract( + const opentracing::HTTPHeadersReader &reader) const +{ + // TextMap and HttpHeaders formats MUST use their explicitly specified TextMapPropagator, + // if any, or else use the global TextMapPropagator. + const auto &propagator = + propagators_.http_headers + ? propagators_.http_headers + : opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + + return extractImpl(reader, propagator); +} + +opentracing::expected TracerShim::injectImpl(const opentracing::SpanContext &sc, + const opentracing::TextMapWriter &writer, + const PropagatorPtr &propagator) const +{ + // Inject the underlying OpenTelemetry Span and Baggage using either the explicitly registered + // or the global OpenTelemetry Propagators, as configured at construction time. + if (auto context_shim = SpanContextShim::extractFrom(&sc)) + { + auto current_context = opentelemetry::context::RuntimeContext::GetCurrent(); + // It MUST inject any non-empty Baggage even amidst no valid SpanContext. + const auto &context = + opentelemetry::baggage::SetBaggage(current_context, context_shim->baggage()); + + CarrierWriterShim carrier{writer}; + propagator->Inject(carrier, context); + return opentracing::make_expected(); + } + + return opentracing::make_unexpected(opentracing::invalid_span_context_error); +} + +opentracing::expected> TracerShim::extractImpl( + const opentracing::TextMapReader &reader, + const PropagatorPtr &propagator) const +{ + // Extract the underlying OpenTelemetry Span and Baggage using either the explicitly registered + // or the global OpenTelemetry Propagators, as configured at construction time. + CarrierReaderShim carrier{reader}; + auto current_context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto context = propagator->Extract(carrier, current_context); + auto span_context = opentelemetry::trace::GetSpan(context)->GetContext(); + auto baggage = opentelemetry::baggage::GetBaggage(context); + + // If the extracted SpanContext is invalid AND the extracted Baggage is empty, + // this operation MUST return a null value, and otherwise it MUST return a + // SpanContext Shim instance with the extracted values. + SpanContextShim *context_shim = (!span_context.IsValid() && utils::isBaggageEmpty(baggage)) + ? nullptr + : new (std::nothrow) SpanContextShim(span_context, baggage); + + return opentracing::make_expected(std::unique_ptr(context_shim)); +} + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/test/propagation_test.cc b/opentracing-shim/test/propagation_test.cc new file mode 100644 index 0000000000..eb59630405 --- /dev/null +++ b/opentracing-shim/test/propagation_test.cc @@ -0,0 +1,106 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "shim_mocks.h" + +#include "opentelemetry/opentracingshim/propagation.h" + +#include + +namespace baggage = opentelemetry::baggage; +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; +namespace shim = opentelemetry::opentracingshim; + +TEST(PropagationTest, TextMapReader_Get_LookupKey_Unsupported) +{ + std::unordered_map text_map; + TextMapCarrier testee{text_map}; + ASSERT_FALSE(testee.supports_lookup); + ASSERT_EQ(testee.foreach_key_call_count, 0); + + shim::CarrierReaderShim tester{testee}; + auto lookup_unsupported = testee.LookupKey("foo"); + ASSERT_TRUE(text_map.empty()); + ASSERT_TRUE(opentracing::are_errors_equal(lookup_unsupported.error(), + opentracing::lookup_key_not_supported_error)); + ASSERT_EQ(tester.Get("foo"), nostd::string_view{}); + ASSERT_EQ(testee.foreach_key_call_count, 1); + + text_map["foo"] = "bar"; + auto lookup_still_unsupported = testee.LookupKey("foo"); + ASSERT_FALSE(text_map.empty()); + ASSERT_TRUE(opentracing::are_errors_equal(lookup_still_unsupported.error(), + opentracing::lookup_key_not_supported_error)); + ASSERT_EQ(tester.Get("foo"), nostd::string_view{"bar"}); + ASSERT_EQ(testee.foreach_key_call_count, 2); +} + +TEST(PropagationTest, TextMapReader_Get_LookupKey_Supported) +{ + std::unordered_map text_map; + TextMapCarrier testee{text_map}; + testee.supports_lookup = true; + ASSERT_TRUE(testee.supports_lookup); + ASSERT_EQ(testee.foreach_key_call_count, 0); + + shim::CarrierReaderShim tester{testee}; + auto lookup_not_found = testee.LookupKey("foo"); + ASSERT_TRUE(text_map.empty()); + ASSERT_TRUE( + opentracing::are_errors_equal(lookup_not_found.error(), opentracing::key_not_found_error)); + ASSERT_EQ(tester.Get("foo"), nostd::string_view{}); + ASSERT_EQ(testee.foreach_key_call_count, 1); + + text_map["foo"] = "bar"; + auto lookup_found = testee.LookupKey("foo"); + ASSERT_FALSE(text_map.empty()); + ASSERT_EQ(lookup_found.value(), opentracing::string_view{"bar"}); + ASSERT_EQ(tester.Get("foo"), nostd::string_view{"bar"}); + ASSERT_EQ(testee.foreach_key_call_count, 1); +} + +TEST(PropagationTest, TextMapReader_Keys) +{ + std::unordered_map text_map; + TextMapCarrier testee{text_map}; + ASSERT_EQ(testee.foreach_key_call_count, 0); + + shim::CarrierReaderShim tester{testee}; + std::vector kvs; + auto callback = [&text_map, &kvs](nostd::string_view k) { + kvs.emplace_back(k); + return !text_map.empty(); + }; + + ASSERT_TRUE(tester.Keys(callback)); + ASSERT_TRUE(text_map.empty()); + ASSERT_TRUE(kvs.empty()); + ASSERT_EQ(testee.foreach_key_call_count, 1); + + text_map["foo"] = "bar"; + text_map["bar"] = "baz"; + text_map["baz"] = "foo"; + ASSERT_TRUE(tester.Keys(callback)); + ASSERT_FALSE(text_map.empty()); + ASSERT_EQ(text_map.size(), kvs.size()); + ASSERT_EQ(testee.foreach_key_call_count, 2); +} + +TEST(PropagationTest, TextMapWriter_Set) +{ + std::unordered_map text_map; + TextMapCarrier testee{text_map}; + shim::CarrierWriterShim tester{testee}; + ASSERT_TRUE(text_map.empty()); + + tester.Set("foo", "bar"); + tester.Set("bar", "baz"); + tester.Set("baz", "foo"); + ASSERT_EQ(text_map.size(), 3); + ASSERT_EQ(text_map["foo"], "bar"); + ASSERT_EQ(text_map["bar"], "baz"); + ASSERT_EQ(text_map["baz"], "foo"); +} diff --git a/opentracing-shim/test/shim_mocks.h b/opentracing-shim/test/shim_mocks.h new file mode 100644 index 0000000000..555f6cb65b --- /dev/null +++ b/opentracing-shim/test/shim_mocks.h @@ -0,0 +1,230 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "opentelemetry/baggage/baggage_context.h" +#include "opentelemetry/context/propagation/text_map_propagator.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_metadata.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/trace/tracer_provider.h" +#include "opentracing/propagation.h" + +#include +#include + +namespace trace_api = opentelemetry::trace; +namespace baggage = opentelemetry::baggage; +namespace common = opentelemetry::common; +namespace context = opentelemetry::context; +namespace nostd = opentelemetry::nostd; + +struct MockSpan final : public trace_api::Span +{ + void SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept override + { + attribute_ = {key.data(), value}; + } + + void AddEvent(nostd::string_view name, + common::SystemTimestamp timestamp, + const common::KeyValueIterable &attributes) noexcept override + { + std::unordered_map attribute_map; + attribute_map.reserve(attributes.size()); + attributes.ForEachKeyValue( + [&attribute_map](nostd::string_view key, const common::AttributeValue &value) { + attribute_map.emplace(key.data(), value); + return true; + }); + event_ = {name.data(), timestamp, attribute_map}; + } + + void AddEvent(nostd::string_view name, + const common::KeyValueIterable &attributes) noexcept override + { + AddEvent(name, {}, attributes); + } + + void AddEvent(nostd::string_view, common::SystemTimestamp) noexcept override {} + + void AddEvent(nostd::string_view) noexcept override {} + + void SetStatus(trace_api::StatusCode code, nostd::string_view description) noexcept override + { + status_ = {code, description.data()}; + } + + void UpdateName(nostd::string_view name) noexcept override { name_ = name.data(); } + + void End(const trace_api::EndSpanOptions &options) noexcept override { options_ = options; } + + bool IsRecording() const noexcept override { return false; } + + trace_api::SpanContext GetContext() const noexcept override + { + return trace_api::SpanContext(false, false); + } + + std::pair attribute_; + std::tuple> + event_; + std::pair status_; + std::string name_; + trace_api::EndSpanOptions options_; +}; + +struct MockTracer final : public trace_api::Tracer +{ + nostd::shared_ptr StartSpan( + nostd::string_view name, + const common::KeyValueIterable &, + const trace_api::SpanContextKeyValueIterable &, + const trace_api::StartSpanOptions &) noexcept override + { + span_ = new MockSpan(); + span_->name_ = std::string{name}; + return nostd::shared_ptr(span_); + } + + void ForceFlushWithMicroseconds(uint64_t) noexcept override {} + + void CloseWithMicroseconds(uint64_t) noexcept override {} + + MockSpan *span_; +}; + +struct MockTracerProvider final : public trace_api::TracerProvider +{ + nostd::shared_ptr GetTracer(nostd::string_view library_name, + nostd::string_view, + nostd::string_view) noexcept override + { + library_name_ = std::string{library_name}; + tracer_ = new MockTracer(); + return nostd::shared_ptr(tracer_); + } + + std::string library_name_; + MockTracer *tracer_; +}; + +struct MockPropagator : public context::propagation::TextMapPropagator +{ + // Returns the context that is stored in the carrier with the TextMapCarrier as extractor. + context::Context Extract(const context::propagation::TextMapCarrier &carrier, + context::Context &context) noexcept override + { + std::vector> kvs; + carrier.Keys([&carrier, &kvs](nostd::string_view k) { + kvs.emplace_back(k, carrier.Get(k)); + return true; + }); + is_extracted = true; + return baggage::SetBaggage(context, + nostd::shared_ptr(new baggage::Baggage(kvs))); + } + + // Sets the context for carrier with self defined rules. + void Inject(context::propagation::TextMapCarrier &carrier, + const context::Context &context) noexcept override + { + auto baggage = baggage::GetBaggage(context); + baggage->GetAllEntries([&carrier](nostd::string_view k, nostd::string_view v) { + carrier.Set(k, v); + return true; + }); + is_injected = true; + } + + // Gets the fields set in the carrier by the `inject` method + bool Fields(nostd::function_ref) const noexcept override + { + return true; + } + + bool is_extracted = false; + bool is_injected = false; +}; + +struct TextMapCarrier : opentracing::TextMapReader, opentracing::TextMapWriter +{ + TextMapCarrier(std::unordered_map &text_map_) : text_map(text_map_) {} + + opentracing::expected Set(opentracing::string_view key, + opentracing::string_view value) const override + { + text_map[key] = value; + return {}; + } + + opentracing::expected LookupKey( + opentracing::string_view key) const override + { + if (!supports_lookup) + { + return opentracing::make_unexpected(opentracing::lookup_key_not_supported_error); + } + auto iter = text_map.find(key); + if (iter != text_map.end()) + { + return opentracing::string_view{iter->second}; + } + else + { + return opentracing::make_unexpected(opentracing::key_not_found_error); + } + } + + opentracing::expected ForeachKey( + std::function(opentracing::string_view key, + opentracing::string_view value)> f) const override + { + ++foreach_key_call_count; + for (const auto &key_value : text_map) + { + auto result = f(key_value.first, key_value.second); + if (!result) + return result; + } + return {}; + } + + bool supports_lookup = false; + mutable int foreach_key_call_count = 0; + std::unordered_map &text_map; +}; + +struct HTTPHeadersCarrier : opentracing::HTTPHeadersReader, opentracing::HTTPHeadersWriter +{ + HTTPHeadersCarrier(std::unordered_map &text_map_) : text_map(text_map_) + {} + + opentracing::expected Set(opentracing::string_view key, + opentracing::string_view value) const override + { + text_map[key] = value; + return {}; + } + + opentracing::expected ForeachKey( + std::function(opentracing::string_view key, + opentracing::string_view value)> f) const override + { + for (const auto &key_value : text_map) + { + auto result = f(key_value.first, key_value.second); + if (!result) + return result; + } + return {}; + } + + std::unordered_map &text_map; +}; diff --git a/opentracing-shim/test/shim_utils_test.cc b/opentracing-shim/test/shim_utils_test.cc new file mode 100644 index 0000000000..aa2316f2d2 --- /dev/null +++ b/opentracing-shim/test/shim_utils_test.cc @@ -0,0 +1,281 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "shim_mocks.h" + +#include "opentelemetry/opentracingshim/shim_utils.h" + +#include "opentracing/tracer.h" + +#include + +namespace trace_api = opentelemetry::trace; +namespace baggage = opentelemetry::baggage; +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; +namespace shim = opentelemetry::opentracingshim; + +TEST(ShimUtilsTest, IsBaggageEmpty) +{ + auto none = nostd::shared_ptr(nullptr); + ASSERT_TRUE(shim::utils::isBaggageEmpty(none)); + + auto empty = nostd::shared_ptr(new baggage::Baggage({})); + ASSERT_TRUE(shim::utils::isBaggageEmpty(empty)); + + std::map list{{"foo", "bar"}}; + auto non_empty = nostd::shared_ptr(new baggage::Baggage(list)); + ASSERT_FALSE(shim::utils::isBaggageEmpty(non_empty)); +} + +TEST(ShimUtilsTest, StringFromValue) +{ + ASSERT_EQ(shim::utils::stringFromValue(true), "true"); + ASSERT_EQ(shim::utils::stringFromValue(false), "false"); + ASSERT_EQ(shim::utils::stringFromValue(1234.567890), "1234.567890"); + ASSERT_EQ(shim::utils::stringFromValue(42l), "42"); + ASSERT_EQ(shim::utils::stringFromValue(55ul), "55"); + ASSERT_EQ(shim::utils::stringFromValue(std::string{"a string"}), "a string"); + ASSERT_EQ(shim::utils::stringFromValue(opentracing::string_view{"a string view"}), + "a string view"); + ASSERT_EQ(shim::utils::stringFromValue(nullptr), ""); + ASSERT_EQ(shim::utils::stringFromValue("a char ptr"), "a char ptr"); + + opentracing::util::recursive_wrapper values{}; + ASSERT_EQ(shim::utils::stringFromValue(values.get()), ""); + + opentracing::util::recursive_wrapper dict{}; + ASSERT_EQ(shim::utils::stringFromValue(dict.get()), ""); +} + +TEST(ShimUtilsTest, AttributeFromValue) +{ + auto value = shim::utils::attributeFromValue(true); + ASSERT_EQ(value.index(), common::AttributeType::kTypeBool); + ASSERT_TRUE(nostd::get(value)); + + value = shim::utils::attributeFromValue(false); + ASSERT_EQ(value.index(), common::AttributeType::kTypeBool); + ASSERT_FALSE(nostd::get(value)); + + value = shim::utils::attributeFromValue(1234.567890); + ASSERT_EQ(value.index(), common::AttributeType::kTypeDouble); + ASSERT_EQ(nostd::get(value), 1234.567890); + + value = shim::utils::attributeFromValue(42l); + ASSERT_EQ(value.index(), common::AttributeType::kTypeInt64); + ASSERT_EQ(nostd::get(value), 42l); + + value = shim::utils::attributeFromValue(55ul); + ASSERT_EQ(value.index(), common::AttributeType::kTypeUInt64); + ASSERT_EQ(nostd::get(value), 55ul); + + opentracing::Value str{std::string{"a string"}}; + value = shim::utils::attributeFromValue(str); + ASSERT_EQ(value.index(), common::AttributeType::kTypeString); + ASSERT_EQ(nostd::get(value), nostd::string_view{"a string"}); + + value = shim::utils::attributeFromValue(opentracing::string_view{"a string view"}); + ASSERT_EQ(value.index(), common::AttributeType::kTypeString); + ASSERT_EQ(nostd::get(value), nostd::string_view{"a string view"}); + + value = shim::utils::attributeFromValue(nullptr); + ASSERT_EQ(value.index(), common::AttributeType::kTypeString); + ASSERT_EQ(nostd::get(value), nostd::string_view{}); + + value = shim::utils::attributeFromValue("a char ptr"); + ASSERT_EQ(value.index(), common::AttributeType::kTypeCString); + ASSERT_EQ(nostd::get(value), "a char ptr"); + + opentracing::util::recursive_wrapper values{}; + value = shim::utils::attributeFromValue(values.get()); + ASSERT_EQ(value.index(), common::AttributeType::kTypeString); + ASSERT_EQ(nostd::get(value), nostd::string_view{}); + + opentracing::util::recursive_wrapper dict{}; + value = shim::utils::attributeFromValue(dict.get()); + ASSERT_EQ(value.index(), common::AttributeType::kTypeString); + ASSERT_EQ(nostd::get(value), nostd::string_view{}); +} + +TEST(ShimUtilsTest, MakeOptionsShim_EmptyRefs) +{ + opentracing::StartSpanOptions options; + options.start_system_timestamp = opentracing::SystemTime::time_point::clock::now(); + options.start_steady_timestamp = opentracing::SteadyTime::time_point::clock::now(); + + auto options_shim = shim::utils::makeOptionsShim(options); + ASSERT_EQ(options_shim.start_system_time, + common::SystemTimestamp{options.start_system_timestamp}); + ASSERT_EQ(options_shim.start_steady_time, + common::SteadyTimestamp{options.start_steady_timestamp}); + ASSERT_EQ(nostd::get(options_shim.parent), + trace_api::SpanContext::GetInvalid()); +} + +TEST(ShimUtilsTest, MakeOptionsShim_InvalidSpanContext) +{ + opentracing::StartSpanOptions options; + options.start_system_timestamp = opentracing::SystemTime::time_point::clock::now(); + options.start_steady_timestamp = opentracing::SteadyTime::time_point::clock::now(); + options.references = {{opentracing::SpanReferenceType::FollowsFromRef, nullptr}}; + + auto options_shim = shim::utils::makeOptionsShim(options); + ASSERT_EQ(options_shim.start_system_time, + common::SystemTimestamp{options.start_system_timestamp}); + ASSERT_EQ(options_shim.start_steady_time, + common::SteadyTimestamp{options.start_steady_timestamp}); + ASSERT_EQ(nostd::get(options_shim.parent), + trace_api::SpanContext::GetInvalid()); +} + +TEST(ShimUtilsTest, MakeOptionsShim_FirstChildOf) +{ + auto span_context_shim = nostd::shared_ptr(new shim::SpanContextShim( + trace_api::SpanContext::GetInvalid(), baggage::Baggage::GetDefault())); + auto span_context = static_cast(span_context_shim.get()); + + opentracing::StartSpanOptions options; + options.start_system_timestamp = opentracing::SystemTime::time_point::clock::now(); + options.start_steady_timestamp = opentracing::SteadyTime::time_point::clock::now(); + options.references = {{opentracing::SpanReferenceType::FollowsFromRef, nullptr}, + {opentracing::SpanReferenceType::ChildOfRef, span_context}, + {opentracing::SpanReferenceType::ChildOfRef, nullptr}}; + + auto options_shim = shim::utils::makeOptionsShim(options); + ASSERT_EQ(options_shim.start_system_time, + common::SystemTimestamp{options.start_system_timestamp}); + ASSERT_EQ(options_shim.start_steady_time, + common::SteadyTimestamp{options.start_steady_timestamp}); + ASSERT_EQ(nostd::get(options_shim.parent), span_context_shim->context()); +} + +TEST(ShimUtilsTest, MakeOptionsShim_FirstInList) +{ + auto span_context_shim = nostd::shared_ptr(new shim::SpanContextShim( + trace_api::SpanContext::GetInvalid(), baggage::Baggage::GetDefault())); + auto span_context = static_cast(span_context_shim.get()); + + opentracing::StartSpanOptions options; + options.start_system_timestamp = opentracing::SystemTime::time_point::clock::now(); + options.start_steady_timestamp = opentracing::SteadyTime::time_point::clock::now(); + options.references = {{opentracing::SpanReferenceType::FollowsFromRef, span_context}, + {opentracing::SpanReferenceType::FollowsFromRef, nullptr}}; + + auto options_shim = shim::utils::makeOptionsShim(options); + ASSERT_EQ(options_shim.start_system_time, + common::SystemTimestamp{options.start_system_timestamp}); + ASSERT_EQ(options_shim.start_steady_time, + common::SteadyTimestamp{options.start_steady_timestamp}); + ASSERT_EQ(nostd::get(options_shim.parent), span_context_shim->context()); +} + +TEST(ShimUtilsTest, MakeIterableLinks) +{ + auto span_context_shim1 = nostd::shared_ptr(new shim::SpanContextShim( + trace_api::SpanContext::GetInvalid(), baggage::Baggage::GetDefault())); + auto span_context1 = static_cast(span_context_shim1.get()); + auto span_context_shim2 = nostd::shared_ptr(new shim::SpanContextShim( + trace_api::SpanContext::GetInvalid(), baggage::Baggage::GetDefault())); + auto span_context2 = static_cast(span_context_shim2.get()); + + opentracing::StartSpanOptions options; + auto empty = shim::utils::makeIterableLinks(options); + ASSERT_EQ(empty.size(), 0); + + options.references = {{opentracing::SpanReferenceType::FollowsFromRef, nullptr}, + {opentracing::SpanReferenceType::FollowsFromRef, span_context1}, + {opentracing::SpanReferenceType::ChildOfRef, span_context2}}; + auto full = shim::utils::makeIterableLinks(options); + ASSERT_EQ(full.size(), 3); + + std::vector> links; + full.ForEachKeyValue([&links](trace_api::SpanContext ctx, const common::KeyValueIterable &it) { + it.ForEachKeyValue([&links, &ctx](nostd::string_view key, common::AttributeValue value) { + links.emplace_back(ctx, key, value); + return false; + }); + return true; + }); + ASSERT_EQ(links.size(), 2); + + trace_api::SpanContext ctx = trace_api::SpanContext::GetInvalid(); + nostd::string_view key; + common::AttributeValue value; + std::tie(ctx, key, value) = links[0]; + ASSERT_EQ(ctx, span_context_shim1->context()); + ASSERT_EQ(key, "opentracing.ref_type"); + ASSERT_EQ(nostd::get(value), "follows_from"); + std::tie(ctx, key, value) = links[1]; + ASSERT_EQ(ctx, span_context_shim2->context()); + ASSERT_EQ(key, "opentracing.ref_type"); + ASSERT_EQ(nostd::get(value), "child_of"); +} + +TEST(ShimUtilsTest, MakeBaggage_EmptyRefs) +{ + auto baggage = baggage::Baggage::GetDefault()->Set("foo", "bar"); + std::string value; + ASSERT_TRUE(baggage->GetValue("foo", value)); + ASSERT_EQ(value, "bar"); + + auto context = context::RuntimeContext::GetCurrent(); + auto new_context = baggage::SetBaggage(context, baggage); + auto token = context::RuntimeContext::Attach(new_context); + ASSERT_EQ(context::RuntimeContext::GetCurrent(), new_context); + + opentracing::StartSpanOptions options; + auto new_baggage = shim::utils::makeBaggage(options); + ASSERT_TRUE(new_baggage->GetValue("foo", value)); + ASSERT_EQ(value, "bar"); +} + +TEST(ShimUtilsTest, MakeBaggage_NonEmptyRefs) +{ + auto span_context_shim1 = nostd::shared_ptr(new shim::SpanContextShim( + trace_api::SpanContext::GetInvalid(), + baggage::Baggage::GetDefault()->Set("test", "foo")->Set("test1", "hello"))); + auto span_context1 = static_cast(span_context_shim1.get()); + auto span_context_shim2 = nostd::shared_ptr(new shim::SpanContextShim( + trace_api::SpanContext::GetInvalid(), + baggage::Baggage::GetDefault()->Set("test", "bar")->Set("test2", "world"))); + auto span_context2 = static_cast(span_context_shim2.get()); + + opentracing::StartSpanOptions options; + options.references = {{opentracing::SpanReferenceType::FollowsFromRef, span_context1}, + {opentracing::SpanReferenceType::ChildOfRef, span_context2}}; + + auto baggage = shim::utils::makeBaggage(options); + std::string value; + ASSERT_TRUE(baggage->GetValue("test", value)); + ASSERT_EQ(value, "foo"); + ASSERT_TRUE(baggage->GetValue("test1", value)); + ASSERT_EQ(value, "hello"); + ASSERT_TRUE(baggage->GetValue("test2", value)); + ASSERT_EQ(value, "world"); +} + +TEST(ShimUtilsTest, MakeIterableTags) +{ + opentracing::StartSpanOptions options; + auto empty = shim::utils::makeIterableTags(options); + ASSERT_EQ(empty.size(), 0); + + options.tags = {{"foo", 42.0}, {"bar", true}, {"baz", "test"}}; + auto full = shim::utils::makeIterableTags(options); + ASSERT_EQ(full.size(), 3); + + std::vector> attributes; + full.ForEachKeyValue([&attributes](nostd::string_view key, common::AttributeValue value) { + attributes.push_back({key, value}); + return true; + }); + ASSERT_EQ(attributes[0].first, "foo"); + ASSERT_EQ(nostd::get(attributes[0].second), 42.0); + ASSERT_EQ(attributes[1].first, "bar"); + ASSERT_TRUE(nostd::get(attributes[1].second)); + ASSERT_EQ(attributes[2].first, "baz"); + ASSERT_STREQ(nostd::get(attributes[2].second), "test"); +} diff --git a/opentracing-shim/test/span_context_shim_test.cc b/opentracing-shim/test/span_context_shim_test.cc new file mode 100644 index 0000000000..93978f079a --- /dev/null +++ b/opentracing-shim/test/span_context_shim_test.cc @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "opentelemetry/opentracingshim/span_context_shim.h" + +#include "opentelemetry/baggage/baggage.h" +#include "opentelemetry/trace/span_context.h" + +#include "opentracing/noop.h" + +#include + +namespace trace_api = opentelemetry::trace; +namespace baggage = opentelemetry::baggage; +namespace nostd = opentelemetry::nostd; +namespace shim = opentelemetry::opentracingshim; + +class SpanContextShimTest : public testing::Test +{ +public: + nostd::unique_ptr span_context_shim; + +protected: + virtual void SetUp() override + { + auto span_context = trace_api::SpanContext::GetInvalid(); + auto baggage = baggage::Baggage::GetDefault()->Set("foo", "bar"); + span_context_shim = + nostd::unique_ptr(new shim::SpanContextShim(span_context, baggage)); + } + + virtual void TearDown() override { span_context_shim.reset(); } +}; + +TEST_F(SpanContextShimTest, ExtractFrom) +{ + ASSERT_TRUE(shim::SpanContextShim::extractFrom(nullptr) == nullptr); + + auto tracer = opentracing::MakeNoopTracer(); + auto span = tracer->StartSpanWithOptions("operation", {}); + ASSERT_TRUE(shim::SpanContextShim::extractFrom(&span->context()) == nullptr); + + auto span_context_shim = nostd::shared_ptr(new shim::SpanContextShim( + trace_api::SpanContext::GetInvalid(), baggage::Baggage::GetDefault())); + ASSERT_TRUE(shim::SpanContextShim::extractFrom(span_context_shim.get()) != nullptr); +} + +TEST_F(SpanContextShimTest, BaggageItem) +{ + std::string value; + ASSERT_TRUE(span_context_shim->BaggageItem("foo", value)); + ASSERT_EQ(value, "bar"); + ASSERT_FALSE(span_context_shim->BaggageItem("", value)); +} + +TEST_F(SpanContextShimTest, NewWithKeyValue) +{ + auto new_span_context_shim = span_context_shim->newWithKeyValue("test", "this"); + ASSERT_NE(span_context_shim.get(), &new_span_context_shim); + ASSERT_EQ(span_context_shim->context(), new_span_context_shim.context()); + ASSERT_EQ(span_context_shim->context().IsValid(), new_span_context_shim.context().IsValid()); + ASSERT_EQ(span_context_shim->context().IsRemote(), new_span_context_shim.context().IsRemote()); + + std::string value; + ASSERT_TRUE(new_span_context_shim.BaggageItem("foo", value)); + ASSERT_EQ(value, "bar"); + ASSERT_TRUE(new_span_context_shim.BaggageItem("test", value)); + ASSERT_EQ(value, "this"); +} + +TEST_F(SpanContextShimTest, ForeachBaggageItem) +{ + std::initializer_list> list{ + {"foo", "bar"}, {"bar", "baz"}, {"baz", "foo"}}; + nostd::shared_ptr baggage(new baggage::Baggage(list)); + shim::SpanContextShim new_span_context_shim(span_context_shim->context(), baggage); + + std::vector concatenated; + new_span_context_shim.ForeachBaggageItem( + [&concatenated](const std::string &key, const std::string &value) { + concatenated.emplace_back(key + ":" + value); + return true; + }); + + ASSERT_EQ(concatenated.size(), 3); + ASSERT_EQ(concatenated[0], "foo:bar"); + ASSERT_EQ(concatenated[1], "bar:baz"); + ASSERT_EQ(concatenated[2], "baz:foo"); +} + +TEST_F(SpanContextShimTest, Clone) +{ + auto new_span_context = span_context_shim->Clone(); + auto new_span_context_shim = static_cast(new_span_context.get()); + ASSERT_TRUE(new_span_context_shim != nullptr); + ASSERT_NE(span_context_shim.get(), new_span_context_shim); + ASSERT_EQ(span_context_shim->context(), new_span_context_shim->context()); + ASSERT_EQ(span_context_shim->context().IsValid(), new_span_context_shim->context().IsValid()); + ASSERT_EQ(span_context_shim->context().IsRemote(), new_span_context_shim->context().IsRemote()); + + std::string value; + std::string new_value; + ASSERT_TRUE(span_context_shim->BaggageItem("foo", value)); + ASSERT_TRUE(new_span_context_shim->BaggageItem("foo", new_value)); + ASSERT_EQ(value, new_value); +} diff --git a/opentracing-shim/test/span_shim_test.cc b/opentracing-shim/test/span_shim_test.cc new file mode 100644 index 0000000000..d73f3e0061 --- /dev/null +++ b/opentracing-shim/test/span_shim_test.cc @@ -0,0 +1,225 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "shim_mocks.h" + +#include "opentelemetry/opentracingshim/span_shim.h" +#include "opentelemetry/opentracingshim/tracer_shim.h" + +#include + +namespace trace_api = opentelemetry::trace; +namespace baggage = opentelemetry::baggage; +namespace nostd = opentelemetry::nostd; +namespace shim = opentelemetry::opentracingshim; + +class SpanShimTest : public testing::Test +{ +public: + nostd::unique_ptr span_shim; + MockSpan *mock_span; + +protected: + virtual void SetUp() override + { + mock_span = new MockSpan(); + auto span = nostd::shared_ptr(mock_span); + auto tracer = shim::TracerShim::createTracerShim(); + auto tracer_shim = static_cast(tracer.get()); + auto baggage = baggage::Baggage::GetDefault()->Set("baggage", "item"); + span_shim = nostd::unique_ptr(new shim::SpanShim(*tracer_shim, span, baggage)); + } + + virtual void TearDown() override { span_shim.reset(); } +}; + +TEST_F(SpanShimTest, HandleError) +{ + span_shim->handleError(true); + ASSERT_EQ(mock_span->status_.first, trace_api::StatusCode::kError); + span_shim->handleError("true"); + ASSERT_EQ(mock_span->status_.first, trace_api::StatusCode::kError); + span_shim->handleError(false); + ASSERT_EQ(mock_span->status_.first, trace_api::StatusCode::kOk); + span_shim->handleError("false"); + ASSERT_EQ(mock_span->status_.first, trace_api::StatusCode::kOk); + span_shim->handleError(nullptr); + ASSERT_EQ(mock_span->status_.first, trace_api::StatusCode::kUnset); + span_shim->handleError("unknown"); + ASSERT_EQ(mock_span->status_.first, trace_api::StatusCode::kUnset); + span_shim->handleError(42); + ASSERT_EQ(mock_span->status_.first, trace_api::StatusCode::kUnset); +} + +TEST_F(SpanShimTest, FinishWithOptions) +{ + opentracing::FinishSpanOptions options; + options.finish_steady_timestamp = opentracing::SteadyTime::clock::now(); + ASSERT_NE(mock_span->options_.end_steady_time, options.finish_steady_timestamp); + span_shim->FinishWithOptions(options); + ASSERT_EQ(mock_span->options_.end_steady_time, options.finish_steady_timestamp); +} + +TEST_F(SpanShimTest, SetOperationName) +{ + ASSERT_NE(mock_span->name_, "foo"); + span_shim->SetOperationName("foo"); + ASSERT_EQ(mock_span->name_, "foo"); +} + +TEST_F(SpanShimTest, SetTag_Normal) +{ + ASSERT_NE(mock_span->attribute_.first, "foo"); + span_shim->SetTag("foo", "bar"); + ASSERT_EQ(mock_span->attribute_.first, "foo"); + ASSERT_STREQ(nostd::get(mock_span->attribute_.second), "bar"); +} + +TEST_F(SpanShimTest, SetTag_Error) +{ + ASSERT_NE(mock_span->attribute_.first, "error"); + span_shim->SetTag("error", true); + ASSERT_NE(mock_span->attribute_.first, "error"); + span_shim->SetTag("error", "false"); + ASSERT_NE(mock_span->attribute_.first, "error"); + span_shim->SetTag("error", 42); + ASSERT_NE(mock_span->attribute_.first, "error"); + span_shim->SetTag("error", nullptr); + ASSERT_NE(mock_span->attribute_.first, "error"); +} + +TEST_F(SpanShimTest, BaggageItem) +{ + ASSERT_EQ(span_shim->BaggageItem({}), ""); + ASSERT_EQ(span_shim->BaggageItem(""), ""); + ASSERT_EQ(span_shim->BaggageItem("invalid"), ""); + ASSERT_EQ(span_shim->BaggageItem("baggage"), "item"); +} + +TEST_F(SpanShimTest, SetBaggageItem) +{ + span_shim->SetBaggageItem("new", "entry"); + ASSERT_EQ(span_shim->BaggageItem("new"), "entry"); + ASSERT_EQ(span_shim->BaggageItem("baggage"), "item"); + span_shim->SetBaggageItem("empty", ""); + ASSERT_EQ(span_shim->BaggageItem("empty"), ""); + span_shim->SetBaggageItem("no value", {}); + ASSERT_EQ(span_shim->BaggageItem("no value"), ""); +} + +TEST_F(SpanShimTest, SetBaggageItem_MultiThreaded) +{ + auto span = nostd::shared_ptr(new MockSpan()); + auto tracer = shim::TracerShim::createTracerShim(); + auto tracer_shim = static_cast(tracer.get()); + auto baggage = baggage::Baggage::GetDefault(); + shim::SpanShim span_shim(*tracer_shim, span, baggage); + + std::vector threads; + std::vector keys; + std::vector values; + int thread_count = 100; + + for (int index = 0; index < thread_count; ++index) + { + keys.emplace_back("key-" + std::to_string(index)); + values.emplace_back("value-" + std::to_string(index)); + threads.emplace_back( + std::bind(&shim::SpanShim::SetBaggageItem, &span_shim, keys[index], values[index])); + } + + for (auto &thread : threads) + { + thread.join(); + } + + for (int index = 0; index < thread_count; ++index) + { + ASSERT_EQ(span_shim.BaggageItem(keys[index]), values[index]); + } +} + +TEST_F(SpanShimTest, Log_NoEvent) +{ + std::string name; + common::SystemTimestamp timestamp; + std::unordered_map attributes; + + span_shim->Log({{"test", 42}}); + std::tie(name, timestamp, attributes) = mock_span->event_; + + ASSERT_EQ(name, "log"); + ASSERT_EQ(timestamp, common::SystemTimestamp{}); + ASSERT_EQ(attributes.size(), 1); + ASSERT_EQ(nostd::get(attributes["test"]), 42); +} + +TEST_F(SpanShimTest, Log_NoEvent_Timestamp) +{ + std::string name; + common::SystemTimestamp timestamp; + std::unordered_map attributes; + + auto logtime = opentracing::SystemTime::time_point::clock::now(); + span_shim->Log(logtime, {{"foo", "bar"}}); + std::tie(name, timestamp, attributes) = mock_span->event_; + + ASSERT_EQ(name, "log"); + ASSERT_EQ(timestamp, common::SystemTimestamp{logtime}); + ASSERT_EQ(attributes.size(), 1); + ASSERT_STREQ(nostd::get(attributes["foo"]), "bar"); +} + +TEST_F(SpanShimTest, Log_Event) +{ + std::string name; + common::SystemTimestamp timestamp; + std::unordered_map attributes; + + auto logtime = opentracing::SystemTime::time_point::clock::now(); + std::initializer_list> fields{ + {"event", "normal"}, + {"foo", opentracing::string_view{"bar"}}, + {"error.kind", 42}, + {"message", "hello"}, + {"stack", "overflow"}}; + span_shim->Log(logtime, fields); + std::tie(name, timestamp, attributes) = mock_span->event_; + + ASSERT_EQ(name, "normal"); + ASSERT_EQ(timestamp, common::SystemTimestamp{logtime}); + ASSERT_EQ(attributes.size(), 5); + ASSERT_STREQ(nostd::get(attributes["event"]), "normal"); + ASSERT_EQ(nostd::get(attributes["foo"]), nostd::string_view{"bar"}); + ASSERT_EQ(nostd::get(attributes["error.kind"]), 42); + ASSERT_STREQ(nostd::get(attributes["message"]), "hello"); + ASSERT_STREQ(nostd::get(attributes["stack"]), "overflow"); +} + +TEST_F(SpanShimTest, Log_Error) +{ + std::string name; + common::SystemTimestamp timestamp; + std::unordered_map attributes; + + auto logtime = opentracing::SystemTime::time_point::clock::now(); + std::vector> fields{ + {"event", "error"}, + {"foo", opentracing::string_view{"bar"}}, + {"error.kind", 42}, + {"message", "hello"}, + {"stack", "overflow"}}; + span_shim->Log(logtime, fields); + std::tie(name, timestamp, attributes) = mock_span->event_; + + ASSERT_EQ(name, "exception"); + ASSERT_EQ(timestamp, common::SystemTimestamp{logtime}); + ASSERT_EQ(attributes.size(), 5); + ASSERT_STREQ(nostd::get(attributes["event"]), "error"); + ASSERT_EQ(nostd::get(attributes["foo"]), nostd::string_view{"bar"}); + ASSERT_EQ(nostd::get(attributes["exception.type"]), 42); + ASSERT_STREQ(nostd::get(attributes["exception.message"]), "hello"); + ASSERT_STREQ(nostd::get(attributes["exception.stacktrace"]), "overflow"); +} diff --git a/opentracing-shim/test/tracer_shim_test.cc b/opentracing-shim/test/tracer_shim_test.cc new file mode 100644 index 0000000000..7a06175323 --- /dev/null +++ b/opentracing-shim/test/tracer_shim_test.cc @@ -0,0 +1,219 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "shim_mocks.h" + +#include "opentelemetry/opentracingshim/shim_utils.h" +#include "opentelemetry/opentracingshim/span_context_shim.h" +#include "opentelemetry/opentracingshim/span_shim.h" +#include "opentelemetry/opentracingshim/tracer_shim.h" + +#include "opentracing/noop.h" + +#include + +namespace trace_api = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace context = opentelemetry::context; +namespace baggage = opentelemetry::baggage; +namespace shim = opentelemetry::opentracingshim; + +class TracerShimTest : public testing::Test +{ +public: + std::shared_ptr tracer_shim; + MockPropagator *text_map_format; + MockPropagator *http_headers_format; + +protected: + virtual void SetUp() override + { + using context::propagation::TextMapPropagator; + + text_map_format = new MockPropagator(); + http_headers_format = new MockPropagator(); + + tracer_shim = shim::TracerShim::createTracerShim( + trace_api::Provider::GetTracerProvider(), + {.text_map = nostd::shared_ptr(text_map_format), + .http_headers = nostd::shared_ptr(http_headers_format)}); + } + + virtual void TearDown() override { tracer_shim.reset(); } +}; + +TEST_F(TracerShimTest, TracerName) +{ + auto mock_provider_ptr = new MockTracerProvider(); + nostd::shared_ptr provider(mock_provider_ptr); + ASSERT_NE(shim::TracerShim::createTracerShim(provider), nullptr); + ASSERT_EQ(mock_provider_ptr->library_name_, "opentracing-shim"); +} + +TEST_F(TracerShimTest, SpanReferenceToCreatingTracer) +{ + auto span_shim = tracer_shim->StartSpan("a"); + ASSERT_NE(span_shim, nullptr); + ASSERT_EQ(&span_shim->tracer(), tracer_shim.get()); +} + +TEST_F(TracerShimTest, SpanParentChildRelationship) +{ + auto span_shim1 = tracer_shim->StartSpan("a"); + auto span_shim2 = tracer_shim->StartSpan("b", {opentracing::ChildOf(&span_shim1->context())}); + ASSERT_NE(span_shim1, nullptr); + ASSERT_NE(span_shim2, nullptr); + ASSERT_NE(span_shim1, span_shim2); + ASSERT_EQ(span_shim1->context().ToSpanID(), span_shim2->context().ToSpanID()); + ASSERT_EQ(span_shim1->context().ToTraceID(), span_shim2->context().ToTraceID()); + + auto span_context_shim1 = static_cast(&span_shim1->context()); + auto span_context_shim2 = static_cast(&span_shim2->context()); + ASSERT_TRUE(span_context_shim1 != nullptr); + ASSERT_TRUE(span_context_shim2 != nullptr); + ASSERT_EQ(span_context_shim1->context(), span_context_shim2->context()); +} + +TEST_F(TracerShimTest, TracerGloballyRegistered) +{ + ASSERT_FALSE(opentracing::Tracer::IsGlobalTracerRegistered()); + ASSERT_NE(opentracing::Tracer::InitGlobal(tracer_shim), nullptr); + ASSERT_TRUE(opentracing::Tracer::IsGlobalTracerRegistered()); +} + +TEST_F(TracerShimTest, Close) +{ + tracer_shim->Close(); + auto span_shim = tracer_shim->StartSpan("a"); + ASSERT_TRUE(span_shim == nullptr); +} + +TEST_F(TracerShimTest, SpanHandleErrorTagAtCreation) +{ + auto mock_provider_ptr = new MockTracerProvider(); + nostd::shared_ptr provider(mock_provider_ptr); + auto tracer_shim = shim::TracerShim::createTracerShim(provider); + auto span_shim = tracer_shim->StartSpanWithOptions("test", {}); + ASSERT_TRUE(span_shim != nullptr); + ASSERT_TRUE(mock_provider_ptr->tracer_ != nullptr); + ASSERT_TRUE(mock_provider_ptr->tracer_->span_ != nullptr); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->name_, "test"); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->status_.first, trace_api::StatusCode::kUnset); + + auto span_shim1 = tracer_shim->StartSpanWithOptions("test1", {.tags = {{"event", "normal"}}}); + ASSERT_TRUE(span_shim1 != nullptr); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->name_, "test1"); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->status_.first, trace_api::StatusCode::kUnset); + + auto span_shim2 = tracer_shim->StartSpanWithOptions("test2", {.tags = {{"error", true}}}); + ASSERT_TRUE(span_shim2 != nullptr); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->name_, "test2"); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->status_.first, trace_api::StatusCode::kError); + + auto span_shim3 = tracer_shim->StartSpanWithOptions("test3", {.tags = {{"error", "false"}}}); + ASSERT_TRUE(span_shim3 != nullptr); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->name_, "test3"); + ASSERT_EQ(mock_provider_ptr->tracer_->span_->status_.first, trace_api::StatusCode::kOk); +} + +TEST_F(TracerShimTest, InjectInvalidCarrier) +{ + auto span_shim = tracer_shim->StartSpan("a"); + auto result = tracer_shim->Inject(span_shim->context(), std::cout); + ASSERT_TRUE(opentracing::are_errors_equal(result.error(), opentracing::invalid_carrier_error)); +} + +TEST_F(TracerShimTest, InjectNullContext) +{ + std::unordered_map text_map; + auto noop_tracer = opentracing::MakeNoopTracer(); + auto span = noop_tracer->StartSpan("a"); + auto result = tracer_shim->Inject(span->context(), TextMapCarrier{text_map}); + ASSERT_TRUE( + opentracing::are_errors_equal(result.error(), opentracing::invalid_span_context_error)); + ASSERT_TRUE(text_map.empty()); +} + +TEST_F(TracerShimTest, InjectTextMap) +{ + ASSERT_FALSE(text_map_format->is_injected); + ASSERT_FALSE(http_headers_format->is_injected); + + std::unordered_map text_map; + auto span_shim = tracer_shim->StartSpan("a"); + tracer_shim->Inject(span_shim->context(), TextMapCarrier{text_map}); + ASSERT_TRUE(text_map_format->is_injected); + ASSERT_FALSE(http_headers_format->is_injected); +} + +TEST_F(TracerShimTest, InjectHttpsHeaders) +{ + ASSERT_FALSE(text_map_format->is_injected); + ASSERT_FALSE(http_headers_format->is_injected); + + std::unordered_map text_map; + auto span_shim = tracer_shim->StartSpan("a"); + tracer_shim->Inject(span_shim->context(), HTTPHeadersCarrier{text_map}); + ASSERT_FALSE(text_map_format->is_injected); + ASSERT_TRUE(http_headers_format->is_injected); +} + +TEST_F(TracerShimTest, ExtractInvalidCarrier) +{ + auto result = tracer_shim->Extract(std::cin); + ASSERT_TRUE(opentracing::are_errors_equal(result.error(), opentracing::invalid_carrier_error)); +} + +TEST_F(TracerShimTest, ExtractNullContext) +{ + std::unordered_map text_map; + auto result = tracer_shim->Extract(TextMapCarrier{text_map}); + ASSERT_EQ(result.value(), nullptr); +} + +TEST_F(TracerShimTest, ExtractTextMap) +{ + ASSERT_FALSE(text_map_format->is_extracted); + ASSERT_FALSE(http_headers_format->is_extracted); + + std::unordered_map text_map; + auto result = tracer_shim->Extract(TextMapCarrier{text_map}); + ASSERT_EQ(result.value(), nullptr); + ASSERT_TRUE(text_map_format->is_extracted); + ASSERT_FALSE(http_headers_format->is_extracted); +} + +TEST_F(TracerShimTest, ExtractHttpsHeaders) +{ + ASSERT_FALSE(text_map_format->is_extracted); + ASSERT_FALSE(http_headers_format->is_extracted); + + std::unordered_map text_map; + auto result = tracer_shim->Extract(HTTPHeadersCarrier{text_map}); + ASSERT_EQ(result.value(), nullptr); + ASSERT_FALSE(text_map_format->is_extracted); + ASSERT_TRUE(http_headers_format->is_extracted); +} + +TEST_F(TracerShimTest, ExtractOnlyBaggage) +{ + std::unordered_map text_map; + auto span_shim = tracer_shim->StartSpan("a"); + span_shim->SetBaggageItem("foo", "bar"); + ASSERT_EQ(span_shim->BaggageItem("foo"), "bar"); + + tracer_shim->Inject(span_shim->context(), TextMapCarrier{text_map}); + auto span_context = tracer_shim->Extract(TextMapCarrier{text_map}); + ASSERT_TRUE(span_context.value() != nullptr); + + auto span_context_shim = static_cast(span_context.value().get()); + ASSERT_TRUE(span_context_shim != nullptr); + ASSERT_FALSE(span_context_shim->context().IsValid()); + ASSERT_FALSE(shim::utils::isBaggageEmpty(span_context_shim->baggage())); + + std::string value; + ASSERT_TRUE(span_context_shim->BaggageItem("foo", value)); + ASSERT_EQ(value, "bar"); +} diff --git a/third_party/opentracing-cpp b/third_party/opentracing-cpp new file mode 160000 index 0000000000..06b57f48de --- /dev/null +++ b/third_party/opentracing-cpp @@ -0,0 +1 @@ +Subproject commit 06b57f48ded1fa3bdd3d4346f6ef29e40e08eaf5 diff --git a/third_party_release b/third_party_release index eaf7ac519f..7290896c2d 100644 --- a/third_party_release +++ b/third_party_release @@ -21,5 +21,6 @@ googletest=release-1.12.1 ms-gsl=v3.1.0-67-g6f45293 nlohmann-json=v3.10.5 opentelemetry-proto=v0.19.0 +opentracing-cpp=v1.6.0 prometheus-cpp=v1.1.0 vcpkg=2022.08.15