From bb24ec220dc1e1de4761f9d205f9b67cb3cb1ac8 Mon Sep 17 00:00:00 2001 From: Dimitris Christodoulou Date: Thu, 22 Jan 2026 11:21:24 +0000 Subject: [PATCH 1/9] RCBC-524: OpenTelemetry integration --- Gemfile | 3 + .../couchbase-opentelemetry.gemspec | 52 +++++ .../couchbase/metrics/open_telemetry_meter.rb | 49 +++++ .../metrics/open_telemetry_value_recorder.rb | 42 ++++ .../tracing/open_telemetry_request_span.rb | 49 +++++ .../tracing/open_telemetry_request_tracer.rb | 50 +++++ lib/couchbase/errors.rb | 6 + lib/couchbase/tracing/noop_span.rb | 10 +- lib/couchbase/tracing/request_span.rb | 4 + .../tracing/threshold_logging_span.rb | 2 + lib/couchbase/utils/observability.rb | 8 + .../utils/observability_constants.rb | 5 + test/opentelemetry_test.rb | 189 ++++++++++++++++++ test/utils/tracing/test_span.rb | 5 + 14 files changed, 468 insertions(+), 6 deletions(-) create mode 100644 couchbase-opentelemetry/couchbase-opentelemetry.gemspec create mode 100644 couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb create mode 100644 couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_value_recorder.rb create mode 100644 couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_span.rb create mode 100644 couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_tracer.rb create mode 100644 test/opentelemetry_test.rb diff --git a/Gemfile b/Gemfile index fb7d7fa1..26b3f6f2 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ source "https://rubygems.org" git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } gemspec +gemspec path: "couchbase-opentelemetry" gem "rake" @@ -35,6 +36,8 @@ group :development do gem "minitest", "< 6.0" gem "minitest-reporters" gem "mutex_m" + gem "opentelemetry-metrics-sdk" + gem "opentelemetry-sdk" gem "rack" gem "reek" gem "rubocop", require: false diff --git a/couchbase-opentelemetry/couchbase-opentelemetry.gemspec b/couchbase-opentelemetry/couchbase-opentelemetry.gemspec new file mode 100644 index 00000000..3dc74d82 --- /dev/null +++ b/couchbase-opentelemetry/couchbase-opentelemetry.gemspec @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +lib = File.expand_path("lib", __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +Gem::Specification.new do |spec| + spec.name = "couchbase-opentelemetry" + spec.version = "0.0.1" + spec.authors = ["Sergey Avseyev"] + spec.email = ["sergey.avseyev@gmail.com"] + spec.summary = "OpenTelemetry integration for the Couchbase Ruby Client" + spec.description = "OpenTelemetry integration for the Couchbase Ruby Client" + spec.homepage = "https://www.couchbase.com" + spec.license = "Apache-2.0" + spec.required_ruby_version = "> 3.1" + + spec.metadata = { + "homepage_uri" => "https://docs.couchbase.com/ruby-sdk/current/hello-world/start-using-sdk.html", + "bug_tracker_uri" => "https://jira.issues.couchbase.com/browse/RCBC", + "mailing_list_uri" => "https://www.couchbase.com/forums/c/ruby-sdk", + "source_code_uri" => "https://github.com/couchbase/couchbase-ruby-client/tree/#{spec.version}", + "changelog_uri" => "https://github.com/couchbase/couchbase-ruby-client/releases/tag/#{spec.version}", + "documentation_uri" => "https://docs.couchbase.com/sdk-api/couchbase-ruby-client-#{spec.version}/index.html", + "github_repo" => "https://github.com/couchbase/couchbase-ruby-client", + "rubygems_mfa_required" => "true", + } + + spec.files = Dir.glob([ + "lib/**/*.rb", + ], File::FNM_DOTMATCH).select { |path| File.file?(path) } + spec.bindir = "exe" + spec.executables = spec.files.grep(/^exe\//) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_dependency "concurrent-ruby", "~> 1.3" + spec.add_dependency "opentelemetry-api", "~> 1.7" + spec.add_dependency "opentelemetry-metrics-api", "~> 0.4.0" +end diff --git a/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb b/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb new file mode 100644 index 00000000..82b5525d --- /dev/null +++ b/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "couchbase/metrics/meter" +require "couchbase/errors" +require_relative "open_telemetry_value_recorder" + +require "opentelemetry-metrics-api" + +module Couchbase + module Metrics + class OpenTelemetryMeter < Meter + def initialize(meter_provider) + super() + @histogram_cache = Concurrent::Map.new + begin + @wrapped = meter_provider.meter("com.couchbase.client/ruby") + rescue StandardError => e + raise Error::MeterError.new("Failed to create OpenTelemetry Meter: #{e.message}", nil, e) + end + end + + def value_recorder(name, tags) + unit = tags.delete("__unit") + + otel_histogram = @histogram_cache.compute_if_absent(name) do + @wrapped.create_histogram(name, unit: unit) + end + + OpenTelemetryValueRecorder.new(otel_histogram, tags, unit: unit) + rescue StandardError => e + raise Error::MeterError.new("Failed to create OpenTelemetry Histogram: #{e.message}", nil, e) + end + end + end +end diff --git a/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_value_recorder.rb b/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_value_recorder.rb new file mode 100644 index 00000000..28bbfd69 --- /dev/null +++ b/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_value_recorder.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "couchbase/metrics/value_recorder" + +module Couchbase + module Metrics + class OpenTelemetryValueRecorder < ValueRecorder + def initialize(recorder, tags, unit: nil) + super() + @wrapped = recorder + @tags = tags + @unit = unit + end + + def record_value(value) + value = + case @unit + when "s" + value / 1_000_000.0 + else + value + end + + @wrapped.record(value, attributes: @tags) + end + end + end +end diff --git a/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_span.rb b/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_span.rb new file mode 100644 index 00000000..af17b6d6 --- /dev/null +++ b/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_span.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "couchbase/tracing/request_span" + +require "opentelemetry-api" + +module Couchbase + module Tracing + class OpenTelemetryRequestSpan < RequestSpan + def initialize(span) + super() + + @wrapped = span + end + + def set_attribute(key, value) + @wrapped.set_attribute(key, value) + end + + def status=(status_code) + @wrapped.status = if status_code == :ok + ::OpenTelemetry::Trace::Status.ok + elsif status_code == :error + ::OpenTelemetry::Trace::Status.error + else + ::OpenTelemetry::Trace::Status.unset + end + end + + def finish(end_timestamp: nil) + @wrapped.finish(end_timestamp: end_timestamp) + end + end + end +end diff --git a/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_tracer.rb b/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_tracer.rb new file mode 100644 index 00000000..776f7e70 --- /dev/null +++ b/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_tracer.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "opentelemetry-api" + +require "couchbase/tracing/request_tracer" +require "couchbase/errors" +require_relative "open_telemetry_request_span" + +module Couchbase + module Tracing + class OpenTelemetryRequestTracer < RequestTracer + def initialize(tracer_provider) + super() + begin + @wrapped = tracer_provider.tracer("com.couchbase.client/ruby") + rescue StandardError => e + raise Error::TracerError.new("Failed to create OpenTelemetry tracer: #{e.message}", nil, e) + end + end + + def request_span(name, parent: nil, start_timestamp: nil) + parent_context = parent.nil? ? nil : ::OpenTelemetry::Trace.context_with_span(parent.instance_variable_get(:@wrapped)) + OpenTelemetryRequestSpan.new( + @wrapped.start_span( + name, + with_parent: parent_context, + start_timestamp: start_timestamp, + kind: :client, + ), + ) + rescue StandardError => e + raise Error::TracerError.new("Failed to create OpenTelemetry span: #{e.message}", nil, e) + end + end + end +end diff --git a/lib/couchbase/errors.rb b/lib/couchbase/errors.rb index c1bc7170..61008b94 100644 --- a/lib/couchbase/errors.rb +++ b/lib/couchbase/errors.rb @@ -410,5 +410,11 @@ class NoEnvironment < CouchbaseError class ClusterClosed < CouchbaseError end + + class TracerError < CouchbaseError + end + + class MeterError < CouchbaseError + end end end diff --git a/lib/couchbase/tracing/noop_span.rb b/lib/couchbase/tracing/noop_span.rb index be598b50..9c3b1c3a 100644 --- a/lib/couchbase/tracing/noop_span.rb +++ b/lib/couchbase/tracing/noop_span.rb @@ -19,13 +19,11 @@ module Couchbase module Tracing class NoopSpan < RequestSpan - def set_attribute(*) - nil - end + def set_attribute(*); end - def finish(*) - nil - end + def status=(*); end + + def finish(*); end end end end diff --git a/lib/couchbase/tracing/request_span.rb b/lib/couchbase/tracing/request_span.rb index 8e7c42ff..d70f3814 100644 --- a/lib/couchbase/tracing/request_span.rb +++ b/lib/couchbase/tracing/request_span.rb @@ -22,6 +22,10 @@ def set_attribute(key, value) raise NotImplementedError, "The RequestSpan does not implement #set_attribute" end + def status=(status_code) + raise NotImplementedError, "The RequestSpan does not implement #status=" + end + def finish(end_timestamp: nil) raise NotImplementedError, "The RequestSpan does not implement #finish" end diff --git a/lib/couchbase/tracing/threshold_logging_span.rb b/lib/couchbase/tracing/threshold_logging_span.rb index 8ef94513..36cf474c 100644 --- a/lib/couchbase/tracing/threshold_logging_span.rb +++ b/lib/couchbase/tracing/threshold_logging_span.rb @@ -63,6 +63,8 @@ def set_attribute(key, value) end end + def status=(*); end + def finish(end_timestamp: nil) duration_us = (((end_timestamp || Time.now) - @start_timestamp) * 1_000_000).round case name diff --git a/lib/couchbase/utils/observability.rb b/lib/couchbase/utils/observability.rb index 8bfdfe12..5deaf452 100644 --- a/lib/couchbase/utils/observability.rb +++ b/lib/couchbase/utils/observability.rb @@ -47,6 +47,8 @@ def record_operation(op_name, parent_span, receiver, service = nil) rescue StandardError => e handler.add_error(e) raise e + else + handler.set_success ensure handler.finish end @@ -144,7 +146,12 @@ def add_retries(retries) @op_span.set_attribute(ATTR_RETRIES, retries.to_i) end + def set_success + @op_span.status = :ok + end + def add_error(error) + @op_span.status = :error @meter_attributes[ATTR_ERROR_TYPE] = if error.is_a?(Couchbase::Error::CouchbaseError) || error.is_a?(Couchbase::Error::InvalidArgument) error.class.name.split("::").last @@ -201,6 +208,7 @@ def convert_backend_timestamp(backend_timestamp) def create_meter_attributes attrs = { ATTR_SYSTEM_NAME => ATTR_VALUE_SYSTEM_NAME, + ATTR_RESERVED_UNIT => ATTR_VALUE_RESERVED_UNIT_SECONDS, } attrs[ATTR_CLUSTER_NAME] = @cluster_name unless @cluster_name.nil? attrs[ATTR_CLUSTER_UUID] = @cluster_uuid unless @cluster_uuid.nil? diff --git a/lib/couchbase/utils/observability_constants.rb b/lib/couchbase/utils/observability_constants.rb index 611a7d48..c4ca87a7 100644 --- a/lib/couchbase/utils/observability_constants.rb +++ b/lib/couchbase/utils/observability_constants.rb @@ -177,6 +177,9 @@ module Observability # rubocop:disable Metrics/ModuleLength ATTR_PEER_PORT = "network.peer.port" ATTR_SERVER_DURATION = "couchbase.server_duration" + # Reserved attributes + ATTR_RESERVED_UNIT = "__unit" + ATTR_VALUE_SYSTEM_NAME = "couchbase" ATTR_VALUE_DURABILITY_MAJORITY = "majority" @@ -190,6 +193,8 @@ module Observability # rubocop:disable Metrics/ModuleLength ATTR_VALUE_SERVICE_ANALYTICS = "analytics" ATTR_VALUE_SERVICE_MANAGEMENT = "management" + ATTR_VALUE_RESERVED_UNIT_SECONDS = "s" + METER_NAME_OPERATION_DURATION = "db.client.operation.duration" end end diff --git a/test/opentelemetry_test.rb b/test/opentelemetry_test.rb new file mode 100644 index 00000000..c92fcba4 --- /dev/null +++ b/test/opentelemetry_test.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative "test_helper" + +require "couchbase/tracing/open_telemetry_request_tracer" +require "couchbase/metrics/open_telemetry_meter" + +require "opentelemetry-sdk" +require "opentelemetry-metrics-sdk" + +module Couchbase + class OpenTelemetryTest < Minitest::Test + include TestUtilities + + def setup + @span_exporter = ::OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new + @tracer_provider = + begin + tracer_provider = ::OpenTelemetry::SDK::Trace::TracerProvider.new + tracer_provider.add_span_processor( + ::OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(@span_exporter), + ) + tracer_provider + end + @tracer = Couchbase::Tracing::OpenTelemetryRequestTracer.new(@tracer_provider) + + @metric_exporter = ::OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter.new + @metric_reader = ::OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(exporter: @metric_exporter) + @meter_provider = + begin + meter_provider = ::OpenTelemetry::SDK::Metrics::MeterProvider.new + meter_provider.add_metric_reader(@metric_reader) + meter_provider + end + @meter = Couchbase::Metrics::OpenTelemetryMeter.new(@meter_provider) + + connect(Options::Cluster.new(tracer: @tracer, meter: @meter)) + @bucket = @cluster.bucket(env.bucket) + @collection = @bucket.default_collection + @parent_span = @tracer.request_span("parent_span") + end + + def teardown + disconnect + @tracer_provider.shutdown + @meter_provider.shutdown + end + + def assert_otel_span( + span_data, + name, + attributes: {}, + parent_span_id: nil, + status_code: OpenTelemetry::Trace::Status::UNSET + ) + assert_equal name, span_data.name + assert_equal :client, span_data.kind + assert_equal status_code, span_data.status.code + + if parent_span_id.nil? + assert_predicate span_data.parent_span_id.hex, :zero? + else + assert_equal parent_span_id, span_data.parent_span_id + end + + attributes.each do |key, value| + if value.nil? + assert span_data.attributes.key?(key), "Expected attribute #{key} to be present" + else + assert_equal value, span_data.attributes[key], "Expected attribute #{key} to have value #{value}" + end + end + end + + def test_opentelemetry_tracer + res = @collection.upsert(uniq_id(:otel_test), {foo: "bar"}, Options::Upsert.new(parent_span: @parent_span)) + + assert_predicate res.cas, :positive? + + @parent_span.finish + spans = @span_exporter.finished_spans.sort_by(&:start_timestamp) + + assert_otel_span( + spans[0], + "parent_span", + attributes: {}, + parent_span_id: nil, + ) + + assert_otel_span( + spans[1], + "upsert", + attributes: { + "db.system.name" => "couchbase", + "couchbase.cluster.name" => env.cluster_name, + "couchbase.cluster.uuid" => env.cluster_uuid, + "db.operation.name" => "upsert", + "db.namespace" => @bucket.name, + "couchbase.scope.name" => "_default", + "couchbase.collection.name" => "_default", + "couchbase.retries" => nil, + }, + parent_span_id: spans[0].span_id, + status_code: OpenTelemetry::Trace::Status::OK, + ) + + assert_otel_span( + spans[2], + "request_encoding", + attributes: { + "db.system.name" => "couchbase", + "couchbase.cluster.name" => env.cluster_name, + "couchbase.cluster.uuid" => env.cluster_uuid, + }, + parent_span_id: spans[1].span_id, + ) + + assert_otel_span( + spans[3], + "dispatch_to_server", + attributes: { + "db.system.name" => "couchbase", + "couchbase.cluster.name" => env.cluster_name, + "couchbase.cluster.uuid" => env.cluster_uuid, + "network.peer.address" => nil, + "network.peer.port" => nil, + "network.transport" => "tcp", + "server.address" => nil, + "server.port" => nil, + "couchbase.local_id" => nil, + }, + parent_span_id: spans[1].span_id, + ) + end + + def test_opentelemetry_meter + assert_raises(Error::DocumentNotFound) do + @collection.get(uniq_id(:does_not_exist)) + end + + @collection.insert(uniq_id(:otel_test), {foo: "bar"}) + + @metric_reader.force_flush + snapshots = @metric_exporter.metric_snapshots + + assert_equal 1, snapshots.size + + snapshot = snapshots[0] + + assert_equal "db.client.operation.duration", snapshot.name + assert_equal "s", snapshot.unit + assert_equal :histogram, snapshot.instrument_kind + assert_equal 2, snapshot.data_points.size + + snapshot.data_points.each_with_index do |p, idx| + assert_equal "couchbase", p.attributes["db.system.name"] + assert_equal env.cluster_name, p.attributes["couchbase.cluster.name"] + assert_equal env.cluster_uuid, p.attributes["couchbase.cluster.uuid"] + assert_equal env.bucket, p.attributes["db.namespace"] + assert_equal "_default", p.attributes["couchbase.scope.name"] + assert_equal "_default", p.attributes["couchbase.collection.name"] + assert_equal "kv", p.attributes["couchbase.service"] + + case idx + when 0 + assert_equal "get", p.attributes["db.operation.name"] + assert_equal "DocumentNotFound", p.attributes["error.type"] + when 1 + assert_equal "insert", p.attributes["db.operation.name"] + assert_nil p.attributes["error.type"] + end + end + end + end +end diff --git a/test/utils/tracing/test_span.rb b/test/utils/tracing/test_span.rb index 17a1c803..3d27eae9 100644 --- a/test/utils/tracing/test_span.rb +++ b/test/utils/tracing/test_span.rb @@ -24,6 +24,7 @@ class TestSpan < Couchbase::Tracing::RequestSpan attr_accessor :attributes attr_accessor :parent attr_accessor :children + attr_accessor :status_code def initialize(name, parent: nil, start_timestamp: nil) super() @@ -38,6 +39,10 @@ def set_attribute(key, value) @attributes[key] = value end + def status=(status_code) + @status_code = status_code + end + def finish(end_timestamp: nil) @end_time = end_timestamp.nil? ? Time.now : end_timestamp end From fd7b831f3634a1a123d678f8af07335fbcfc6b97 Mon Sep 17 00:00:00 2001 From: Sergey Avseyev Date: Thu, 22 Jan 2026 10:26:45 -0800 Subject: [PATCH 2/9] fix: improve regex pattern precision in CI workflow Update the gemspec replacement pattern from `/gemspec/` to `/gemspec$/` to ensure it only matches "gemspec" at the end of lines. This prevents potential partial matches that could cause incorrect Gemfile modifications during the test gem installation process. The $ anchor makes the regex more precise and robust for the automated gem unpacking and bundling workflow. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6d143411..ccd9eae1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -471,7 +471,7 @@ jobs: COUCHBASE_GEM_PATH=$(realpath couchbase-*.gem) UNPACKED_GEM_PATH=$(gem unpack ${COUCHBASE_GEM_PATH} | grep "Unpacked gem" | cut -d "'" -f 2) gem unpack --spec --target ${UNPACKED_GEM_PATH} ${COUCHBASE_GEM_PATH} - ruby -i.bak -pe "gsub(/gemspec/, 'gem \"couchbase\", path: \"${UNPACKED_GEM_PATH}\"')" Gemfile + ruby -i.bak -pe "gsub(/gemspec$/, 'gem \"couchbase\", path: \"${UNPACKED_GEM_PATH}\"')" Gemfile bundle install bundle exec ruby -r bundler/setup -r couchbase -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO' - name: Test From 570bfe3d8735e5f337f02855440d0b8cceffc949 Mon Sep 17 00:00:00 2001 From: Sergey Avseyev Date: Thu, 22 Jan 2026 12:52:21 -0800 Subject: [PATCH 3/9] fix(rcb_query.cxx): add missing bucket_name to error message The bucket_name parameter was missing from the fmt::format call in cb_Backend_query_index_build_deferred, causing the error message to not include the bucket name when reporting deferred index build failures. Which in turn caused build failure with recent GCC, where fmtlib tries to validate format strings compile-time. --- ext/rcb_query.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/rcb_query.cxx b/ext/rcb_query.cxx index 72fcf063..d14be73c 100644 --- a/ext/rcb_query.cxx +++ b/ext/rcb_query.cxx @@ -536,8 +536,8 @@ cb_Backend_query_index_build_deferred(VALUE self, cb_throw_error( resp.ctx, fmt::format(R"(unable to build deferred indexes on the bucket "{}" ({}: {}))", + req.bucket_name, first_error.code, - first_error.message)); } else { cb_throw_error( From 4e89bf17b6028407233657711940309f50ca9a3e Mon Sep 17 00:00:00 2001 From: Dimitris Christodoulou Date: Tue, 27 Jan 2026 12:24:08 +0000 Subject: [PATCH 4/9] Add Rakefile, README, API docs, change namespace of Otel tracer/meter --- .gitignore | 6 +- README.md | 2 +- couchbase-opentelemetry/.rubocop.yml | 1 + couchbase-opentelemetry/.yardopts | 3 + couchbase-opentelemetry/README.md | 83 +++++++++++++++++++ couchbase-opentelemetry/Rakefile | 38 +++++++++ .../couchbase-opentelemetry.gemspec | 2 +- .../couchbase/metrics/open_telemetry_meter.rb | 49 ----------- .../lib/couchbase/opentelemetry.rb | 18 ++++ .../lib/couchbase/opentelemetry/meter.rb | 82 ++++++++++++++++++ .../request_span.rb} | 7 +- .../request_tracer.rb} | 37 ++++++++- .../value_recorder.rb} | 4 +- .../lib/couchbase/opentelemetry/version.rb | 22 +++++ lib/couchbase.rb | 4 +- test/opentelemetry_test.rb | 11 ++- 16 files changed, 298 insertions(+), 71 deletions(-) create mode 100644 couchbase-opentelemetry/.rubocop.yml create mode 100644 couchbase-opentelemetry/.yardopts create mode 100644 couchbase-opentelemetry/README.md create mode 100644 couchbase-opentelemetry/Rakefile delete mode 100644 couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb create mode 100644 couchbase-opentelemetry/lib/couchbase/opentelemetry.rb create mode 100644 couchbase-opentelemetry/lib/couchbase/opentelemetry/meter.rb rename couchbase-opentelemetry/lib/couchbase/{tracing/open_telemetry_request_span.rb => opentelemetry/request_span.rb} (90%) rename couchbase-opentelemetry/lib/couchbase/{tracing/open_telemetry_request_tracer.rb => opentelemetry/request_tracer.rb} (50%) rename couchbase-opentelemetry/lib/couchbase/{metrics/open_telemetry_value_recorder.rb => opentelemetry/value_recorder.rb} (92%) create mode 100644 couchbase-opentelemetry/lib/couchbase/opentelemetry/version.rb diff --git a/.gitignore b/.gitignore index 78599282..0ba66d00 100644 --- a/.gitignore +++ b/.gitignore @@ -8,14 +8,14 @@ .byebug_history /.rakeTasks /.ruby-version -/.yardoc +/**/.yardoc /_yardoc/ /cmake-build-*/ /build* /coverage/ -/doc/ +/**/doc/ /ext/cache/ -/pkg/ +/**/pkg/ /test/reports/ /tmp/ /logs/ diff --git a/README.md b/README.md index e74e83be..298a7240 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Please attach version information to the ticket/post. To obtain this information ## Installation -The library has been tested with MRI 3.1, 3.2, 3.3 and 3.4. Supported platforms are Linux and MacOS. +The library has been tested with MRI 3.2, 3.3, 3.4 and 4.0. Supported platforms are Linux and MacOS. Add this line to your application's Gemfile: diff --git a/couchbase-opentelemetry/.rubocop.yml b/couchbase-opentelemetry/.rubocop.yml new file mode 100644 index 00000000..9ac5a99a --- /dev/null +++ b/couchbase-opentelemetry/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: ../.rubocop.yml \ No newline at end of file diff --git a/couchbase-opentelemetry/.yardopts b/couchbase-opentelemetry/.yardopts new file mode 100644 index 00000000..79286974 --- /dev/null +++ b/couchbase-opentelemetry/.yardopts @@ -0,0 +1,3 @@ +--no-progress --output-dir doc/couchbase-ruby-client-master lib - README.md +--tag couchbase.stability:"Stability" +--transitive-tag couchbase.stability diff --git a/couchbase-opentelemetry/README.md b/couchbase-opentelemetry/README.md new file mode 100644 index 00000000..d74136c6 --- /dev/null +++ b/couchbase-opentelemetry/README.md @@ -0,0 +1,83 @@ +# Couchbase Ruby Client OpenTelemetry Integration + +## Installation + +The library has been tested with MRI 3.2, 3.3, 3.4 and 4.0. Supported platforms are Linux and MacOS. + +Add this line to your application's Gemfile: + +```ruby +gem "couchbase-opentelemetry", "0.1.0" +``` + +And then execute: + + $ bundle install + +Or install it yourself as: + + $ gem install couchbase-opentelemetry + +## Usage + +Here is an example on how to set up Tracing and Metrics with OpenTelemetr: + +```ruby +require "couchbase" +require "couchbase/opentelemetry" + +require "opentelemetry-sdk" +require "opentelemetry-metrics-sdk" + +# Initialize a tracer provider +tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new +tracer_provider.add_span_processor( + OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new( + OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: "https://:/v1/traces") + ) +) + +# Initialize the Couchbase OpenTelemetry Request Tracer +tracer = Couchbase::OpenTelemetry::RequestTracer.new(tracer_provider) + +# Initialize a meter provider +meter_provider = OpenTelemetry::SDK::Metrics::MeterProvider.new +meter_provider.add_metric_reader( + OpenTelemetry::SDk::Metrics::Export::PeriodicMetricReader.new( + exporter: OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new( + endpoint: "https://:/v1/metrics" + ) + ) +) + +# Initialize the Couchbase OpenTelemetry Meter +meter = Couchbase::OpenTelemetry::Meter.new(meter_provider) + +# Configure tracer and meter in cluster options +options = Couchbase::Options::Cluster.new( + authenticator: Couchbase::PasswordAuthenticator.new("Administrator", "password") + tracer: tracer, + meter: meter +) + +# Initialize cluster instance +cluster = Cluster.connect("couchbase://127.0.0.1", options) +``` + +## License + +The gem is available as open source under the terms of the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0). + + Copyright 2025-Present Couchbase, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/couchbase-opentelemetry/Rakefile b/couchbase-opentelemetry/Rakefile new file mode 100644 index 00000000..6a3c8689 --- /dev/null +++ b/couchbase-opentelemetry/Rakefile @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "bundler/gem_tasks" +require "rubocop/rake_task" + +desc "Generate YARD documentation" +task :doc do + require "couchbase/opentelemetry/version" + input_dir = File.join(__dir__, "lib") + output_dir = File.join(__dir__, "doc", "couchbase-ruby-client-#{Couchbase::OpenTelemetry::VERSION}") + rm_rf output_dir + sh "bundle exec yard doc --no-progress --hide-api private --output-dir #{output_dir} #{input_dir} --main README.md" + puts "#{File.realpath(output_dir)}/index.html" +end + +desc "An alias for documentation generation task" +task :docs => :doc + +desc "Display stats on undocumented things" +task :undocumented => :doc do + sh "yard stats --list-undoc --compact" +end + +RuboCop::RakeTask.new diff --git a/couchbase-opentelemetry/couchbase-opentelemetry.gemspec b/couchbase-opentelemetry/couchbase-opentelemetry.gemspec index 3dc74d82..75d71d07 100644 --- a/couchbase-opentelemetry/couchbase-opentelemetry.gemspec +++ b/couchbase-opentelemetry/couchbase-opentelemetry.gemspec @@ -26,7 +26,7 @@ Gem::Specification.new do |spec| spec.description = "OpenTelemetry integration for the Couchbase Ruby Client" spec.homepage = "https://www.couchbase.com" spec.license = "Apache-2.0" - spec.required_ruby_version = "> 3.1" + spec.required_ruby_version = "> 3.2" spec.metadata = { "homepage_uri" => "https://docs.couchbase.com/ruby-sdk/current/hello-world/start-using-sdk.html", diff --git a/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb b/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb deleted file mode 100644 index 82b5525d..00000000 --- a/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_meter.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -# Copyright 2026-Present Couchbase, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require "couchbase/metrics/meter" -require "couchbase/errors" -require_relative "open_telemetry_value_recorder" - -require "opentelemetry-metrics-api" - -module Couchbase - module Metrics - class OpenTelemetryMeter < Meter - def initialize(meter_provider) - super() - @histogram_cache = Concurrent::Map.new - begin - @wrapped = meter_provider.meter("com.couchbase.client/ruby") - rescue StandardError => e - raise Error::MeterError.new("Failed to create OpenTelemetry Meter: #{e.message}", nil, e) - end - end - - def value_recorder(name, tags) - unit = tags.delete("__unit") - - otel_histogram = @histogram_cache.compute_if_absent(name) do - @wrapped.create_histogram(name, unit: unit) - end - - OpenTelemetryValueRecorder.new(otel_histogram, tags, unit: unit) - rescue StandardError => e - raise Error::MeterError.new("Failed to create OpenTelemetry Histogram: #{e.message}", nil, e) - end - end - end -end diff --git a/couchbase-opentelemetry/lib/couchbase/opentelemetry.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry.rb new file mode 100644 index 00000000..2e7e4764 --- /dev/null +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative "opentelemetry/request_tracer" +require_relative "opentelemetry/meter" diff --git a/couchbase-opentelemetry/lib/couchbase/opentelemetry/meter.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry/meter.rb new file mode 100644 index 00000000..8c7d08d0 --- /dev/null +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry/meter.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "couchbase/metrics/meter" +require "couchbase/errors" +require_relative "value_recorder" + +require "opentelemetry-metrics-api" + +module Couchbase + module OpenTelemetry + # @couchbase.stability + # **Uncommitted:** This API may change in the future, as Metrics in OpenTelemetry Ruby are currently in development. + # + # See the {https://opentelemetry.io/docs/languages/ruby/#status-and-releases OpenTelemetry Ruby documentation} for more information. + class Meter < ::Couchbase::Metrics::Meter + # Initializes a Couchbase OpenTelemetry Meter + # + # @param [::OpenTelemetry::Metrics::MeterProvider] meter_provider The OpenTelemetry meter provider + # + # @raise [Couchbase::Error::MeterError] if the meter cannot be created for any reason + # + # @example Initializing a Couchbase OpenTelemetry Meter with an OTLP Exporter + # require "opentelemetry-metrics-sdk" + # + # # Initialize a meter provider + # meter_provider = ::OpenTelemetry::SDK::Metrics::MeterProvider.new + # meter_provider.add_metric_reader( + # ::OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new( + # exporter: ::OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new( + # endpoint: "https://:/v1/metrics" + # ) + # ) + # ) + # # Initialize the Couchbase OpenTelemetry Meter + # meter = Couchbase::OpenTelemetry::Meter.new(meter_provider) + # + # # Set the meter in the cluster options + # options = Couchbase::Options::Cluster.new( + # authenticator: Couchbase::PasswordAuthenticator.new("Administrator", "password") + # meter: meter + # ) + # + # @see https://www.rubydoc.info/gems/opentelemetry-metrics-sdk/OpenTelemetry/SDK/Metrics/MeterProvider + # opentelemetry-metrics-sdk API Reference + def initialize(meter_provider) + super() + @histogram_cache = Concurrent::Map.new + begin + @wrapped = meter_provider.meter("com.couchbase.client/ruby") + rescue StandardError => e + raise Error::MeterError.new("Failed to create OpenTelemetry Meter: #{e.message}", nil, e) + end + end + + def value_recorder(name, tags) + unit = tags.delete("__unit") + + otel_histogram = @histogram_cache.compute_if_absent(name) do + @wrapped.create_histogram(name, unit: unit) + end + + ValueRecorder.new(otel_histogram, tags, unit: unit) + rescue StandardError => e + raise Error::MeterError.new("Failed to create OpenTelemetry Histogram: #{e.message}", nil, e) + end + end + end +end diff --git a/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_span.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_span.rb similarity index 90% rename from couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_span.rb rename to couchbase-opentelemetry/lib/couchbase/opentelemetry/request_span.rb index af17b6d6..5075ee7f 100644 --- a/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_span.rb +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_span.rb @@ -19,8 +19,8 @@ require "opentelemetry-api" module Couchbase - module Tracing - class OpenTelemetryRequestSpan < RequestSpan + module OpenTelemetry + class RequestSpan < ::Couchbase::Tracing::RequestSpan def initialize(span) super() @@ -28,7 +28,8 @@ def initialize(span) end def set_attribute(key, value) - @wrapped.set_attribute(key, value) + @wrapped + .set_attribute(key, value) end def status=(status_code) diff --git a/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_tracer.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_tracer.rb similarity index 50% rename from couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_tracer.rb rename to couchbase-opentelemetry/lib/couchbase/opentelemetry/request_tracer.rb index 776f7e70..f00ceb10 100644 --- a/couchbase-opentelemetry/lib/couchbase/tracing/open_telemetry_request_tracer.rb +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_tracer.rb @@ -18,11 +18,40 @@ require "couchbase/tracing/request_tracer" require "couchbase/errors" -require_relative "open_telemetry_request_span" +require_relative "request_span" module Couchbase - module Tracing - class OpenTelemetryRequestTracer < RequestTracer + module OpenTelemetry + class RequestTracer < ::Couchbase::Tracing::RequestTracer + # Initializes a Couchbase OpenTelemetry Request Tracer + # + # @param [::OpenTelemetry::Trace::TracerProvider] tracer_provider The OpenTelemetry tracer provider + # + # @raise [Couchbase::Error::TracerError] if the tracer cannot be created for any reason + # + # @example Initializing a Couchbase OpenTelemetry Request Tracer with an OTLP Exporter + # require "opentelemetry-sdk" + # + # # Initialize a trqacer provider + # tracer_provider = ::OpenTelemetry::SDK::Trace::TracerProvider.new + # tracer_provider.add_span_processor( + # ::OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new( + # exporter: ::OpenTelemetry::Exporter::OTLP::Exporter.new( + # endpoint: "https://:/v1/traces" + # ) + # ) + # ) + # # Initialize the Couchbase OpenTelemetry Request Tracer + # tracer = Couchbase::OpenTelemetry::RequestTracer.new(tracer_provider) + # + # # Set the tracer in the cluster options + # options = Couchbase::Options::Cluster.new( + # authenticator: Couchbase::PasswordAuthenticator.new("Administrator", "password") + # tracer: tracer + # ) + # + # @see https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry/SDK/Trace/TracerProvider + # opentelemetry-sdk API Reference def initialize(tracer_provider) super() begin @@ -34,7 +63,7 @@ def initialize(tracer_provider) def request_span(name, parent: nil, start_timestamp: nil) parent_context = parent.nil? ? nil : ::OpenTelemetry::Trace.context_with_span(parent.instance_variable_get(:@wrapped)) - OpenTelemetryRequestSpan.new( + RequestSpan.new( @wrapped.start_span( name, with_parent: parent_context, diff --git a/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_value_recorder.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry/value_recorder.rb similarity index 92% rename from couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_value_recorder.rb rename to couchbase-opentelemetry/lib/couchbase/opentelemetry/value_recorder.rb index 28bbfd69..13571e9f 100644 --- a/couchbase-opentelemetry/lib/couchbase/metrics/open_telemetry_value_recorder.rb +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry/value_recorder.rb @@ -17,8 +17,8 @@ require "couchbase/metrics/value_recorder" module Couchbase - module Metrics - class OpenTelemetryValueRecorder < ValueRecorder + module OpenTelemetry + class ValueRecorder < ::Couchbase::Metrics::ValueRecorder def initialize(recorder, tags, unit: nil) super() @wrapped = recorder diff --git a/couchbase-opentelemetry/lib/couchbase/opentelemetry/version.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry/version.rb new file mode 100644 index 00000000..d5a16cd8 --- /dev/null +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry/version.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Copyright 2026-Present Couchbase, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Couchbase + module OpenTelemetry + # Version of the Couchbase OpenTelemetry integration gem + VERSION = "0.1.0" + end +end diff --git a/lib/couchbase.rb b/lib/couchbase.rb index 7aea5fcf..5b09db63 100644 --- a/lib/couchbase.rb +++ b/lib/couchbase.rb @@ -25,8 +25,8 @@ # @!macro uncommitted # @couchbase.stability -# Uncommitted: This API may change in the future. +# **Uncommitted:** This API may change in the future. # # @!macro volatile # @couchbase.stability -# Volatile: This API is subject to change at any time. +# **Volatile:** This API is subject to change at any time. diff --git a/test/opentelemetry_test.rb b/test/opentelemetry_test.rb index c92fcba4..949051a5 100644 --- a/test/opentelemetry_test.rb +++ b/test/opentelemetry_test.rb @@ -16,8 +16,7 @@ require_relative "test_helper" -require "couchbase/tracing/open_telemetry_request_tracer" -require "couchbase/metrics/open_telemetry_meter" +require "couchbase/opentelemetry" require "opentelemetry-sdk" require "opentelemetry-metrics-sdk" @@ -36,7 +35,7 @@ def setup ) tracer_provider end - @tracer = Couchbase::Tracing::OpenTelemetryRequestTracer.new(@tracer_provider) + @tracer = Couchbase::OpenTelemetry::RequestTracer.new(@tracer_provider) @metric_exporter = ::OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter.new @metric_reader = ::OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(exporter: @metric_exporter) @@ -46,7 +45,7 @@ def setup meter_provider.add_metric_reader(@metric_reader) meter_provider end - @meter = Couchbase::Metrics::OpenTelemetryMeter.new(@meter_provider) + @meter = Couchbase::OpenTelemetry::Meter.new(@meter_provider) connect(Options::Cluster.new(tracer: @tracer, meter: @meter)) @bucket = @cluster.bucket(env.bucket) @@ -65,7 +64,7 @@ def assert_otel_span( name, attributes: {}, parent_span_id: nil, - status_code: OpenTelemetry::Trace::Status::UNSET + status_code: ::OpenTelemetry::Trace::Status::UNSET ) assert_equal name, span_data.name assert_equal :client, span_data.kind @@ -115,7 +114,7 @@ def test_opentelemetry_tracer "couchbase.retries" => nil, }, parent_span_id: spans[0].span_id, - status_code: OpenTelemetry::Trace::Status::OK, + status_code: ::OpenTelemetry::Trace::Status::OK, ) assert_otel_span( From 6887e5f048c6b30671886655ac6caf9b40bf47fe Mon Sep 17 00:00:00 2001 From: Dimitris Christodoulou Date: Tue, 27 Jan 2026 14:05:27 +0000 Subject: [PATCH 5/9] GHA: Include couchbase-opentelemetry in uploaded artifacts --- .github/workflows/tests.yml | 62 +++++++++++++++++++++++++------ .yardopts | 1 + couchbase-opentelemetry/.yardopts | 1 + 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ccd9eae1..6e2ac1b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,7 @@ jobs: runs-on: ubuntu-22.04 outputs: gem_version: ${{ steps.build_gem.outputs.gem_version }} + otel_gem_version: ${{ steps.build_otel_gem.outputs.otel_gem_version }} steps: - uses: actions/checkout@v4 with: @@ -30,7 +31,7 @@ jobs: with: ruby-version: 3.3 bundler-cache: true - - name: Build + - name: Build couchbase gem id: build_gem run: | COMMITS_SINCE_LAST_TAG=$(git describe --tags --always --long | awk -F '-' '{print $2}') @@ -38,7 +39,14 @@ jobs: GEM_VERSION=$(ruby -r ./lib/couchbase/version.rb -e "puts Couchbase::VERSION[:sdk]") echo "gem_version=${GEM_VERSION}" >> "$GITHUB_OUTPUT" bundle exec rake build - - name: RDoc + - name: Build couchbase-opentelemetry gem + id: build_otel_gem + run: | + cd couchbase-opentelemetry + OTEL_GEM_VERSION=$(ruby -r ./lib/couchbase/opentelemetry/version.rb -e "puts Couchbase::OpenTelemetry::VERSION") + echo "otel_gem_version=${OTEL_GEM_VERSION}" >> "$GITHUB_OUTPUT" + bundle exec rake build + - name: Generate documentation for the couchbase gem run: | cat > patch-readme.rb < patch-readme.rb < Date: Tue, 27 Jan 2026 15:14:55 +0000 Subject: [PATCH 6/9] GHA: Install couchbase-opentelemetry gem for tests --- .github/workflows/tests.yml | 74 +++++++++++++------ couchbase-opentelemetry/Rakefile | 2 +- .../couchbase-opentelemetry.gemspec | 3 +- .../lib/couchbase/opentelemetry.rb | 1 + .../couchbase/opentelemetry/request_span.rb | 3 +- 5 files changed, 57 insertions(+), 26 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6e2ac1b0..375a4ee2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -495,6 +495,9 @@ jobs: - uses: actions/download-artifact@v4 with: name: couchbase-${{ needs.source.outputs.gem_version }}-x86_64-linux + - uses: actions/download-artifact@v4 + with: + name: couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }} - uses: actions/download-artifact@v4 with: name: scripts-${{ needs.source.outputs.gem_version }} @@ -506,12 +509,17 @@ jobs: ruby-version: ${{ matrix.ruby }} - name: Install run: | - COUCHBASE_GEM_PATH=$(realpath couchbase-*.gem) - UNPACKED_GEM_PATH=$(gem unpack ${COUCHBASE_GEM_PATH} | grep "Unpacked gem" | cut -d "'" -f 2) - gem unpack --spec --target ${UNPACKED_GEM_PATH} ${COUCHBASE_GEM_PATH} - ruby -i.bak -pe "gsub(/gemspec$/, 'gem \"couchbase\", path: \"${UNPACKED_GEM_PATH}\"')" Gemfile + COUCHBASE_GEM_PATH=$(realpath couchbase-${{ needs.source.outputs.gem_version }}-*.gem) + COUCHBASE_OPENTELEMETRY_GEM_PATH=$(realpath couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }}.gem) + mkdir -p local-gem-repo/gems + mv ${COUCHBASE_GEM_PATH} local-gem-repo/gems + mv ${COUCHBASE_OPENTELEMETRY_GEM_PATH} local-gem-repo/gems + GEM_REPO_PATH=$(realpath local-gem-repo) + gem generate_index --directory local-gem-repo + ruby -i.bak -pe "gsub(/gemspec$/, 'gem \"couchbase\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile + ruby -i.bak -pe "gsub(/gemspec path: \"couchbase-opentelemetry\"$/, 'gem \"couchbase-opentelemetry\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile bundle install - bundle exec ruby -r bundler/setup -r couchbase -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO' + bundle exec ruby -r bundler/setup -r couchbase -r couchbase/opentelemetry -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO, {otel: Couchbase::OpenTelemetry::VERSION}' - name: Test env: CB_CAVES_LOGS_DIR: caves-logs @@ -556,6 +564,9 @@ jobs: - uses: actions/download-artifact@v4 with: name: couchbase-${{ needs.source.outputs.gem_version }}-arm64-darwin + - uses: actions/download-artifact@v4 + with: + name: couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }} - uses: actions/download-artifact@v4 with: name: scripts-${{ needs.source.outputs.gem_version }} @@ -568,14 +579,17 @@ jobs: - name: Install run: | set -xe - COUCHBASE_GEM_PATH=$(realpath couchbase-*.gem) - UNPACKED_GEM_PATH=$(gem unpack ${COUCHBASE_GEM_PATH} | grep "Unpacked gem" | cut -d "'" -f 2) - gem unpack --spec --target ${UNPACKED_GEM_PATH} ${COUCHBASE_GEM_PATH} - ruby -i.bak -pe "gsub(/gemspec/, 'gem \"couchbase\", path: \"${UNPACKED_GEM_PATH}\"')" Gemfile - find . - ls -ld ${UNPACKED_GEM_PATH} + COUCHBASE_GEM_PATH=$(realpath couchbase-${{ needs.source.outputs.gem_version }}-*.gem) + COUCHBASE_OPENTELEMETRY_GEM_PATH=$(realpath couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }}.gem) + mkdir -p local-gem-repo/gems + mv ${COUCHBASE_GEM_PATH} local-gem-repo/gems + mv ${COUCHBASE_OPENTELEMETRY_GEM_PATH} local-gem-repo/gems + GEM_REPO_PATH=$(realpath local-gem-repo) + gem generate_index --directory local-gem-repo + ruby -i.bak -pe "gsub(/gemspec$/, 'gem \"couchbase\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile + ruby -i.bak -pe "gsub(/gemspec path: \"couchbase-opentelemetry\"$/, 'gem \"couchbase-opentelemetry\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile bundle install - bundle exec ruby -r bundler/setup -r couchbase -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO' + bundle exec ruby -r bundler/setup -r couchbase -r couchbase/opentelemetry -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO, {otel: Couchbase::OpenTelemetry::VERSION}' - name: Test env: CB_CAVES_LOGS_DIR: caves-logs @@ -629,6 +643,9 @@ jobs: - uses: actions/download-artifact@v4 with: name: couchbase-${{ needs.source.outputs.gem_version }}-x86_64-darwin + - uses: actions/download-artifact@v4 + with: + name: couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }} - uses: actions/download-artifact@v4 with: name: scripts-${{ needs.source.outputs.gem_version }} @@ -640,12 +657,17 @@ jobs: ruby-version: ${{ matrix.ruby }} - name: Install run: | - COUCHBASE_GEM_PATH=$(realpath couchbase-*.gem) - UNPACKED_GEM_PATH=$(gem unpack ${COUCHBASE_GEM_PATH} | grep "Unpacked gem" | cut -d "'" -f 2) - gem unpack --spec --target ${UNPACKED_GEM_PATH} ${COUCHBASE_GEM_PATH} - ruby -i.bak -pe "gsub(/gemspec/, 'gem \"couchbase\", path: \"${UNPACKED_GEM_PATH}\"')" Gemfile + COUCHBASE_GEM_PATH=$(realpath couchbase-${{ needs.source.outputs.gem_version }}-*.gem) + COUCHBASE_OPENTELEMETRY_GEM_PATH=$(realpath couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }}.gem) + mkdir -p local-gem-repo/gems + mv ${COUCHBASE_GEM_PATH} local-gem-repo/gems + mv ${COUCHBASE_OPENTELEMETRY_GEM_PATH} local-gem-repo/gems + GEM_REPO_PATH=$(realpath local-gem-repo) + gem generate_index --directory local-gem-repo + ruby -i.bak -pe "gsub(/gemspec$/, 'gem \"couchbase\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile + ruby -i.bak -pe "gsub(/gemspec path: \"couchbase-opentelemetry\"$/, 'gem \"couchbase-opentelemetry\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile bundle install - bundle exec ruby -r bundler/setup -r couchbase -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO' + bundle exec ruby -r bundler/setup -r couchbase -r couchbase/opentelemetry -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO, {otel: Couchbase::OpenTelemetry::VERSION}' - name: Test env: CB_CAVES_LOGS_DIR: caves-logs @@ -734,6 +756,9 @@ jobs: - uses: actions/download-artifact@v4 with: name: couchbase-${{ needs.source.outputs.gem_version }}-x86_64-linux + - uses: actions/download-artifact@v4 + with: + name: couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }} - uses: actions/download-artifact@v4 with: name: scripts-${{ needs.source.outputs.gem_version }} @@ -745,12 +770,17 @@ jobs: ruby-version: 3.3 - name: Install run: | - COUCHBASE_GEM_PATH=$(realpath couchbase-*.gem) - UNPACKED_GEM_PATH=$(gem unpack ${COUCHBASE_GEM_PATH} | grep "Unpacked gem" | cut -d "'" -f 2) - gem unpack --spec --target ${UNPACKED_GEM_PATH} ${COUCHBASE_GEM_PATH} - ruby -i.bak -pe "gsub(/gemspec/, 'gem \"couchbase\", path: \"${UNPACKED_GEM_PATH}\"')" Gemfile + COUCHBASE_GEM_PATH=$(realpath couchbase-${{ needs.source.outputs.gem_version }}-*.gem) + COUCHBASE_OPENTELEMETRY_GEM_PATH=$(realpath couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }}.gem) + mkdir -p local-gem-repo/gems + mv ${COUCHBASE_GEM_PATH} local-gem-repo/gems + mv ${COUCHBASE_OPENTELEMETRY_GEM_PATH} local-gem-repo/gems + GEM_REPO_PATH=$(realpath local-gem-repo) + gem generate_index --directory local-gem-repo + ruby -i.bak -pe "gsub(/gemspec$/, 'gem \"couchbase\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile + ruby -i.bak -pe "gsub(/gemspec path: \"couchbase-opentelemetry\"$/, 'gem \"couchbase-opentelemetry\", source: \"file://${GEM_REPO_PATH}\"')" Gemfile bundle install - bundle exec ruby -r bundler/setup -r couchbase -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO' + bundle exec ruby -r bundler/setup -r couchbase -r couchbase/opentelemetry -e 'pp Couchbase::VERSION, Couchbase::BUILD_INFO, {otel: Couchbase::OpenTelemetry::VERSION}' - name: Test env: TEST_SERVER_VERSION: ${{ matrix.server }} diff --git a/couchbase-opentelemetry/Rakefile b/couchbase-opentelemetry/Rakefile index 6a3c8689..b0800ce8 100644 --- a/couchbase-opentelemetry/Rakefile +++ b/couchbase-opentelemetry/Rakefile @@ -21,7 +21,7 @@ desc "Generate YARD documentation" task :doc do require "couchbase/opentelemetry/version" input_dir = File.join(__dir__, "lib") - output_dir = File.join(__dir__, "doc", "couchbase-ruby-client-#{Couchbase::OpenTelemetry::VERSION}") + output_dir = File.join(__dir__, "doc", "couchbase-ruby-client-opentelemetry-#{Couchbase::OpenTelemetry::VERSION}") rm_rf output_dir sh "bundle exec yard doc --no-progress --hide-api private --output-dir #{output_dir} #{input_dir} --main README.md" puts "#{File.realpath(output_dir)}/index.html" diff --git a/couchbase-opentelemetry/couchbase-opentelemetry.gemspec b/couchbase-opentelemetry/couchbase-opentelemetry.gemspec index 75d71d07..71111448 100644 --- a/couchbase-opentelemetry/couchbase-opentelemetry.gemspec +++ b/couchbase-opentelemetry/couchbase-opentelemetry.gemspec @@ -16,10 +16,11 @@ lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "couchbase/opentelemetry/version" Gem::Specification.new do |spec| spec.name = "couchbase-opentelemetry" - spec.version = "0.0.1" + spec.version = Couchbase::OpenTelemetry::VERSION spec.authors = ["Sergey Avseyev"] spec.email = ["sergey.avseyev@gmail.com"] spec.summary = "OpenTelemetry integration for the Couchbase Ruby Client" diff --git a/couchbase-opentelemetry/lib/couchbase/opentelemetry.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry.rb index 2e7e4764..bdb02c6e 100644 --- a/couchbase-opentelemetry/lib/couchbase/opentelemetry.rb +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry.rb @@ -16,3 +16,4 @@ require_relative "opentelemetry/request_tracer" require_relative "opentelemetry/meter" +require_relative "opentelemetry/version" diff --git a/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_span.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_span.rb index 5075ee7f..1ffcf69d 100644 --- a/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_span.rb +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_span.rb @@ -28,8 +28,7 @@ def initialize(span) end def set_attribute(key, value) - @wrapped - .set_attribute(key, value) + @wrapped.set_attribute(key, value) end def status=(status_code) From 3a44e025bbfe76934a226b7e5e646c0ac009f9ba Mon Sep 17 00:00:00 2001 From: Dimitris Christodoulou Date: Tue, 27 Jan 2026 16:24:42 +0000 Subject: [PATCH 7/9] Otel test: Add check for cluster label support --- test/opentelemetry_test.rb | 75 ++++++++++++++++++++++++-------------- test/utils/metrics.rb | 1 + 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/test/opentelemetry_test.rb b/test/opentelemetry_test.rb index 949051a5..dc3e3fea 100644 --- a/test/opentelemetry_test.rb +++ b/test/opentelemetry_test.rb @@ -100,48 +100,60 @@ def test_opentelemetry_tracer parent_span_id: nil, ) + expected_attributes = { + "db.system.name" => "couchbase", + "db.operation.name" => "upsert", + "db.namespace" => @bucket.name, + "couchbase.scope.name" => "_default", + "couchbase.collection.name" => "_default", + "couchbase.retries" => nil, + } + if env.server_version.supports_cluster_labels? + expected_attributes["couchbase.cluster.name"] = env.cluster_name + expected_attributes["couchbase.cluster.uuid"] = env.cluster_uuid + end + assert_otel_span( spans[1], "upsert", - attributes: { - "db.system.name" => "couchbase", - "couchbase.cluster.name" => env.cluster_name, - "couchbase.cluster.uuid" => env.cluster_uuid, - "db.operation.name" => "upsert", - "db.namespace" => @bucket.name, - "couchbase.scope.name" => "_default", - "couchbase.collection.name" => "_default", - "couchbase.retries" => nil, - }, + attributes: expected_attributes, parent_span_id: spans[0].span_id, status_code: ::OpenTelemetry::Trace::Status::OK, ) + expected_attributes = { + "db.system.name" => "couchbase", + } + if env.server_version.supports_cluster_labels? + expected_attributes["couchbase.cluster.name"] = env.cluster_name + expected_attributes["couchbase.cluster.uuid"] = env.cluster_uuid + end + assert_otel_span( spans[2], "request_encoding", - attributes: { - "db.system.name" => "couchbase", - "couchbase.cluster.name" => env.cluster_name, - "couchbase.cluster.uuid" => env.cluster_uuid, - }, + attributes: expected_attributes, parent_span_id: spans[1].span_id, ) + expected_attributes = { + "db.system.name" => "couchbase", + "network.peer.address" => nil, + "network.peer.port" => nil, + "network.transport" => "tcp", + "server.address" => nil, + "server.port" => nil, + "couchbase.local_id" => nil, + } + if env.server_version.supports_cluster_labels? + expected_attributes["couchbase.cluster.name"] = env.cluster_name + expected_attributes["couchbase.cluster.uuid"] = env.cluster_uuid + end + assert_otel_span( spans[3], "dispatch_to_server", - attributes: { - "db.system.name" => "couchbase", - "couchbase.cluster.name" => env.cluster_name, - "couchbase.cluster.uuid" => env.cluster_uuid, - "network.peer.address" => nil, - "network.peer.port" => nil, - "network.transport" => "tcp", - "server.address" => nil, - "server.port" => nil, - "couchbase.local_id" => nil, - }, + attributes: expected_attributes, parent_span_id: spans[1].span_id, ) end @@ -167,8 +179,15 @@ def test_opentelemetry_meter snapshot.data_points.each_with_index do |p, idx| assert_equal "couchbase", p.attributes["db.system.name"] - assert_equal env.cluster_name, p.attributes["couchbase.cluster.name"] - assert_equal env.cluster_uuid, p.attributes["couchbase.cluster.uuid"] + + if env.server_version.supports_cluster_labels? + assert_equal env.cluster_name, p.attributes["couchbase.cluster.name"] + assert_equal env.cluster_uuid, p.attributes["couchbase.cluster.uuid"] + else + assert_nil p.attributes["couchbase.cluster.name"] + assert_nil p.attributes["couchbase.cluster.uuid"] + end + assert_equal env.bucket, p.attributes["db.namespace"] assert_equal "_default", p.attributes["couchbase.scope.name"] assert_equal "_default", p.attributes["couchbase.collection.name"] diff --git a/test/utils/metrics.rb b/test/utils/metrics.rb index b0d8909b..4952520e 100644 --- a/test/utils/metrics.rb +++ b/test/utils/metrics.rb @@ -30,6 +30,7 @@ def assert_operation_metrics( attributes = { "db.system.name" => "couchbase", "db.operation.name" => operation_name, + "__unit" => "s", } if env.server_version.supports_cluster_labels? From f3cb3ee8dd9013649bafb70bcce7c5aa784e1c38 Mon Sep 17 00:00:00 2001 From: Dimitris Christodoulou Date: Tue, 27 Jan 2026 17:42:01 +0000 Subject: [PATCH 8/9] Fix alpine build --- .github/workflows/tests.yml | 7 +++++++ test/utils/metrics.rb | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 375a4ee2..97c796d3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,6 +97,9 @@ jobs: Gemfile bin/**/* task/**/* + couchbase-opentelemetry/Rakefile + couchbase-opentelemetry/couchbase-opentelemetry*.gemspec + couchbase-opentelemetry/lib/couchbase/opentelemetry/version.rb - name: Upload artifact - tests and test data uses: actions/upload-artifact@v4 with: @@ -169,6 +172,10 @@ jobs: with: path: pkg name: couchbase-${{ needs.source.outputs.gem_version }} + - uses: actions/download-artifact@v4 + with: + path: pkg + name: couchbase-opentelemetry-${{ needs.source.outputs.otel_gem_version }} - name: Build gem env: SUPPORTED_RUBY_VERSIONS: "3.2 3.3 3.4 4.0" diff --git a/test/utils/metrics.rb b/test/utils/metrics.rb index 4952520e..ab84ce3d 100644 --- a/test/utils/metrics.rb +++ b/test/utils/metrics.rb @@ -56,5 +56,5 @@ def assert_operation_metrics( end assert_equal count, values.size, - "Expected exactly #{count} value for meter db.client.operation.duration and attributes #{attributes.inspect}" + "Expected exactly #{count} value(s) for meter db.client.operation.duration and attributes #{attributes.inspect}" end From c5d92e7558d097fc84824a5ed0a586550306d73a Mon Sep 17 00:00:00 2001 From: Dimitris Christodoulou <36637689+DemetrisChr@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:44:53 +0000 Subject: [PATCH 9/9] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- couchbase-opentelemetry/README.md | 4 ++-- .../lib/couchbase/opentelemetry/request_tracer.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/couchbase-opentelemetry/README.md b/couchbase-opentelemetry/README.md index d74136c6..371b764e 100644 --- a/couchbase-opentelemetry/README.md +++ b/couchbase-opentelemetry/README.md @@ -20,7 +20,7 @@ Or install it yourself as: ## Usage -Here is an example on how to set up Tracing and Metrics with OpenTelemetr: +Here is an example on how to set up Tracing and Metrics with OpenTelemetry: ```ruby require "couchbase" @@ -43,7 +43,7 @@ tracer = Couchbase::OpenTelemetry::RequestTracer.new(tracer_provider) # Initialize a meter provider meter_provider = OpenTelemetry::SDK::Metrics::MeterProvider.new meter_provider.add_metric_reader( - OpenTelemetry::SDk::Metrics::Export::PeriodicMetricReader.new( + OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new( exporter: OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new( endpoint: "https://:/v1/metrics" ) diff --git a/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_tracer.rb b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_tracer.rb index f00ceb10..d18f6d4a 100644 --- a/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_tracer.rb +++ b/couchbase-opentelemetry/lib/couchbase/opentelemetry/request_tracer.rb @@ -32,7 +32,7 @@ class RequestTracer < ::Couchbase::Tracing::RequestTracer # @example Initializing a Couchbase OpenTelemetry Request Tracer with an OTLP Exporter # require "opentelemetry-sdk" # - # # Initialize a trqacer provider + # # Initialize a tracer provider # tracer_provider = ::OpenTelemetry::SDK::Trace::TracerProvider.new # tracer_provider.add_span_processor( # ::OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(