Skip to content

Commit 5e64c1d

Browse files
RUBY-3612 OpenTelemetry (#2957)
Co-authored-by: Dmitry Rybakov <[email protected]>
1 parent dda2da1 commit 5e64c1d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+4856
-237
lines changed

.evergreen/config.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,20 @@ functions:
208208
TEST_CMD="bundle exec rspec spec/spec_tests/client_side_operations_timeout_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
209209
.evergreen/run-tests.sh
210210
211+
"run OTel tests":
212+
- command: shell.exec
213+
type: test
214+
params:
215+
shell: bash
216+
working_dir: "src"
217+
script: |
218+
${PREPARE_SHELL}
219+
export OTEL_SPEC_TESTS=1
220+
unset TOPOLOGY
221+
export TOPOLOGY=${MLAUNCH_TOPOLOGY}
222+
TEST_CMD="bundle exec rspec spec/spec_tests/open_telemetry_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
223+
.evergreen/run-tests.sh
224+
211225
"export FLE credentials":
212226
- command: shell.exec
213227
type: test
@@ -641,6 +655,9 @@ tasks:
641655
- name: "test-csot"
642656
commands:
643657
- func: "run CSOT tests"
658+
- name: "test-otel"
659+
commands:
660+
- func: "run OTel tests"
644661
- name: "test-fle"
645662
commands:
646663
- func: "export FLE credentials"
@@ -1257,6 +1274,16 @@ buildvariants:
12571274
tasks:
12581275
- name: test-csot
12591276

1277+
- matrix_name: OTel
1278+
matrix_spec:
1279+
ruby: "ruby-3.3"
1280+
mongodb-version: "8.0"
1281+
topology: replica-set-single-node
1282+
os: ubuntu2204
1283+
display_name: "OTel - ${mongodb-version}"
1284+
tasks:
1285+
- name: test-otel
1286+
12601287
- matrix_name: "no-retry-reads"
12611288
matrix_spec:
12621289
retry-reads: no-retry-reads

.evergreen/config/common.yml.erb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,20 @@ functions:
205205
TEST_CMD="bundle exec rspec spec/spec_tests/client_side_operations_timeout_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
206206
.evergreen/run-tests.sh
207207

208+
"run OTel tests":
209+
- command: shell.exec
210+
type: test
211+
params:
212+
shell: bash
213+
working_dir: "src"
214+
script: |
215+
${PREPARE_SHELL}
216+
export OTEL_SPEC_TESTS=1
217+
unset TOPOLOGY
218+
export TOPOLOGY=${MLAUNCH_TOPOLOGY}
219+
TEST_CMD="bundle exec rspec spec/spec_tests/open_telemetry_spec.rb --format Rfc::Riff --format RspecJunitFormatter --out tmp/rspec.xml" \
220+
.evergreen/run-tests.sh
221+
208222
"export FLE credentials":
209223
- command: shell.exec
210224
type: test
@@ -638,6 +652,9 @@ tasks:
638652
- name: "test-csot"
639653
commands:
640654
- func: "run CSOT tests"
655+
- name: "test-otel"
656+
commands:
657+
- func: "run OTel tests"
641658
- name: "test-fle"
642659
commands:
643660
- func: "export FLE credentials"

.evergreen/config/standard.yml.erb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ buildvariants:
144144
tasks:
145145
- name: test-csot
146146

147+
- matrix_name: OTel
148+
matrix_spec:
149+
ruby: <%= latest_ruby %>
150+
mongodb-version: <%= latest_stable_mdb %>
151+
topology: replica-set-single-node
152+
os: ubuntu2204
153+
display_name: "OTel - ${mongodb-version}"
154+
tasks:
155+
- name: test-otel
156+
147157
- matrix_name: "no-retry-reads"
148158
matrix_spec:
149159
retry-reads: no-retry-reads

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,6 @@ RSpec/ExampleLength:
116116

117117
RSpec/MessageSpies:
118118
EnforcedStyle: receive
119+
120+
RSpec/NamedSubject:
121+
Enabled: false

gemfiles/standard.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def standard_dependencies
1212
gem 'activesupport', '<7.1'
1313
gem 'rake'
1414
gem 'webrick'
15+
gem 'opentelemetry-sdk'
1516

1617
gem 'byebug', platforms: :mri
1718
gem 'ruby-debug', platforms: :jruby

lib/mongo.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
require 'mongo/socket'
7575
require 'mongo/srv'
7676
require 'mongo/timeout'
77+
require 'mongo/tracing'
7778
require 'mongo/uri'
7879
require 'mongo/version'
7980
require 'mongo/write_concern'

lib/mongo/client.rb

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class Client
112112
:ssl_verify_hostname,
113113
:ssl_verify_ocsp_endpoint,
114114
:timeout_ms,
115+
:tracing,
115116
:truncate_logs,
116117
:user,
117118
:wait_queue_timeout,
@@ -437,6 +438,20 @@ def hash
437438
# See Ruby's Zlib module for valid levels.
438439
# @option options [ Hash ] :resolv_options For internal driver use only.
439440
# Options to pass through to Resolv::DNS constructor for SRV lookups.
441+
# @option options [ Hash ] :tracing OpenTelemetry tracing options.
442+
# - :enabled => Boolean, whether to enable OpenTelemetry tracing. The default
443+
# value is nil that means that the configuration will be taken from the
444+
# OTEL_RUBY_INSTRUMENTATION_MONGODB_ENABLED environment variable.
445+
# - :tracer => OpenTelemetry::Trace::Tracer, the tracer to use for
446+
# tracing. Must be an implementation of OpenTelemetry::Trace::Tracer
447+
# interface.
448+
# - :query_text_max_length => Integer, the maximum length of the query text
449+
# to be included in the span attributes. If the query text exceeds this
450+
# length, it will be truncated. Value 0 means no query text
451+
# will be included in the span attributes. The default value is nil that
452+
# means that the configuration will be taken from the
453+
# OTEL_RUBY_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH environment
454+
# variable.
440455
# @option options [ Hash ] :auto_encryption_options Auto-encryption related
441456
# options.
442457
# - :key_vault_client => Client | nil, a client connected to the MongoDB
@@ -574,8 +589,11 @@ def initialize(addresses_or_uri, options = nil)
574589

575590
@connect_lock = Mutex.new
576591
@connect_lock.synchronize do
577-
@cluster = Cluster.new(addresses, @monitoring,
578-
cluster_options.merge(srv_uri: srv_uri))
592+
@cluster = Cluster.new(
593+
addresses,
594+
@monitoring,
595+
cluster_options.merge(srv_uri: srv_uri)
596+
)
579597
end
580598

581599
begin
@@ -623,6 +641,7 @@ def cluster_options
623641
# applications should read these values from client, not from cluster
624642
max_read_retries: options[:max_read_retries],
625643
read_retry_interval: options[:read_retry_interval],
644+
tracer: tracer,
626645
).tap do |options|
627646
# If the client has a cluster already, forward srv_uri to the new
628647
# cluster to maintain SRV monitoring. If the client is brand new,
@@ -965,7 +984,10 @@ def list_databases(filter = {}, name_only = false, opts = {})
965984
cmd[:nameOnly] = !!name_only
966985
cmd[:filter] = filter unless filter.empty?
967986
cmd[:authorizedDatabases] = true if opts[:authorized_databases]
968-
use(Database::ADMIN).database.read_command(cmd, opts).first[Database::DATABASES]
987+
use(Database::ADMIN)
988+
.database
989+
.read_command(cmd, opts.merge(op_name: 'listDatabases'))
990+
.first[Database::DATABASES]
969991
end
970992

