Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b78e04a
RUBY-3612 OpenTelemetry support
comandeo-mongo Jun 26, 2025
87e8ac3
wip
comandeo-mongo Aug 12, 2025
d6f7509
With nesting
comandeo-mongo Aug 15, 2025
10f5f6c
Disable cursor nesting
comandeo-mongo Aug 15, 2025
bf0dd5d
Disable cursor nesting
comandeo-mongo Aug 15, 2025
f059514
Spec runner
comandeo-mongo Aug 21, 2025
5310ffb
wip
comandeo-mongo Sep 1, 2025
51eb11a
more specs
comandeo-mongo Sep 2, 2025
564a9b9
wip: fresh spec tests
comandeo Oct 31, 2025
2cfeb87
wip: fresh spec tests
comandeo Nov 1, 2025
1bd96fd
Pass transactions tests
comandeo Nov 2, 2025
814bb1e
Skip unimplemented tests
comandeo Nov 2, 2025
c91ccac
Add documenting comments
comandeo Nov 2, 2025
821495d
Refactor command tracer
comandeo Nov 2, 2025
d6571d9
Fix rubocop complaints
comandeo Nov 3, 2025
23a3601
Fix lsid
comandeo Nov 5, 2025
9108e72
Fix dependencies
comandeo Nov 11, 2025
eac5d9c
Merge branch 'master' into 3612-otel
comandeo Nov 12, 2025
75e5370
Merge remote-tracking branch 'upstream/master' into 3612-otel
comandeo-mongo Dec 8, 2025
8f8e137
wip
comandeo-mongo Dec 9, 2025
e047cf0
wip
comandeo-mongo Dec 9, 2025
f2f902e
wip
comandeo-mongo Dec 10, 2025
cf5395d
wip
comandeo-mongo Dec 10, 2025
1ffd096
wip
comandeo-mongo Dec 10, 2025
e1e80c4
wip
comandeo-mongo Dec 10, 2025
15c81ea
wip
comandeo-mongo Dec 11, 2025
83daac8
wip
comandeo-mongo Dec 11, 2025
5846980
wip
comandeo-mongo Dec 11, 2025
6aae681
wip
comandeo-mongo Dec 11, 2025
eae217c
wip
comandeo-mongo Dec 11, 2025
1230598
Fix code review remarks
comandeo-mongo Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ functions:
TEST_CMD="bundle exec rspec spec/spec_tests/client_side_operations_timeout_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
.evergreen/run-tests.sh

"run OTel tests":
- command: shell.exec
type: test
params:
shell: bash
working_dir: "src"
script: |
${PREPARE_SHELL}
export OTEL_SPEC_TESTS=1
unset TOPOLOGY
export TOPOLOGY=${MLAUNCH_TOPOLOGY}
TEST_CMD="bundle exec rspec spec/spec_tests/open_telemetry_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
.evergreen/run-tests.sh

"export FLE credentials":
- command: shell.exec
type: test
Expand Down Expand Up @@ -641,6 +655,9 @@ tasks:
- name: "test-csot"
commands:
- func: "run CSOT tests"
- name: "test-otel"
commands:
- func: "run OTel tests"
- name: "test-fle"
commands:
- func: "export FLE credentials"
Expand Down Expand Up @@ -1257,6 +1274,16 @@ buildvariants:
tasks:
- name: test-csot

- matrix_name: OTel
matrix_spec:
ruby: "ruby-3.3"
mongodb-version: "8.0"
topology: replica-set-single-node
os: ubuntu2204
display_name: "OTel - ${mongodb-version}"
tasks:
- name: test-otel

- matrix_name: "no-retry-reads"
matrix_spec:
retry-reads: no-retry-reads
Expand Down
17 changes: 17 additions & 0 deletions .evergreen/config/common.yml.erb
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,20 @@ functions:
TEST_CMD="bundle exec rspec spec/spec_tests/client_side_operations_timeout_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
.evergreen/run-tests.sh

"run OTel tests":
- command: shell.exec
type: test
params:
shell: bash
working_dir: "src"
script: |
${PREPARE_SHELL}
export OTEL_SPEC_TESTS=1
unset TOPOLOGY
export TOPOLOGY=${MLAUNCH_TOPOLOGY}
TEST_CMD="bundle exec rspec spec/spec_tests/open_telemetry_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
.evergreen/run-tests.sh

"export FLE credentials":
- command: shell.exec
type: test
Expand Down Expand Up @@ -638,6 +652,9 @@ tasks:
- name: "test-csot"
commands:
- func: "run CSOT tests"
- name: "test-otel"
commands:
- func: "run OTel tests"
- name: "test-fle"
commands:
- func: "export FLE credentials"
Expand Down
10 changes: 10 additions & 0 deletions .evergreen/config/standard.yml.erb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ buildvariants:
tasks:
- name: test-csot

