From 45e0c4b4f55d7e848aa48ea120816459afd3aaf6 Mon Sep 17 00:00:00 2001 From: Alex Emirov Date: Sat, 31 Dec 2022 22:58:27 -0500 Subject: [PATCH] First attempt --- .gitmodules | 5 + CMakeLists.txt | 7 + opentracing-shim/CMakeLists.txt | 40 +++ opentracing-shim/include/shim_utils.h | 134 +++++++++ opentracing-shim/include/span_context_shim.h | 51 ++++ opentracing-shim/include/span_shim.h | 56 ++++ opentracing-shim/include/tracer_shim.h | 103 +++++++ opentracing-shim/src/CMakeLists.txt | 34 +++ opentracing-shim/src/span_context_shim.cc | 37 +++ opentracing-shim/src/span_shim.cc | 133 +++++++++ opentracing-shim/src/tracer_shim.cc | 273 +++++++++++++++++++ opentracing-shim/test/CMakeLists.txt | 9 + opentracing-shim/test/shim_test.cc | 47 ++++ opentracing-shim/test/tracer_test.cpp | 43 +++ 14 files changed, 972 insertions(+) create mode 100644 opentracing-shim/CMakeLists.txt create mode 100644 opentracing-shim/include/shim_utils.h create mode 100644 opentracing-shim/include/span_context_shim.h create mode 100644 opentracing-shim/include/span_shim.h create mode 100644 opentracing-shim/include/tracer_shim.h create mode 100644 opentracing-shim/src/CMakeLists.txt create mode 100644 opentracing-shim/src/span_context_shim.cc create mode 100644 opentracing-shim/src/span_shim.cc create mode 100644 opentracing-shim/src/tracer_shim.cc create mode 100644 opentracing-shim/test/CMakeLists.txt create mode 100644 opentracing-shim/test/shim_test.cc create mode 100644 opentracing-shim/test/tracer_test.cpp 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 f1a6d1cd17..b0db1cbfe7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,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" ON) + set(OTELCPP_PROTO_PATH "" CACHE PATH "Path to opentelemetry-proto") @@ -492,6 +494,10 @@ include_directories(api/include) add_subdirectory(api) +if(WITH_OPENTRACING) + add_subdirectory(opentracing-shim) +endif() + if(NOT WITH_API_ONLY) set(BUILD_TESTING ${BUILD_TESTING}) include_directories(sdk/include) @@ -501,6 +507,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/opentracing-shim/CMakeLists.txt b/opentracing-shim/CMakeLists.txt new file mode 100644 index 0000000000..4b6daf8619 --- /dev/null +++ b/opentracing-shim/CMakeLists.txt @@ -0,0 +1,40 @@ +#set(this_target opentelemetry_opentracing_shim) + +#message("CMAKE_CURRENT_LIST_DIR " ${CMAKE_CURRENT_LIST_DIR}) +#message("CMAKE_CURRENT_SOURCE_DIR " ${CMAKE_CURRENT_SOURCE_DIR}) +#message("CMAKE_SOURCE_DIR " ${CMAKE_SOURCE_DIR}) +#message("PROJECT_SOURCE_DIR " ${PROJECT_SOURCE_DIR}) + +#add_library(${this_target} INTERFACE) +#target_include_directories( +# ${this_target} +# PUBLIC "$" +# "$" +# "$") +# +#set_target_properties(${this_target} PROPERTIES EXPORT_NAME "opentracing-shim") +#target_link_libraries(${this_target} INTERFACE ${this_target} opentracing-cpp) +# +#get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) +#foreach(dir ${dirs}) +# message(STATUS "dir='${dir}'") +#endforeach() +# +#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/opentracing-shim +# DESTINATION include/opentelemetry/ +# FILES_MATCHING +# PATTERN "*.h") + +add_subdirectory(src) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() # BUILD_TESTING \ No newline at end of file diff --git a/opentracing-shim/include/shim_utils.h b/opentracing-shim/include/shim_utils.h new file mode 100644 index 0000000000..3c7fce0fcc --- /dev/null +++ b/opentracing-shim/include/shim_utils.h @@ -0,0 +1,134 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim::shimutils +{ + +using opentelemetry::common::AttributeValue; + +static inline AttributeValue attributeFromValue(const opentracing::Value& value) +{ + 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()(std::string v) { return v.c_str(); } + AttributeValue operator()(opentracing::string_view v) { return nostd::string_view{v.data()}; } + AttributeValue operator()(std::nullptr_t) { return std::string{}; } + AttributeValue operator()(const char* v) { return v; } + AttributeValue operator()(opentracing::util::recursive_wrapper) { return std::string{}; } + AttributeValue operator()(opentracing::util::recursive_wrapper) { return std::string{}; } + } 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()(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); +} + +template::value, bool> = true> +class CarrierWriterShim : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + + CarrierWriterShim(const T& writer) : writer_(writer) {} + + // returns the value associated with the passed key. + virtual nostd::string_view Get(nostd::string_view key) const noexcept override + { + return ""; + } + + // stores the key-value pair. + virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override + { + writer_.Set(key.data(), value.data()); + } + +private: + + const T& writer_; + +}; + +template::value, bool> = true> +class CarrierReaderShim : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + + CarrierReaderShim(const T& reader) : reader_(reader) {} + + // returns the value associated with the passed key. + virtual nostd::string_view Get(nostd::string_view key) const noexcept override + { + // First try carrier.LookupKey since that can potentially be the fastest approach. + auto result = reader_.LookupKey(key.data()); + + if (!result.has_value() || + opentracing::are_errors_equal(result.error(), opentracing::lookup_key_not_supported_error)) + { + // Fall back to iterating through all of the keys. + reader_.ForeachKey([key, &result] + (opentracing::string_view k, opentracing::string_view v) -> opentracing::expected { + if (key == k) + { + result = opentracing::make_expected(v); + // 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 nostd::string_view{ result.has_value() ? result.value().data() : "" }; + } + + // stores the key-value pair. + virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override + { + // Not required for Opentracing reader + } + +private: + + const T& reader_; + +}; + +static inline bool isBaggageEmpty(const BaggagePtr& baggage) +{ + if (baggage) + { + return baggage->GetAllEntries([](nostd::string_view, nostd::string_view){ + return false; + }); + } + + return true; +} + +} // namespace opentracingshim::shimutils +OPENTELEMETRY_END_NAMESPACE diff --git a/opentracing-shim/include/span_context_shim.h b/opentracing-shim/include/span_context_shim.h new file mode 100644 index 0000000000..b2db5ed6c8 --- /dev/null +++ b/opentracing-shim/include/span_context_shim.h @@ -0,0 +1,51 @@ +/* + * 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 SpanContextShim operator=(const SpanContextShim& other) + { + return SpanContextShim(other.context_, other.baggage_); + } + + inline const opentelemetry::trace::SpanContext& context() const { return context_; } + + inline const BaggagePtr& baggage() const { return baggage_; } + + SpanContextShim newWithKeyValue(const nostd::string_view &key, const 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; + +private: + + const opentelemetry::trace::SpanContext context_; + const BaggagePtr baggage_; + +}; + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/opentracing-shim/include/span_shim.h b/opentracing-shim/include/span_shim.h new file mode 100644 index 0000000000..a90b7499dd --- /dev/null +++ b/opentracing-shim/include/span_shim.h @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "tracer_shim.h" +#include "span_context_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(opentracing::SystemTime timestamp, nostd::span fields) noexcept; + + TracerShim tracer_; + SpanPtr span_; + SpanContextShim context_; + mutable opentelemetry::common::SpinLockMutex context_lock_; + +}; + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/opentracing-shim/include/tracer_shim.h b/opentracing-shim/include/tracer_shim.h new file mode 100644 index 0000000000..df98b2647d --- /dev/null +++ b/opentracing-shim/include/tracer_shim.h @@ -0,0 +1,103 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/trace/provider.h" +#include "opentelemetry/context/propagation/text_map_propagator.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 textMap; + PropagatorPtr httpHeaders; +}; + +class TracerShim : public opentracing::Tracer +{ +public: + + /** + * Creates a {@code opentracing::Tracer} shim out of + * {@code Provider::GetTracerProvider()} and + * {@code GlobalTextMapPropagator::GetGlobalPropagator()}. + * + * @returns a {@code opentracing::Tracer}. + */ + static inline TracerShim createTracerShim() + { + return createTracerShim(opentelemetry::trace::Provider::GetTracerProvider()); + } + + /** + * Creates a {@code opentracing::Tracer} shim using the provided + * {@code TracerProvider} and + * {@code TextMapPropagator} instance. + * + * @param provider the {@code TracerProvider} instance used to create this shim. + * @param propagators the {@code OpenTracingPropagators} instance used to create this shim. + * @returns a {@code opentracing::Tracer}. + */ + static inline TracerShim createTracerShim(const TracerProviderPtr& provider, + const OpenTracingPropagators& propagators = {}) + { + return createTracerShim(provider->GetTracer("opentracing-shim"), propagators); + } + + /** + * Creates a {@code opentracing::Tracer} shim using provided + * {@code Tracer} instance and + * {@code GlobalTextMapPropagator::GetGlobalPropagator()}. + * + * @returns a {@code opentracing::Tracer}. + */ + static inline TracerShim createTracerShim(const TracerPtr& tracer, + const OpenTracingPropagators& propagators = {}) + { + return TracerShim(tracer, 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) {} + + template + opentracing::expected injectImpl(const opentracing::SpanContext& sc, + const T& writer, + const PropagatorPtr& propagator) const; + + template + opentracing::expected> extractImpl(const T& reader, + const PropagatorPtr& propagator) const; + + TracerPtr tracer_; + OpenTracingPropagators propagators_; + bool is_closed_ = false; +}; + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/opentracing-shim/src/CMakeLists.txt b/opentracing-shim/src/CMakeLists.txt new file mode 100644 index 0000000000..10e840a377 --- /dev/null +++ b/opentracing-shim/src/CMakeLists.txt @@ -0,0 +1,34 @@ +set(THIS_TARGET opentelemetry_opentracing_shim) +set(TARGET_NAME opentracing_shim) + +include_directories( + ${CMAKE_CURRENT_LIST_DIR}/../include + ${CMAKE_SOURCE_DIR}/third_party/opentracing-cpp/include + ${CMAKE_SOURCE_DIR}/third_party/opentracing-cpp/build/include + ${CMAKE_SOURCE_DIR}/third_party/opentracing-cpp/3rd_party/include) + +file(GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/*.cc) +file(GLOB_RECURSE HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/../include/*.h) + +add_library(${THIS_TARGET} SHARED ${SOURCE_FILES} ${HEADER_FILES}) + +set_target_properties(${THIS_TARGET} PROPERTIES EXPORT_NAME ${TARGET_NAME}) + +target_include_directories( + ${THIS_TARGET} + PUBLIC "$" + "$") + +target_link_libraries(${THIS_TARGET} PUBLIC opentelemetry_api) + +install( + TARGETS ${THIS_TARGET} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) +foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") +endforeach() \ No newline at end of file diff --git a/opentracing-shim/src/span_context_shim.cc b/opentracing-shim/src/span_context_shim.cc new file mode 100644 index 0000000000..d220634701 --- /dev/null +++ b/opentracing-shim/src/span_context_shim.cc @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "span_context_shim.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +SpanContextShim SpanContextShim::newWithKeyValue(const nostd::string_view &key, const 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_)); +} + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/opentracing-shim/src/span_shim.cc b/opentracing-shim/src/span_shim.cc new file mode 100644 index 0000000000..6600872494 --- /dev/null +++ b/opentracing-shim/src/span_shim.cc @@ -0,0 +1,133 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "span_shim.h" +#include "span_context_shim.h" +#include "tracer_shim.h" +#include "shim_utils.h" + +#include "opentelemetry/trace/semantic_conventions.h" +#include "opentelemetry/trace/span_metadata.h" +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +void SpanShim::handleError(const opentracing::Value& value) noexcept +{ + using opentelemetry::trace::StatusCode; + + auto code = StatusCode::kUnset; + const auto& str_value = shimutils::stringFromValue(value); + if (str_value == "true") + { + code = StatusCode::kError; + } + else if (str_value == "false") + { + code = StatusCode::kOk; + } + + span_->SetStatus(code); +} + +void SpanShim::FinishWithOptions(const opentracing::FinishSpanOptions& finish_span_options) noexcept +{ + 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 +{ + if (key == opentracing::ext::error) + { + handleError(value); + } + else + { + span_->SetAttribute(key.data(), shimutils::attributeFromValue(value)); + } +} + +void SpanShim::SetBaggageItem(opentracing::string_view restricted_key, opentracing::string_view value) noexcept +{ + 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 +{ + 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 +{ + logImpl(opentracing::SystemTime::min(), fields); +} + +void SpanShim::Log(opentracing::SystemTime timestamp, std::initializer_list fields) noexcept +{ + logImpl(timestamp, fields); +} + +void SpanShim::Log(opentracing::SystemTime timestamp, const std::vector& fields) noexcept +{ + logImpl(timestamp, fields); +} + +void SpanShim::logImpl(opentracing::SystemTime timestamp, nostd::span fields) noexcept +{ + const auto event = std::find_if(fields.begin(), fields.end(), [](EventEntry item){ return item.first == "event"; }); + auto name = (event != fields.end()) ? shimutils::stringFromValue(event->second) : std::string{"log"}; + + bool is_error = (name == opentracing::ext::error); + if (is_error) name = "exception"; + + std::vector> attributes; + attributes.reserve(fields.size()); + + for (const auto& entry : fields) + { + auto key = entry.first; + const auto& value = shimutils::attributeFromValue(entry.second); + + if (is_error) + { + if (key == "error.kind") + { + key = opentelemetry::trace::SemanticConventions::kExceptionType; + } + else if (key == "message") + { + key = opentelemetry::trace::SemanticConventions::kExceptionMessage; + } + else if (key == "stack") + { + key = opentelemetry::trace::SemanticConventions::kExceptionStacktrace; + } + } + + attributes.emplace_back(key, value); + } + + if (timestamp != opentracing::SystemTime::min()) + { + span_->AddEvent(name, timestamp, attributes); + } + else + { + span_->AddEvent(name, attributes); + } +} + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/opentracing-shim/src/tracer_shim.cc b/opentracing-shim/src/tracer_shim.cc new file mode 100644 index 0000000000..ac5cd31ef9 --- /dev/null +++ b/opentracing-shim/src/tracer_shim.cc @@ -0,0 +1,273 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tracer_shim.h" +#include "span_shim.h" +#include "shim_utils.h" + +#include +#include +#include +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace opentracingshim +{ + +namespace detail +{ + +using RefsList = std::initializer_list>; +using LinksList = std::vector>; + +static opentelemetry::trace::StartSpanOptions makeOptionsShim(const opentracing::StartSpanOptions& options) noexcept +{ + using opentracing::SpanReferenceType; + + opentelemetry::trace::StartSpanOptions options_shim; + // If an explicit start timestamp is specified, a conversion MUST + // be done to match the OpenTracing and OpenTelemetry units. + 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()) + { + 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 = dynamic_cast(context)) + { + options_shim.parent = context_shim->context(); + } + } + + return options_shim; +} + +static LinksList makeReferenceLinks(const opentracing::StartSpanOptions& options) noexcept +{ + using opentracing::SpanReferenceType; + using namespace opentelemetry::trace::SemanticConventions; + + LinksList links; + links.reserve(options.references.size()); + + // All values in the list MUST be added as Links with the reference type value + // as a Link attribute, i.e. opentracing.ref_type set to follows_from or child_of + for (const auto& entry : options.references) + { + auto context_shim = dynamic_cast(entry.second); + nostd::string_view span_kind; + + if (entry.first == SpanReferenceType::ChildOfRef) + { + span_kind = OpentracingRefTypeValues::kChildOf; + } + else if (entry.first == SpanReferenceType::FollowsFromRef) + { + span_kind = OpentracingRefTypeValues::kFollowsFrom; + } + + if (context_shim && !span_kind.empty()) + { + // links.push_back({ context_shim->context(), {{ opentracing::ext::span_kind.data(), span_kind }} }); + links.emplace_back(std::piecewise_construct, + std::forward_as_tuple(context_shim->context()), + std::forward_as_tuple(std::forward({{ opentracing::ext::span_kind.data(), span_kind }}))); + } + } + + return links; +} + +static BaggagePtr 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) + { + if (auto context_shim = dynamic_cast(entry.second)) + { + // The union of their Baggage values MUST be used as the initial Baggage of the newly created Span. + context_shim->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 lisf 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)); +} + +static std::vector> makeTags(const opentracing::StartSpanOptions& options) noexcept +{ + std::vector> tags; + tags.reserve(options.tags.size()); + + // If an initial set of tags is specified, the values MUST + // be set at the creation time of the OpenTelemetry Span. + for (const auto& entry : options.tags) + { + tags.emplace_back(entry.first, shimutils::attributeFromValue(entry.second)); + } + + return tags; +} + +} // namespace opentracingshim::detail + +std::unique_ptr TracerShim::StartSpanWithOptions(opentracing::string_view operation_name, + const opentracing::StartSpanOptions& options) const noexcept +{ + if (is_closed_) return nullptr; + + const auto& opts = detail::makeOptionsShim(options); + const auto& links = detail::makeReferenceLinks(options); + const auto& baggage = detail::makeBaggage(options); + const auto& attributes = detail::makeTags(options); + + auto span = tracer_->StartSpan(operation_name.data(), attributes, links, opts); + auto span_shim = new 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.begin(), options.tags.end(), + [](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 (error_entry != options.tags.end()) { + span_shim->handleError(error_entry->second); + } + + return std::unique_ptr(span_shim); +} + +opentracing::expected TracerShim::Inject(const opentracing::SpanContext& sc, + std::ostream& writer) 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_.textMap + ? propagators_.textMap + : 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_.httpHeaders + ? propagators_.httpHeaders + : opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + + return injectImpl(sc, writer, propagator); +} + +opentracing::expected> TracerShim::Extract(std::istream& reader) 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_.textMap + ? propagators_.textMap + : 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_.httpHeaders + ? propagators_.httpHeaders + : opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + + return extractImpl(reader, propagator); +} + +template +opentracing::expected TracerShim::injectImpl(const opentracing::SpanContext& sc, + const T& 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 = dynamic_cast(&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()); + + shimutils::CarrierWriterShim carrier{writer}; + propagator->Inject(carrier, context); + } + + return opentracing::make_expected(); +} + +template +opentracing::expected> TracerShim::extractImpl(const T& 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. + shimutils::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() && shimutils::isBaggageEmpty(baggage)) + ? nullptr + : new SpanContextShim(span_context, baggage); + + return opentracing::make_expected(std::unique_ptr(context_shim)); +} + +} // namespace opentracingshim +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/opentracing-shim/test/CMakeLists.txt b/opentracing-shim/test/CMakeLists.txt new file mode 100644 index 0000000000..016f938785 --- /dev/null +++ b/opentracing-shim/test/CMakeLists.txt @@ -0,0 +1,9 @@ +set(this_target shim_test) + +add_executable(${this_target} "${this_target}.cc") +target_link_libraries(${this_target} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) +gtest_add_tests( + TARGET ${this_target} + TEST_PREFIX ${this_target}. + TEST_LIST ${this_target}) diff --git a/opentracing-shim/test/shim_test.cc b/opentracing-shim/test/shim_test.cc new file mode 100644 index 0000000000..6d9020b1bb --- /dev/null +++ b/opentracing-shim/test/shim_test.cc @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/trace/noop.h" +#include "opentelemetry/trace/scope.h" + +#include + +namespace trace_api = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace context = opentelemetry::context; + +TEST(ShimTest, SpanReferenceToCreatingTracer) +{ + // auto tracer = MakeNoopTracer(); + + // auto span1 = tracer->StartSpan("a"); + // CHECK(span1); + + // CHECK(&span1->tracer() == tracer.get()); + + // std::unique_ptr tracer(new trace_api::NoopTracer()); + // nostd::shared_ptr span_first(new trace_api::NoopSpan(nullptr)); + // nostd::shared_ptr span_second(new trace_api::NoopSpan(nullptr)); + + // auto current = tracer->GetCurrentSpan(); + // ASSERT_FALSE(current->GetContext().IsValid()); + + // { + // auto scope_first = tracer->WithActiveSpan(span_first); + // current = tracer->GetCurrentSpan(); + // ASSERT_EQ(current, span_first); + + // { + // auto scope_second = tracer->WithActiveSpan(span_second); + // current = tracer->GetCurrentSpan(); + // ASSERT_EQ(current, span_second); + // } + // current = tracer->GetCurrentSpan(); + // ASSERT_EQ(current, span_first); + // } + + // current = tracer->GetCurrentSpan(); + // ASSERT_FALSE(current->GetContext().IsValid()); + ASSERT_TRUE(true); +} diff --git a/opentracing-shim/test/tracer_test.cpp b/opentracing-shim/test/tracer_test.cpp new file mode 100644 index 0000000000..eca3a367fb --- /dev/null +++ b/opentracing-shim/test/tracer_test.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +using namespace opentracing; + +#define CATCH_CONFIG_MAIN +#include + +TEST_CASE("tracer") { + auto tracer = MakeNoopTracer(); + + auto span1 = tracer->StartSpan("a"); + CHECK(span1); + + SECTION("Spans provide references to the tracer that created them.") { + CHECK(&span1->tracer() == tracer.get()); + } + + SECTION("Ensure basic operations compile.") { + auto span2 = tracer->StartSpan("b", {ChildOf(&span1->context())}); + CHECK(span2); + span2->SetOperationName("b1"); + span2->SetTag("x", true); + span2->SetTag(opentracing::ext::span_kind, + opentracing::ext::span_kind_rpc_client); + CHECK(span2->BaggageItem("y").empty()); + span2->Log({{"event", "xyz"}, {"abc", 123}}); + span2->Finish(); + } + + SECTION("A reference to a null SpanContext is ignored.") { + StartSpanOptions options; + ChildOf(nullptr).Apply(options); + CHECK(options.references.size() == 0); + } +} + +TEST_CASE("A tracer can be globally registered") { + CHECK(!Tracer::IsGlobalTracerRegistered()); + auto tracer = MakeNoopTracer(); + CHECK(Tracer::InitGlobal(tracer) != nullptr); + CHECK(Tracer::IsGlobalTracerRegistered()); +}