971993
# Returns a list of Mongo::Database objects.
@@ -1195,6 +1217,18 @@ def timeout_sec
11951217
end
11961218
end
11971219

1220+
# Get the tracer configured for this client.
1221+
#
1222+
# @return [ Tracing::Tracer | nil ] The tracer configured for this client.
1223+
def tracer
1224+
tracing_opts = @options[:tracing] || {}
1225+
@tracer ||= Tracing.create_tracer(
1226+
enabled: tracing_opts[:enabled],
1227+
query_text_max_length: tracing_opts[:query_text_max_length],
1228+
otel_tracer: tracing_opts[:tracer],
1229+
)
1230+
end
1231+
11981232
private
11991233

12001234
# Attempts to parse the given list of addresses, using the provided options.

lib/mongo/cluster.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def initialize(seeds, monitoring, options = Options::Redacted.new)
126126
if options[:monitoring_io] == false && !options.key?(:cleanup)
127127
options[:cleanup] = false
128128
end
129+
@tracer = options.delete(:tracer)
129130
@options = options.freeze
130131

131132
# @update_lock covers @servers, @connecting, @connected, @topology and
@@ -298,7 +299,7 @@ def self.create(client, monitoring: nil)
298299
cluster = Cluster.new(
299300
client.cluster.addresses.map(&:to_s),
300301
monitoring || Monitoring.new,
301-
client.cluster_options,
302+
client.cluster_options
302303
)
303304
client.instance_variable_set(:@cluster, cluster)
304305
end
@@ -309,6 +310,8 @@ def self.create(client, monitoring: nil)
309310
# @return [ Monitoring ] monitoring The monitoring.
310311
attr_reader :monitoring
311312

313+
attr_reader :tracer
314+
312315
# @return [ Object ] The cluster topology.
313316
attr_reader :topology
314317

lib/mongo/collection.rb

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class Collection
5757
# Delegate to the cluster for the next primary.
5858
def_delegators :cluster, :next_primary
5959