- matrix_name: OTel
matrix_spec:
ruby: <%= latest_ruby %>
mongodb-version: <%= latest_stable_mdb %>
topology: replica-set-single-node
os: ubuntu2204
display_name: "OTel - ${mongodb-version}"
tasks:
- name: test-otel

- matrix_name: "no-retry-reads"
matrix_spec:
retry-reads: no-retry-reads
Expand Down
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,6 @@ RSpec/ExampleLength:

RSpec/MessageSpies:
EnforcedStyle: receive

RSpec/NamedSubject:
Enabled: false
3 changes: 3 additions & 0 deletions gemfiles/standard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
def standard_dependencies
gem 'yard', '>= 0.9.35'
gem 'ffi'
gem 'opentelemetry-sdk'

group :development, :testing do
gem 'jruby-openssl', platforms: :jruby
Expand Down Expand Up @@ -40,6 +41,8 @@ def standard_dependencies
gem 'ruby-debug-ide'
end
end

gem 'opentelemetry-api'
end

group :testing do
Expand Down
1 change: 1 addition & 0 deletions lib/mongo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
require 'mongo/socket'
require 'mongo/srv'
require 'mongo/timeout'
require 'mongo/tracing'
require 'mongo/uri'
require 'mongo/version'
require 'mongo/write_concern'
Expand Down
37 changes: 34 additions & 3 deletions lib/mongo/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class Client
:ssl_verify_hostname,
:ssl_verify_ocsp_endpoint,
:timeout_ms,
:tracing,
:truncate_logs,
:user,
:wait_queue_timeout,
Expand Down Expand Up @@ -437,6 +438,20 @@ def hash
# See Ruby's Zlib module for valid levels.
# @option options [ Hash ] :resolv_options For internal driver use only.
# Options to pass through to Resolv::DNS constructor for SRV lookups.
# @option options [ Hash ] :tracing OpenTelemetry tracing options.
# - :enabled => Boolean, whether to enable OpenTelemetry tracing. The default
# value is nil that means that the configuration will be taken from the
# OTEL_RUBY_INSTRUMENTATION_MONGODB_ENABLED environment variable.
# - :tracer => OpenTelemetry::Trace::Tracer, the tracer to use for
# tracing. Must be an implementation of OpenTelemetry::Trace::Tracer
# interface.
# - :query_text_max_length => Integer, the maximum length of the query text
# to be included in the span attributes. If the query text exceeds this
# length, it will be truncated. Value 0 means no query text
# will be included in the span attributes. The default value is nil that
# means that the configuration will be taken from the
# OTEL_RUBY_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH environment
# variable.
# @option options [ Hash ] :auto_encryption_options Auto-encryption related
# options.
# - :key_vault_client => Client | nil, a client connected to the MongoDB
Expand Down Expand Up @@ -574,8 +589,11 @@ def initialize(addresses_or_uri, options = nil)

@connect_lock = Mutex.new
@connect_lock.synchronize do
@cluster = Cluster.new(addresses, @monitoring,
cluster_options.merge(srv_uri: srv_uri))
@cluster = Cluster.new(
addresses,
@monitoring,
cluster_options.merge(srv_uri: srv_uri)
)
end

begin
Expand Down Expand Up @@ -623,6 +641,7 @@ def cluster_options
# applications should read these values from client, not from cluster
max_read_retries: options[:max_read_retries],
read_retry_interval: options[:read_retry_interval],
tracer: tracer,
).tap do |options|
# If the client has a cluster already, forward srv_uri to the new
# cluster to maintain SRV monitoring. If the client is brand new,
Expand Down Expand Up @@ -965,7 +984,10 @@ def list_databases(filter = {}, name_only = false, opts = {})
cmd[:nameOnly] = !!name_only
cmd[:filter] = filter unless filter.empty?
cmd[:authorizedDatabases] = true if opts[:authorized_databases]
use(Database::ADMIN).database.read_command(cmd, opts).first[Database::DATABASES]
use(Database::ADMIN)
.database
.read_command(cmd, opts.merge(op_name: 'listDatabases'))
.first[Database::DATABASES]
end

# Returns a list of Mongo::Database objects.
Expand Down Expand Up @@ -1195,6 +1217,15 @@ def timeout_sec
end
end

def tracer
tracing_opts = @options[:tracing] || {}
@tracer ||= Tracing.create_tracer(
enabled: tracing_opts[:enabled],
query_text_max_length: tracing_opts[:query_text_max_length],
otel_tracer: tracing_opts[:tracer],
)
end

private

# Attempts to parse the given list of addresses, using the provided options.
Expand Down
5 changes: 4 additions & 1 deletion lib/mongo/cluster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def initialize(seeds, monitoring, options = Options::Redacted.new)
if options[:monitoring_io] == false && !options.key?(:cleanup)
options[:cleanup] = false
end
@tracer = options.delete(:tracer)
@options = options.freeze

