diff --git a/.devcontainer/compose.yml b/.devcontainer/compose.yml index f896916862..9a1ccec4fa 100644 --- a/.devcontainer/compose.yml +++ b/.devcontainer/compose.yml @@ -8,6 +8,15 @@ volumes: ydb-certs: services: + jaeger: + image: jaegertracing/all-in-one:1.51.0 + ports: + - "16686:16686" # Jaeger UI frontend + - "4317:4317" # gRPC port for accepts traces in OpenTelemetry OTLP format + - "4318:4318" # HTTP port for accepts traces in OpenTelemetry OTLP format + environment: + - COLLECTOR_OTLP_ENABLED=true + sdk: platform: linux/amd64 diff --git a/CMakeLists.txt b/CMakeLists.txt index a1e8a38f64..7fb60db6fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,8 @@ option(YDB_SDK_EXAMPLES "Build YDB C++ SDK examples" On) set(YDB_SDK_GOOGLE_COMMON_PROTOS_TARGET "" CACHE STRING "Name of cmake target preparing google common proto library") option(YDB_SDK_USE_RAPID_JSON "Search for rapid json library in system" ON) +option(YDB_SDK_TRACING "Enable tracing support" ON) + set(BUILD_SHARED_LIBS Off) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED On) diff --git a/cmake/external_libs.cmake b/cmake/external_libs.cmake index c258dea642..b788604971 100644 --- a/cmake/external_libs.cmake +++ b/cmake/external_libs.cmake @@ -15,6 +15,11 @@ find_package(jwt-cpp REQUIRED) find_package(GTest REQUIRED) find_package(double-conversion REQUIRED) +# OpenTelemetry +if(YDB_SDK_TRACING) + find_package(opentelemetry-cpp REQUIRED) +endif() + # RapidJSON if (YDB_SDK_USE_RAPID_JSON) find_package(RapidJSON REQUIRED) @@ -85,4 +90,4 @@ target_include_directories(nayuki_md5 PUBLIC $ ) -_ydb_sdk_install_targets(TARGETS nayuki_md5) +_ydb_sdk_install_targets(TARGETS nayuki_md5) \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 97119cff5e..f98ec530f4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -4,5 +4,6 @@ add_subdirectory(pagination) add_subdirectory(secondary_index) add_subdirectory(secondary_index_builtin) add_subdirectory(topic_reader) +add_subdirectory(tracing) add_subdirectory(ttl) add_subdirectory(vector_index) diff --git a/examples/tracing/CMakeLists.txt b/examples/tracing/CMakeLists.txt new file mode 100644 index 0000000000..7473f4ecb9 --- /dev/null +++ b/examples/tracing/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(tracing_example tracing_example.cpp) + +if(YDB_SDK_TRACING) + target_link_libraries(tracing_example PRIVATE + ydb-cpp-sdk + ${OPENTELEMETRY_LIBRARIES} + ) +else() + target_link_libraries(tracing_example PRIVATE ydb-cpp-sdk) + target_compile_definitions(tracing_example PRIVATE -DYDB_SDK_TRACING_DISABLED) +endif() \ No newline at end of file diff --git a/examples/tracing/tracing_example.cpp b/examples/tracing/tracing_example.cpp new file mode 100644 index 0000000000..4f36220b8b --- /dev/null +++ b/examples/tracing/tracing_example.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +int main() { + auto exporter = opentelemetry::exporter::jaeger::JaegerExporterFactory::Create(); + auto provider = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(exporter)); + auto otelTracer = provider->GetTracer("ydb-cpp-sdk"); + + auto ydbTracer = std::make_shared(otelTracer); + + auto driver = NYdb::TDriver( + NYdb::TDriverConfig() + .SetEndpoint("localhost:2136") + .SetDatabase("/local") + .SetTracer(ydbTracer) + ); + + auto client = NYdb::NTable::TTableClient(driver); + auto session = client.CreateSession().GetValueSync(); + session.ExecuteDataQuery("SELECT 1", NYdb::NTable::TTxControl::BeginTx().CommitTx()).GetValueSync(); + + return 0; +} diff --git a/include/ydb-cpp-sdk/client/driver/driver.h b/include/ydb-cpp-sdk/client/driver/driver.h index c934c422cb..9fa14c06ae 100644 --- a/include/ydb-cpp-sdk/client/driver/driver.h +++ b/include/ydb-cpp-sdk/client/driver/driver.h @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -118,6 +119,8 @@ class TDriverConfig { //! Log backend. TDriverConfig& SetLog(std::unique_ptr&& log); + + TDriverConfig& SetTracer(std::shared_ptr tracer); private: class TImpl; std::shared_ptr Impl_; diff --git a/include/ydb-cpp-sdk/client/tracing/tracer.h b/include/ydb-cpp-sdk/client/tracing/tracer.h new file mode 100644 index 0000000000..3896c9685d --- /dev/null +++ b/include/ydb-cpp-sdk/client/tracing/tracer.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include + +namespace NYdb { +namespace NTracing { + +using TAttributeMap = std::unordered_map; + +class TTraceContext { +public: + TTraceContext(std::string traceId, std::string spanId, std::string parentSpanId = "") + : TraceId_(std::move(traceId)) + , SpanId_(std::move(spanId)) + , ParentSpanId_(std::move(parentSpanId)) + {} + + const std::string& GetTraceId() const { return TraceId_; } + const std::string& GetSpanId() const { return SpanId_; } + const std::string& GetParentSpanId() const { return ParentSpanId_; } + + static std::shared_ptr GenerateNew(); + std::shared_ptr CreateChild() const; + std::string ToTraceParent() const; + +private: + std::string TraceId_; + std::string SpanId_; + std::string ParentSpanId_; +}; + + +class ISpan { +public: + virtual ~ISpan() = default; + + virtual void AddAttribute(const std::string& key, const std::string& value) = 0; + virtual void AddEvent(const std::string& name, const TAttributeMap& attributes = {}) = 0; + virtual void SetStatus(bool isError, const std::string& description = "") = 0; + virtual void End() = 0; + + virtual const TTraceContext& GetContext() const = 0; +}; + + +class ITracer { +public: + virtual ~ITracer() = default; + + virtual std::unique_ptr StartSpan( + const std::string& name, + const TAttributeMap& attributes = {}, + std::shared_ptr parentContext = nullptr) = 0; + + virtual std::shared_ptr GetCurrentContext() const = 0; + + virtual std::string GetCurrentTraceParent() const { + if (auto ctx = GetCurrentContext()) { + return ctx->ToTraceParent(); + } + return ""; + } +}; + +} // namespace NTracing +} // namespace NYdb diff --git a/include/ydb-cpp-sdk/client/types/request_settings.h b/include/ydb-cpp-sdk/client/types/request_settings.h index 2f2d4dca8a..8d481726e3 100644 --- a/include/ydb-cpp-sdk/client/types/request_settings.h +++ b/include/ydb-cpp-sdk/client/types/request_settings.h @@ -8,6 +8,7 @@ #include #include +#include namespace NYdb::inline V3 { @@ -20,6 +21,7 @@ struct TRequestSettings { FLUENT_SETTING(std::string, RequestType); FLUENT_SETTING(THeader, Header); FLUENT_SETTING(TDuration, ClientTimeout); + FLUENT_SETTING(std::string, TraceParent); TRequestSettings() = default; @@ -29,6 +31,7 @@ struct TRequestSettings { , RequestType_(other.RequestType_) , Header_(other.Header_) , ClientTimeout_(other.ClientTimeout_) + , TraceParent_(other.TraceParent_) {} }; diff --git a/src/client/driver/driver.cpp b/src/client/driver/driver.cpp index 7596a1840a..f158561919 100644 --- a/src/client/driver/driver.cpp +++ b/src/client/driver/driver.cpp @@ -1,4 +1,5 @@ #include +#include #define INCLUDE_YDB_INTERNAL_H #include @@ -203,6 +204,11 @@ TDriverConfig& TDriverConfig::SetLog(std::unique_ptr&& log) { return *this; } +TDriverConfig& TDriverConfig::SetTracer(std::shared_ptr tracer) { + Impl_->Tracer_ = tracer ? tracer : std::make_shared(); + return *this; +} + //////////////////////////////////////////////////////////////////////////////// std::shared_ptr CreateInternalInterface(const TDriver connection) { diff --git a/src/client/impl/ydb_internal/grpc_connections/grpc_connections.h b/src/client/impl/ydb_internal/grpc_connections/grpc_connections.h index db777210ca..9171a2776d 100644 --- a/src/client/impl/ydb_internal/grpc_connections/grpc_connections.h +++ b/src/client/impl/ydb_internal/grpc_connections/grpc_connections.h @@ -181,6 +181,11 @@ class TGRpcConnectionsImpl TCallMeta meta; meta.Timeout = requestSettings.ClientTimeout; + + if (!requestSettings.TraceParent.empty()) { + meta.Aux.emplace_back({"traceparent", requestSettings.TraceParent}); + } + #ifndef YDB_GRPC_UNSECURE_AUTH meta.CallCredentials = dbState->CallCredentials; #else @@ -415,6 +420,11 @@ class TGRpcConnectionsImpl TCallMeta meta; meta.Timeout = requestSettings.ClientTimeout; + + if (!requestSettings.TraceParent.empty()) { + meta.Aux.emplace_back({"traceparent", requestSettings.TraceParent}); + } + #ifndef YDB_GRPC_UNSECURE_AUTH meta.CallCredentials = dbState->CallCredentials; #else @@ -509,6 +519,11 @@ class TGRpcConnectionsImpl } TCallMeta meta; + + if (!requestSettings.TraceParent.empty()) { + meta.Aux.emplace_back({"traceparent", requestSettings.TraceParent}); + } + #ifndef YDB_GRPC_UNSECURE_AUTH meta.CallCredentials = dbState->CallCredentials; #else diff --git a/src/client/tracing/noop_tracer.h b/src/client/tracing/noop_tracer.h new file mode 100644 index 0000000000..8169c81d94 --- /dev/null +++ b/src/client/tracing/noop_tracer.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace NYdb::inline V3 { +namespace NTracing { + + class TNoopSpan : public ISpan { + public: + void AddAttribute(const std::string&, const std::string&) override {} + void AddEvent(const std::string&, const TAttributeMap& = {}) override {} + void SetStatus(bool, const std::string& = "") override {} + void End() override {} + + const TTraceContext& GetContext() const override { + static TTraceContext emptyContext("", ""); + return emptyContext; + } + }; + + class TNoopTracer : public ITracer { + public: + std::unique_ptr StartSpan( + const std::string&, + const TAttributeMap& = {}, + std::shared_ptr = nullptr) override + { + return std::make_unique(); + } + + std::shared_ptr GetCurrentContext() const override { + return nullptr; + } + }; + +} // namespace NTracing +} // namespace NYdb diff --git a/src/client/tracing/otel_tracer.h b/src/client/tracing/otel_tracer.h new file mode 100644 index 0000000000..e40364973f --- /dev/null +++ b/src/client/tracing/otel_tracer.h @@ -0,0 +1,123 @@ +#pragma once + +#include "tracer.h" + +#include +#include +#include +#include +#include + +namespace NYdb { +namespace NTracing { + +class TOpenTelemetrySpan : public ISpan { +public: + OpenTelemetrySpan( + opentelemetry::nostd::shared_ptr span, + std::shared_ptr context + ) : span_(std::move(span)), context_(std::move(context)) {} + + void AddAttribute(const std::string& key, const std::string& value) override { + span_->SetAttribute(key, value); + } + + void AddEvent(const std::string& name, const TAttributeMap& attributes = {}) override { + // Преобразование атрибутов в формат OpenTelemetry + std::vector> kvPairs; + kvPairs.reserve(attributes.size()); + for (const auto& [k, v] : attributes) { + kvPairs.emplace_back(k, v); + } + + // Создание ивента + span_->AddEvent(name, opentelemetry::common::MakeKeyValueIterableView(kvPairs)); + } + + void SetStatus(bool isError, const std::string& description = "") override { + span_->SetStatus(isError ? opentelemetry::trace::StatusCode::kError + : opentelemetry::trace::StatusCode::kOk, + description); + } + + void End() override { + span_->End(); + } + + const TTraceContext& GetContext() const override { + return *context_; + } + +private: + opentelemetry::nostd::shared_ptr span_; + std::shared_ptr context_; +}; + +class TOpenTelemetryTracer : public ITracer { +public: + explicit OpenTelemetryTracer( + opentelemetry::nostd::shared_ptr tracer + ) : tracer_(std::move(tracer)) {} + + std::unique_ptr StartSpan( + const std::string& name, + const TAttributeMap& attributes = {}, + std::shared_ptr parentContext = nullptr + ) override { + opentelemetry::trace::StartSpanOptions options; + + // Установка родительского контекста + if (parentContext) { + auto traceId = opentelemetry::trace::TraceId::FromHex(parentContext->GetTraceId()); + auto spanId = opentelemetry::trace::SpanId::FromHex(parentContext->GetSpanId()); + + auto parentContext = opentelemetry::trace::SpanContext( + traceId, + spanId, + opentelemetry::trace::TraceFlags::kSampled, + true + ); + options.parent = parentContext; + } + + // Создание спана + auto span = tracer_->StartSpan(name, options); + + // Установка атрибутов + for (const auto& [key, value] : attributes) { + span->SetAttribute(key, value); + } + + // Создание контекста + std::shared_ptr context; + if (parentContext) { + context = parentContext->CreateChild(); + } else { + context = TTraceContext::GenerateNew(); + } + + return std::make_unique(span, context); + } + + std::shared_ptr GetCurrentContext() const override { + auto currentSpan = opentelemetry::trace::GetSpan( + opentelemetry::context::RuntimeContext::GetCurrent() + ); + + if (!currentSpan->IsValid()) { + return nullptr; + } + + auto context = currentSpan->GetContext(); + return std::make_shared( + context.trace_id().ToHex(), + context.span_id().ToHex() + ); + } + +private: + opentelemetry::nostd::shared_ptr tracer_; +}; + +} // namespace NTracing +} // namespace NYdb diff --git a/src/client/tracing/tracer.cpp b/src/client/tracing/tracer.cpp new file mode 100644 index 0000000000..a27d2f306c --- /dev/null +++ b/src/client/tracing/tracer.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +namespace NYdb { +namespace NTracing { + +namespace { + std::string GenerateRandomHex(size_t length) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 15); + + std::ostringstream oss; + for (size_t i = 0; i < length; ++i) { + oss << std::hex << dis(gen); + } + return oss.str(); + } +} + +std::shared_ptr TTraceContext::GenerateNew() { + return std::make_shared( + GenerateRandomHex(32), + GenerateRandomHex(16) + ); +} + +std::shared_ptr TTraceContext::CreateChild() const { + return std::make_shared( + TraceId_, + GenerateRandomHex(16), + SpanId_ + ); +} + +std::string TTraceContext::ToTraceParent() const { + // Format: 00---01 + return "00-" + TraceId_ + "-" + SpanId_ + "-01"; +} + +} // namespace NTracing +} // namespace NYdb