60+
def_delegators :client, :tracer
61+
6062
# Options that can be updated on a new Collection instance via the #with method.
6163
#
6264
# @since 2.1.0
@@ -410,21 +412,24 @@ def create(opts = {})
410412
client: client,
411413
session: session
412414
)
413-
maybe_create_qe_collections(opts[:encrypted_fields], client, session) do |encrypted_fields|
414-
Operation::Create.new(
415-
selector: operation,
416-
db_name: database.name,
417-
write_concern: write_concern,
418-
session: session,
419-
# Note that these are collection options, collation isn't
420-
# taken from options passed to the create method.
421-
collation: options[:collation] || options['collation'],
422-
encrypted_fields: encrypted_fields,
423-
validator: options[:validator],
424-
).execute(
425-
next_primary(nil, session),
426-
context: context
427-
)
415+
operation = Operation::Create.new(
416+
selector: operation,
417+
db_name: database.name,
418+
write_concern: write_concern,
419+
session: session,
420+
# Note that these are collection options, collation isn't
421+
# taken from options passed to the create method.
422+
collation: options[:collation] || options['collation'],
423+
validator: options[:validator],
424+
)
425+
tracer.trace_operation(operation, context, op_name: 'createCollection') do
426+
maybe_create_qe_collections(opts[:encrypted_fields], client, session) do |encrypted_fields|
427+
operation.encrypted_fields = encrypted_fields
428+
operation.execute(
429+
next_primary(nil, session),
430+
context: context
431+
)
432+
end
428433
end
429434
end
430435
end
@@ -453,25 +458,27 @@ def create(opts = {})
453458
# @since 2.0.0
454459
def drop(opts = {})
455460
client.with_session(opts) do |session|
456-
maybe_drop_emm_collections(opts[:encrypted_fields], client, session) do
457-
temp_write_concern = write_concern
458-
write_concern = if opts[:write_concern]
459-
WriteConcern.get(opts[:write_concern])
460-
else
461-
temp_write_concern
461+
context = Operation::Context.new(
462+
client: client,
463+
session: session,
464+
operation_timeouts: operation_timeouts(opts)
465+
)
466+
temp_write_concern = write_concern
467+
write_concern = if opts[:write_concern]
468+
WriteConcern.get(opts[:write_concern])
469+
else
470+
temp_write_concern
471+
end
472+
operation = Operation::Drop.new({
473+
selector: { :drop => name },
474+
db_name: database.name,
475+
write_concern: write_concern,
476+
session: session,
477+
})
478+
tracer.trace_operation(operation, context, op_name: 'dropCollection') do
479+
maybe_drop_emm_collections(opts[:encrypted_fields], client, session) do
480+
do_drop(operation, session, context)
462481
end
463-
context = Operation::Context.new(
464-
client: client,
465-
session: session,
466-
operation_timeouts: operation_timeouts(opts)
467-
)
468-
operation = Operation::Drop.new({
469-
selector: { :drop => name },
470-
db_name: database.name,
471-
write_concern: write_concern,
472-
session: session,
473-
})
474-
do_drop(operation, session, context)
475482
end
476483
end
477484
end
@@ -865,19 +872,22 @@ def insert_one(document, opts = {})
865872
session: session,
866873
operation_timeouts: operation_timeouts(opts)
867874
)
868-
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
869-
Operation::Insert.new(
870-
:documents => [ document ],
871-
:db_name => database.name,
872-
:coll_name => name,
873-
:write_concern => write_concern,
874-
:bypass_document_validation => !!opts[:bypass_document_validation],
875-
:options => opts,
876-
:id_generator => client.options[:id_generator],
877-
:session => session,
878-
:txn_num => txn_num,
879-
:comment => opts[:comment]
880-
).execute_with_connection(connection, context: context)
875+
operation = Operation::Insert.new(
876+
:documents => [ document ],
877+
:db_name => database.name,
878+
:coll_name => name,
879+
:write_concern => write_concern,
880+
:bypass_document_validation => !!opts[:bypass_document_validation],
881+
:options => opts,
882+
:id_generator => client.options[:id_generator],
883+
:session => session,
884+
:comment => opts[:comment]
885+
)
886+
tracer.trace_operation(operation, context) do
887+
write_with_retry(write_concern, context: context) do |connection, txn_num, context|
888+
operation.txn_num = txn_num
889+
operation.execute_with_connection(connection, context: context)
890+
end
881891
end
882892
end
883893
end

lib/mongo/collection/view.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class View
7272
# Delegate to the cluster for the next primary.
7373
def_delegators :cluster, :next_primary
7474

75+
def_delegators :client, :tracer
76+
7577
alias :selector :filter
7678

7779
# @return [ Integer | nil | The timeout_ms value that was passed as an

0 commit comments

Comments
 (0)