# @update_lock covers @servers, @connecting, @connected, @topology and
Expand Down Expand Up @@ -298,7 +299,7 @@ def self.create(client, monitoring: nil)
cluster = Cluster.new(
client.cluster.addresses.map(&:to_s),
monitoring || Monitoring.new,
client.cluster_options,
client.cluster_options
)
client.instance_variable_set(:@cluster, cluster)
end
Expand All @@ -309,6 +310,8 @@ def self.create(client, monitoring: nil)
# @return [ Monitoring ] monitoring The monitoring.
attr_reader :monitoring

attr_reader :tracer

# @return [ Object ] The cluster topology.
attr_reader :topology

Expand Down
102 changes: 56 additions & 46 deletions lib/mongo/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class Collection
# Delegate to the cluster for the next primary.
def_delegators :cluster, :next_primary

def_delegators :client, :tracer

# Options that can be updated on a new Collection instance via the #with method.
#
# @since 2.1.0
Expand Down Expand Up @@ -410,21 +412,24 @@ def create(opts = {})
client: client,
session: session
)
maybe_create_qe_collections(opts[:encrypted_fields], client, session) do |encrypted_fields|
Operation::Create.new(
selector: operation,
db_name: database.name,
write_concern: write_concern,
session: session,
# Note that these are collection options, collation isn't
# taken from options passed to the create method.
collation: options[:collation] || options['collation'],
encrypted_fields: encrypted_fields,
validator: options[:validator],
).execute(
next_primary(nil, session),
context: context
)
operation = Operation::Create.new(
selector: operation,
db_name: database.name,
write_concern: write_concern,
session: session,
# Note that these are collection options, collation isn't
# taken from options passed to the create method.
collation: options[:collation] || options['collation'],
validator: options[:validator],
)
tracer.trace_operation(operation, context, op_name: 'createCollection') do
maybe_create_qe_collections(opts[:encrypted_fields], client, session) do |encrypted_fields|
operation.encrypted_fields = encrypted_fields
operation.execute(
next_primary(nil, session),
context: context
)
end
end
end
end
Expand Down Expand Up @@ -453,25 +458,27 @@ def create(opts = {})
# @since 2.0.0
def drop(opts = {})
client.with_session(opts) do |session|
maybe_drop_emm_collections(opts[:encrypted_fields], client, session) do
temp_write_concern = write_concern
write_concern = if opts[:write_concern]
WriteConcern.get(opts[:write_concern])
else
temp_write_concern
context = Operation::Context.new(
client: client,
session: session,
operation_timeouts: operation_timeouts(opts)
)
temp_write_concern = write_concern
write_concern = if opts[:write_concern]
WriteConcern.get(opts[:write_concern])
else
temp_write_concern
end
operation = Operation::Drop.new({
selector: { :drop => name },
db_name: database.name,
write_concern: write_concern,
session: session,
})
tracer.trace_operation(operation, context, op_name: 'dropCollection') do
maybe_drop_emm_collections(opts[:encrypted_fields], client, session) do
do_drop(operation, session, context)
end
context = Operation::Context.new(
client: client,
session: session,
operation_timeouts: operation_timeouts(opts)
)
operation = Operation::Drop.new({
selector: { :drop => name },
db_name: database.name,
write_concern: write_concern,
session: session,
})
do_drop(operation, session, context)
end
end
end
Expand Down Expand Up @@ -865,19 +872,22 @@ def insert_one(document, opts = {})
session: session,
operation_timeouts: operation_timeouts(opts)
)
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
Operation::Insert.new(
:documents => [ document ],
:db_name => database.name,
:coll_name => name,
:write_concern => write_concern,
:bypass_document_validation => !!opts[:bypass_document_validation],
:options => opts,
:id_generator => client.options[:id_generator],
:session => session,
:txn_num => txn_num,
:comment => opts[:comment]
).execute_with_connection(connection, context: context)
operation = Operation::Insert.new(
:documents => [ document ],
:db_name => database.name,
:coll_name => name,
:write_concern => write_concern,
:bypass_document_validation => !!opts[:bypass_document_validation],
:options => opts,
:id_generator => client.options[:id_generator],
:session => session,
:comment => opts[:comment]
)
tracer.trace_operation(operation, context) do
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
operation.txn_num = txn_num
operation.execute_with_connection(connection, context: context)
end
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/mongo/collection/view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class View
# Delegate to the cluster for the next primary.
def_delegators :cluster, :next_primary

def_delegators :client, :tracer

alias :selector :filter

# @return [ Integer | nil | The timeout_ms value that was passed as an
Expand Down
Loading
Loading