1616 */
1717
1818#include " utils.hxx"
19+ #include " CLI/CLI.hpp"
1920#include " core/logger/configuration.hxx"
2021#include " core/logger/logger.hxx"
2122#include " core/meta/version.hxx"
2223#include " core/utils/binary.hxx"
23- #include " core/utils/duration_parser.hxx"
2424#include " core/utils/json.hxx"
2525
2626#include < couchbase/cluster_options.hxx>
3030#include < couchbase/fmt/query_scan_consistency.hxx>
3131#include < couchbase/scope.hxx>
3232
33+ #if defined(__GNUC__)
34+ #pragma GCC diagnostic push
35+ #pragma GCC diagnostic ignored "-Wsign-conversion"
36+ #pragma GCC diagnostic ignored "-Wuseless-cast"
37+ #elif defined(__clang__)
38+ #pragma clang diagnostic push
39+ #pragma clang diagnostic ignored "-Wdeprecated-builtins"
40+ #endif
41+ #include < opentelemetry/sdk/common/global_log_handler.h>
42+
43+ #include < opentelemetry/trace/provider.h>
44+ #include < opentelemetry/trace/span_startoptions.h>
45+ #include < opentelemetry/trace/tracer_provider.h>
46+
47+ #include < opentelemetry/sdk/trace/batch_span_processor.h>
48+ #include < opentelemetry/sdk/trace/batch_span_processor_factory.h>
49+ #include < opentelemetry/sdk/trace/samplers/always_off.h>
50+ #include < opentelemetry/sdk/trace/samplers/always_on.h>
51+ #include < opentelemetry/sdk/trace/samplers/parent.h>
52+ #include < opentelemetry/sdk/trace/samplers/trace_id_ratio.h>
53+ #include < opentelemetry/sdk/trace/simple_processor.h>
54+ #include < opentelemetry/sdk/trace/simple_processor_factory.h>
55+ #include < opentelemetry/sdk/trace/tracer_provider.h>
56+ #include < opentelemetry/sdk/trace/tracer_provider_factory.h>
57+
58+ #include < opentelemetry/exporters/otlp/otlp_http.h>
59+ #include < opentelemetry/exporters/otlp/otlp_http_exporter_factory.h>
60+ #include < opentelemetry/exporters/otlp/otlp_http_exporter_options.h>
61+
62+ #include < couchbase/tracing/otel_tracer.hxx>
63+ #if defined(__GNUC__)
64+ #pragma GCC diagnostic pop
65+ #elif defined(__clang__)
66+ #pragma clang diagnostic pop
67+ #endif
68+
69+ #include < memory>
3370#include < spdlog/details/os.h>
3471#include < spdlog/fmt/bundled/chrono.h>
3572#include < spdlog/spdlog.h>
@@ -43,13 +80,54 @@ namespace cbc
4380{
4481namespace
4582{
83+
84+ class null_opentelemetry_logger : public opentelemetry ::sdk::common::internal_log::LogHandler
85+ {
86+ public:
87+ void Handle (opentelemetry::sdk::common::internal_log::LogLevel /* level */ ,
88+ const char * /* file */ ,
89+ int /* line */ ,
90+ const char * /* msg */ ,
91+ const opentelemetry::sdk::common::AttributeMap& /* attributes */ ) noexcept override
92+ {
93+ // Use it to debug issues with OpenTelemetry
94+ // TODO(SA): maybe we should forward these messages to our logger
95+ }
96+ };
97+
4698auto
47- getenv_or_default (std::string_view var_name, const std::string& default_value) -> std::string
99+ safe_getenv ( const std::string& name) noexcept -> std::optional<std:: string>
48100{
49- if (auto val = spdlog::details::os::getenv (var_name.data ()); !val.empty ()) {
50- return val;
101+ if (name.empty ()) {
102+ return std::nullopt ;
103+ }
104+
105+ #if defined(_WIN32)
106+ char * buf = nullptr ;
107+ size_t len = 0 ;
108+ if (_dupenv_s (&buf, &len, name.c_str ()) == 0 && buf != nullptr ) {
109+ std::string value (buf);
110+ free (buf);
111+ if (!value.empty ()) {
112+ return value;
113+ }
51114 }
52- return default_value;
115+ return std::nullopt ;
116+
117+ #else
118+ if (const char * val = std::getenv (name.c_str ())) { // NOLINT(concurrency-mt-unsafe)
119+ if (val[0 ] != ' \0 ' ) {
120+ return std::string (val);
121+ }
122+ }
123+ return std::nullopt ;
124+ #endif
125+ }
126+
127+ auto
128+ getenv_or_default (const std::string& var_name, const std::string& default_value) -> std::string
129+ {
130+ return safe_getenv (var_name).value_or (default_value);
53131}
54132
55133auto
@@ -388,6 +466,84 @@ add_options(CLI::App* app, tracing_options& options)
388466 ->add_option (" --tracing-threshold-view" , options.threshold_view , " Threshold for Query service." )
389467 ->default_val (defaults.tracing .view_threshold )
390468 ->type_name (" DURATION" );
469+
470+ group->add_flag (" --tracing-use-opentelemetry" ,
471+ options.opentelemetry .use_opentelemetry ,
472+ " Use OpenTelemetry and export with HTTP transport." );
473+
474+ group->add_option (" --tracing-opentelemetry-endpoint" ,
475+ options.opentelemetry .endpoint ,
476+ " The OTLP HTTP endpoint for OpenTelemetry to export to." );
477+
478+ std::vector<std::string> allowed_samplers{
479+ " always_on" ,
480+ " always_off" ,
481+ " trace_id_ratio_based" ,
482+ " parent_based" ,
483+ };
484+ group
485+ ->add_option (" --tracing-opentelemetry-sampler" ,
486+ options.opentelemetry .sampler ,
487+ " Sampler type for OpenTelemetry tracing." )
488+ ->transform (CLI::IsMember (allowed_samplers))
489+ ->default_str (" parent_based" );
490+
491+ group
492+ ->add_option (" --tracing-opentelemetry-sampling-ratio" ,
493+ options.opentelemetry .sampling_ratio ,
494+ " Sampling ratio for trace_id_ratio_based sampler (0.0 to 1.0)." )
495+ ->check (CLI::Range (0.0 , 1.0 ))
496+ ->default_str (" 1.0" );
497+
498+ group->add_flag (" --tracing-opentelemetry-use-batch-processor{true},!--tracing-opentelemetry-use-"
499+ " simple-processor{false}" ,
500+ options.opentelemetry .use_batch_processor ,
501+ " Use batch span processor (default) or simple span processor." );
502+
503+ group
504+ ->add_option (" --tracing-opentelemetry-batch-schedule-delay" ,
505+ options.opentelemetry .batch_schedule_delay ,
506+ " Batch processor schedule delay in milliseconds." )
507+ ->default_val (options.opentelemetry .batch_schedule_delay )
508+ ->type_name (" DURATION" );
509+
510+ group
511+ ->add_option (" --tracing-opentelemetry-batch-max-queue-size" ,
512+ options.opentelemetry .batch_max_queue_size ,
513+ " Maximum queue size for batch processor." )
514+ ->default_val (options.opentelemetry .batch_max_queue_size );
515+
516+ group
517+ ->add_option (" --tracing-opentelemetry-batch-max-export-batch-size" ,
518+ options.opentelemetry .batch_max_export_batch_size ,
519+ " Maximum export batch size for batch processor." )
520+ ->default_val (options.opentelemetry .batch_max_export_batch_size );
521+
522+ group
523+ ->add_option (" --tracing-opentelemetry-exporter-timeout" ,
524+ options.opentelemetry .exporter_timeout ,
525+ " Exporter timeout in milliseconds." )
526+ ->default_val (options.opentelemetry .exporter_timeout )
527+ ->type_name (" DURATION" );
528+
529+ group->add_flag (" --tracing-opentelemetry-use-ssl-credentials" ,
530+ options.opentelemetry .use_ssl_credentials ,
531+ " Use SSL/TLS credentials for secure connection." );
532+
533+ group->add_option (" --tracing-opentelemetry-ssl-credentials-cacert" ,
534+ options.opentelemetry .ssl_credentials_cacert ,
535+ " Path to CA certificate file for SSL/TLS verification." );
536+
537+ group
538+ ->add_option (" --tracing-opentelemetry-headers" ,
539+ options.opentelemetry .headers ,
540+ " HTTP headers (e.g. for authentication) (format: key=value)." )
541+ ->type_name (" KEY=VALUE" )
542+ ->take_all ();
543+
544+ group->add_option (" --tracing-opentelemetry-compression" ,
545+ options.opentelemetry .compression ,
546+ " Compression algorithm for exporter (e.g., 'gzip')." );
391547}
392548
393549auto
@@ -559,9 +715,77 @@ apply_options(couchbase::cluster_options& options, const metrics_options& metric
559715 options.metrics ().emit_interval (metrics.emit_interval );
560716}
561717
718+ void
719+ apply_opentelemetry_tracer_options (couchbase::cluster_options& options,
720+ const opentelemetry_tracing_options& tracing)
721+ {
722+ opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler (
723+ std::make_shared<null_opentelemetry_logger>());
724+
725+ opentelemetry::exporter::otlp::OtlpHttpExporterOptions exporter_options{};
726+ if (auto endpoint = tracing.endpoint ; endpoint) {
727+ exporter_options.url = endpoint.value ();
728+ }
729+ exporter_options.timeout = tracing.exporter_timeout ;
730+ if (!tracing.headers .empty ()) {
731+ for (const auto & [key, value] : tracing.headers ) {
732+ exporter_options.http_headers .insert ({ key, value });
733+ }
734+ }
735+ if (auto compression = tracing.compression ; compression) {
736+ exporter_options.compression = compression.value ();
737+ }
738+
739+ auto exporter = opentelemetry::exporter::otlp::OtlpHttpExporterFactory::Create (exporter_options);
740+
741+ std::unique_ptr<opentelemetry::sdk::trace::SpanProcessor> processor;
742+ if (tracing.use_batch_processor ) {
743+ opentelemetry::sdk::trace::BatchSpanProcessorOptions batch_opts{};
744+ batch_opts.schedule_delay_millis = tracing.batch_schedule_delay ;
745+ batch_opts.max_queue_size = tracing.batch_max_queue_size ;
746+ batch_opts.max_export_batch_size = tracing.batch_max_export_batch_size ;
747+ processor =
748+ opentelemetry::sdk::trace::BatchSpanProcessorFactory::Create (std::move (exporter), batch_opts);
749+ } else {
750+ processor = opentelemetry::sdk::trace::SimpleSpanProcessorFactory::Create (std::move (exporter));
751+ }
752+
753+ auto resource = opentelemetry::sdk::resource::Resource::Create ({
754+ { " service.name" , " cbc" },
755+ { " service.version" , couchbase::core::meta::sdk_semver () },
756+ });
757+
758+ std::unique_ptr<opentelemetry::sdk::trace::Sampler> sampler;
759+ if (!tracing.sampler || tracing.sampler == " parent_based" ) {
760+ sampler = std::make_unique<opentelemetry::sdk::trace::ParentBasedSampler>(
761+ std::make_shared<opentelemetry::sdk::trace::AlwaysOnSampler>());
762+ } else if (tracing.sampler == " always_on" ) {
763+ sampler = std::make_unique<opentelemetry::sdk::trace::AlwaysOnSampler>();
764+ } else if (tracing.sampler == " always_off" ) {
765+ sampler = std::make_unique<opentelemetry::sdk::trace::AlwaysOffSampler>();
766+ } else if (tracing.sampler == " trace_id_ratio_based" ) {
767+ sampler =
768+ std::make_unique<opentelemetry::sdk::trace::TraceIdRatioBasedSampler>(tracing.sampling_ratio );
769+ }
770+
771+ auto provider = opentelemetry::sdk::trace::TracerProviderFactory::Create (
772+ std::move (processor), resource, std::move (sampler));
773+
774+ opentelemetry::trace::Provider::SetTracerProvider (
775+ std::shared_ptr<opentelemetry::trace::TracerProvider>{ std::move (provider) });
776+
777+ options.tracing ().tracer (std::make_shared<couchbase::tracing::otel_request_tracer>(
778+ opentelemetry::trace::Provider::GetTracerProvider ()->GetTracer (
779+ " cbc" , couchbase::core::meta::sdk_semver ())));
780+ }
781+
562782void
563783apply_options (couchbase::cluster_options& options, const tracing_options& tracing)
564784{
785+ if (tracing.opentelemetry .use_opentelemetry ) {
786+ apply_opentelemetry_tracer_options (options, tracing.opentelemetry );
787+ }
788+
565789 options.tracing ().enable (!tracing.disable );
566790 options.tracing ().orphaned_emit_interval (tracing.orphaned_emit_interval );
567791 options.tracing ().orphaned_sample_size (tracing.orphaned_sample_size );
@@ -741,7 +965,8 @@ available_analytics_scan_consistency_modes() -> std::vector<std::string>
741965fail (std::string_view message)
742966{
743967 fmt::print (stderr, " ERROR: {}\n " , message);
744- exit (EXIT_FAILURE);
968+
969+ std::quick_exit (EXIT_FAILURE);
745970}
746971
747972auto
0 commit comments