mongo 2.26.0 → 2.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mongo/collection/view/aggregation.rb +9 -9
- data/lib/mongo/collection/view/change_stream.rb +1 -5
- data/lib/mongo/collection/view/iterable.rb +2 -9
- data/lib/mongo/collection/view/map_reduce.rb +5 -4
- data/lib/mongo/collection/view/readable.rb +3 -3
- data/lib/mongo/cursor.rb +13 -0
- data/lib/mongo/database/view.rb +7 -6
- data/lib/mongo/database.rb +7 -10
- data/lib/mongo/index/view.rb +3 -2
- data/lib/mongo/monitoring/event/secure.rb +6 -1
- data/lib/mongo/operation/map_reduce/result.rb +6 -2
- data/lib/mongo/operation/shared/result/aggregatable.rb +3 -3
- data/lib/mongo/protocol/serializers.rb +1 -9
- data/lib/mongo/server/app_metadata.rb +1 -2
- data/lib/mongo/server/connection.rb +7 -0
- data/lib/mongo/server/connection_pool.rb +60 -9
- data/lib/mongo/session.rb +7 -0
- data/lib/mongo/tracing/open_telemetry/command_tracer.rb +141 -102
- data/lib/mongo/tracing/open_telemetry/operation_tracer.rb +52 -17
- data/lib/mongo/tracing/open_telemetry/tracer.rb +10 -1
- data/lib/mongo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39d9792c95b149dbbcb1b065df222c345eea9c84912e03de9a8994d7aea32ab8
|
|
4
|
+
data.tar.gz: b491401e88ffde8324e802267b35f75f6f92997f5746b3634612e1a9d63f11e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aad7e612834dd20849a707f6fde3d5a8101765af93df37eee119ad0418dd518b738ec8834689f0aeac267ac117aab2f89f84362f0c588f8714b96c1c87c286ef
|
|
7
|
+
data.tar.gz: 6a1c05b23598ac63e37267393de256bd0f2d250f14e881e2fda7a436a2ee2775d60e61d076c4fbaaa77230e7135f89095c55a737edca13a3434f048fdf8aa5db
|
|
@@ -115,15 +115,15 @@ module Mongo
|
|
|
115
115
|
|
|
116
116
|
def send_initial_query(server, context, operation: nil)
|
|
117
117
|
if server.load_balancer?
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
119
|
+
initial_query_op(
|
|
120
|
+
context.session,
|
|
121
|
+
effective_read_preference(connection)
|
|
122
|
+
).execute_with_connection(
|
|
123
|
+
connection,
|
|
124
|
+
context: context
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
127
|
else
|
|
128
128
|
server.with_connection do |connection|
|
|
129
129
|
initial_query_op(
|
|
@@ -350,8 +350,7 @@ module Mongo
|
|
|
350
350
|
if server.load_balancer?
|
|
351
351
|
# In load balanced topology, manually check out a connection
|
|
352
352
|
# so it remains checked out and pinned to the cursor.
|
|
353
|
-
|
|
354
|
-
begin
|
|
353
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
355
354
|
result = send_initial_query(connection, context)
|
|
356
355
|
|
|
357
356
|
start_at_operation_time = if (doc = result.replies.first && result.replies.first.documents.first)
|
|
@@ -360,9 +359,6 @@ module Mongo
|
|
|
360
359
|
nil
|
|
361
360
|
end
|
|
362
361
|
result
|
|
363
|
-
rescue StandardError
|
|
364
|
-
server.pool.check_in(connection)
|
|
365
|
-
raise
|
|
366
362
|
end
|
|
367
363
|
else
|
|
368
364
|
server.with_connection do |connection|
|
|
@@ -172,16 +172,9 @@ module Mongo
|
|
|
172
172
|
def send_initial_query(server, context, operation: nil)
|
|
173
173
|
operation ||= initial_query_op(context.session)
|
|
174
174
|
if server.load_balancer?
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# case it stays checked out for the transaction duration).
|
|
178
|
-
if context.connection_global_id
|
|
179
|
-
connection = server.pool.check_out_pinned_connection(
|
|
180
|
-
context.connection_global_id
|
|
181
|
-
)
|
|
175
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
176
|
+
operation.execute_with_connection(connection, context: context)
|
|
182
177
|
end
|
|
183
|
-
connection ||= server.pool.check_out(context: context)
|
|
184
|
-
operation.execute_with_connection(connection, context: context)
|
|
185
178
|
else
|
|
186
179
|
operation.execute(server, context: context)
|
|
187
180
|
end
|
|
@@ -73,10 +73,11 @@ module Mongo
|
|
|
73
73
|
context = Operation::Context.new(client: client, session: session,
|
|
74
74
|
operation_timeouts: view.operation_timeouts)
|
|
75
75
|
if server.load_balancer?
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
result = server.pool.with_cursor_connection(context: context) do |connection|
|
|
77
|
+
res = send_initial_query_with_connection(connection, context.session, context: context)
|
|
78
|
+
res = send_fetch_query_with_connection(connection, session) unless inline?
|
|
79
|
+
res
|
|
80
|
+
end
|
|
80
81
|
else
|
|
81
82
|
result = send_initial_query(server, context)
|
|
82
83
|
result = send_fetch_query(server, session) unless inline?
|
|
@@ -739,9 +739,9 @@ module Mongo
|
|
|
739
739
|
connection_global_id: result.connection_global_id
|
|
740
740
|
)
|
|
741
741
|
result = if server.load_balancer?
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
742
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
743
|
+
op.execute_with_connection(connection, context: context)
|
|
744
|
+
end
|
|
745
745
|
else
|
|
746
746
|
op.execute(server, context: context)
|
|
747
747
|
end
|
data/lib/mongo/cursor.rb
CHANGED
|
@@ -103,6 +103,19 @@ module Mongo
|
|
|
103
103
|
self.class.finalize(kill_spec(@connection_global_id), cluster)
|
|
104
104
|
)
|
|
105
105
|
end
|
|
106
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
107
|
+
# In load-balanced topology the connection of the initial result is
|
|
108
|
+
# checked out of the pool until the cursor is drained. If the cursor
|
|
109
|
+
# cannot be constructed, nothing will ever check the connection back
|
|
110
|
+
# in, so release it here before the error propagates. Exception (not
|
|
111
|
+
# StandardError) is rescued so that an interrupt does not permanently
|
|
112
|
+
# leak the connection. In other topologies the connection is not owned
|
|
113
|
+
# by the cursor and must not be touched here.
|
|
114
|
+
if server&.load_balancer? && result.is_a?(Operation::Result) &&
|
|
115
|
+
(connection = result.connection)
|
|
116
|
+
connection.connection_pool.check_in_if_checked_out(connection)
|
|
117
|
+
end
|
|
118
|
+
raise
|
|
106
119
|
end
|
|
107
120
|
|
|
108
121
|
# @api private
|
data/lib/mongo/database/view.rb
CHANGED
|
@@ -273,12 +273,13 @@ module Mongo
|
|
|
273
273
|
execution_opts = {}
|
|
274
274
|
execution_opts[:deserialize_as_bson] = opts.delete(:deserialize_as_bson) if opts.key?(:deserialize_as_bson)
|
|
275
275
|
if server.load_balancer?
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
277
|
+
initial_query_op(session, opts).execute_with_connection(
|
|
278
|
+
connection,
|
|
279
|
+
context: context,
|
|
280
|
+
options: execution_opts
|
|
281
|
+
)
|
|
282
|
+
end
|
|
282
283
|
else
|
|
283
284
|
initial_query_op(session, opts).execute(
|
|
284
285
|
server,
|
data/lib/mongo/database.rb
CHANGED
|
@@ -339,15 +339,12 @@ module Mongo
|
|
|
339
339
|
server = selector.select_server(cluster, nil, session)
|
|
340
340
|
if server.load_balancer?
|
|
341
341
|
# The connection is checked in by the cursor when it is drained.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
connection.connection_pool.check_in(connection) unless connection.pinned?
|
|
349
|
-
connection = nil
|
|
350
|
-
raise
|
|
342
|
+
# If the command fails, the connection is released before the
|
|
343
|
+
# error propagates so that a retried attempt checks out a
|
|
344
|
+
# fresh one.
|
|
345
|
+
server.pool.with_cursor_connection(context: context) do |conn|
|
|
346
|
+
connection = conn
|
|
347
|
+
op.execute_with_connection(conn, context: context, options: execution_opts)
|
|
351
348
|
end
|
|
352
349
|
else
|
|
353
350
|
op.execute(server, context: context, options: execution_opts)
|
|
@@ -366,7 +363,7 @@ module Mongo
|
|
|
366
363
|
# If the cursor was created it owns the session and connection;
|
|
367
364
|
# otherwise (error or no cursor in the response) release them here.
|
|
368
365
|
unless cursor
|
|
369
|
-
connection.connection_pool.
|
|
366
|
+
connection.connection_pool.check_in_if_checked_out(connection) if connection
|
|
370
367
|
session.end_session if session && session.implicit?
|
|
371
368
|
end
|
|
372
369
|
end
|
data/lib/mongo/index/view.rb
CHANGED
|
@@ -421,8 +421,9 @@ module Mongo
|
|
|
421
421
|
|
|
422
422
|
def send_initial_query(op, server, _session, context)
|
|
423
423
|
if server.load_balancer?
|
|
424
|
-
|
|
425
|
-
|
|
424
|
+
server.pool.with_cursor_connection(context: context) do |connection|
|
|
425
|
+
op.execute_with_connection(connection, context: context)
|
|
426
|
+
end
|
|
426
427
|
else
|
|
427
428
|
op.execute(server, context: context)
|
|
428
429
|
end
|
|
@@ -37,6 +37,11 @@ module Mongo
|
|
|
37
37
|
copydb
|
|
38
38
|
].freeze
|
|
39
39
|
|
|
40
|
+
# The hello and legacy hello command names. A constant, because
|
|
41
|
+
# sensitive? runs on every command event and an array literal would
|
|
42
|
+
# allocate on each call.
|
|
43
|
+
HELLO_COMMANDS = %w[hello ismaster isMaster].freeze
|
|
44
|
+
|
|
40
45
|
# Check whether the command is sensitive in terms of command monitoring
|
|
41
46
|
# spec. A command is detected as sensitive if it is in the
|
|
42
47
|
# list or if it is a hello/legacy hello command, and
|
|
@@ -53,7 +58,7 @@ module Mongo
|
|
|
53
58
|
# when speculativeAuthenticate is present, their commands AND replies
|
|
54
59
|
# MUST be redacted from the events.
|
|
55
60
|
# See https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.md#security
|
|
56
|
-
|
|
61
|
+
HELLO_COMMANDS.include?(command_name.to_s) &&
|
|
57
62
|
!!document['speculativeAuthenticate']
|
|
58
63
|
end
|
|
59
64
|
|
|
@@ -80,7 +80,7 @@ module Mongo
|
|
|
80
80
|
# @since 2.0.0
|
|
81
81
|
# @api public
|
|
82
82
|
def successful?
|
|
83
|
-
!documents.nil?
|
|
83
|
+
!documents.nil? && parser.message.empty?
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Get the execution time of the map/reduce.
|
|
@@ -112,7 +112,11 @@ module Mongo
|
|
|
112
112
|
# @since 2.0.0
|
|
113
113
|
# @api private
|
|
114
114
|
def validate!
|
|
115
|
-
documents.nil? ?
|
|
115
|
+
if documents.nil? || !parser.message.empty?
|
|
116
|
+
raise_operation_failure
|
|
117
|
+
else
|
|
118
|
+
self
|
|
119
|
+
end
|
|
116
120
|
end
|
|
117
121
|
|
|
118
122
|
# Get the cursor id.
|
|
@@ -36,7 +36,7 @@ module Mongo
|
|
|
36
36
|
return unless @replies
|
|
37
37
|
|
|
38
38
|
@replies.reduce(nil) do |errors, reply|
|
|
39
|
-
next unless (write_errors = reply.documents.first['writeErrors'])
|
|
39
|
+
next errors unless (write_errors = reply.documents.first['writeErrors'])
|
|
40
40
|
|
|
41
41
|
wes = write_errors.collect do |we|
|
|
42
42
|
we.merge!('index' => count + we['index'])
|
|
@@ -59,11 +59,11 @@ module Mongo
|
|
|
59
59
|
return unless @replies
|
|
60
60
|
|
|
61
61
|
@replies.each_with_index.reduce(nil) do |errors, (reply, _)|
|
|
62
|
-
next unless (write_concern_errors = reply.documents.first['writeConcernErrors'])
|
|
62
|
+
next errors unless (write_concern_errors = reply.documents.first['writeConcernErrors'])
|
|
63
63
|
|
|
64
64
|
(errors || []) << write_concern_errors.reduce(nil) do |errs, wce|
|
|
65
65
|
wce.merge!('index' => count + wce['index'])
|
|
66
|
-
(errs || []) <<
|
|
66
|
+
(errs || []) << wce
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
end
|
|
@@ -319,16 +319,8 @@ module Mongo
|
|
|
319
319
|
# @return [ Array<BSON::Document> ] Deserialized section.
|
|
320
320
|
#
|
|
321
321
|
# @since 2.5.0
|
|
322
|
-
def self.deserialize(buffer)
|
|
322
|
+
def self.deserialize(buffer, _options = {})
|
|
323
323
|
raise NotImplementedError
|
|
324
|
-
|
|
325
|
-
start_size = buffer.length
|
|
326
|
-
section_size = buffer.get_int32 # get the size
|
|
327
|
-
end_size = start_size - section_size
|
|
328
|
-
buffer.get_cstring # get the identifier
|
|
329
|
-
documents = []
|
|
330
|
-
documents << BSON::Document.from_bson(buffer) until buffer.length == end_size
|
|
331
|
-
documents
|
|
332
324
|
end
|
|
333
325
|
end
|
|
334
326
|
end
|
|
@@ -130,7 +130,6 @@ module Mongo
|
|
|
130
130
|
doc[:driver] = driver_doc
|
|
131
131
|
doc[:os] = os_doc
|
|
132
132
|
doc[:platform] = platform_string
|
|
133
|
-
doc[:backpressure] = '2'
|
|
134
133
|
env_doc.tap { |env| doc[:env] = env if env }
|
|
135
134
|
end
|
|
136
135
|
end
|
|
@@ -157,7 +156,7 @@ module Mongo
|
|
|
157
156
|
def document
|
|
158
157
|
@document ||= begin
|
|
159
158
|
client = Truncator.new(client_document).document
|
|
160
|
-
BSON::Document.new(compression: @compressors, client: client).tap do |doc|
|
|
159
|
+
BSON::Document.new(compression: @compressors, client: client, backpressure: '2').tap do |doc|
|
|
161
160
|
doc[:saslSupportedMechs] = @request_auth_mech if @request_auth_mech
|
|
162
161
|
doc.update(Utils.transform_server_api(@server_api)) if @server_api
|
|
163
162
|
end
|
|
@@ -124,6 +124,13 @@ module Mongo
|
|
|
124
124
|
# across all connections.
|
|
125
125
|
attr_reader :global_id
|
|
126
126
|
|
|
127
|
+
# @return [ Hash | nil ] OpenTelemetry span attributes describing this
|
|
128
|
+
# connection. Built once by the tracer and reused for every command
|
|
129
|
+
# on the connection.
|
|
130
|
+
#
|
|
131
|
+
# @api private
|
|
132
|
+
attr_accessor :tracing_attributes
|
|
133
|
+
|
|
127
134
|
def_delegators :server, :tracer
|
|
128
135
|
|
|
129
136
|
# The connection pool from which this connection was created.
|
|
@@ -432,6 +432,30 @@ module Mongo
|
|
|
432
432
|
check_invariants
|
|
433
433
|
end
|
|
434
434
|
|
|
435
|
+
# Check a connection back into the pool only if this pool still holds
|
|
436
|
+
# it as checked out and no other owner has claimed it.
|
|
437
|
+
#
|
|
438
|
+
# Unlike #check_in, this method is safe to call when the connection may
|
|
439
|
+
# have been checked in already (e.g. by Session#unpin while handling a
|
|
440
|
+
# transient transaction error) or may be pinned to a transaction or
|
|
441
|
+
# cursor that will check it in later; in both cases it does nothing.
|
|
442
|
+
#
|
|
443
|
+
# @param [ Mongo::Server::Connection ] connection The connection.
|
|
444
|
+
#
|
|
445
|
+
# @api private
|
|
446
|
+
def check_in_if_checked_out(connection)
|
|
447
|
+
check_invariants
|
|
448
|
+
|
|
449
|
+
@lock.synchronize do
|
|
450
|
+
return if connection.pinned?
|
|
451
|
+
return unless @checked_out_connections.include?(connection)
|
|
452
|
+
|
|
453
|
+
do_check_in(connection)
|
|
454
|
+
end
|
|
455
|
+
ensure
|
|
456
|
+
check_invariants
|
|
457
|
+
end
|
|
458
|
+
|
|
435
459
|
# Executes the check in after having already acquired the lock.
|
|
436
460
|
#
|
|
437
461
|
# @param [ Mongo::Server::Connection ] connection The connection.
|
|
@@ -758,16 +782,43 @@ module Mongo
|
|
|
758
782
|
rescue Error::SocketError, Error::SocketTimeoutError, Error::ConnectionPerished => e
|
|
759
783
|
maybe_raise_pool_cleared!(connection, e)
|
|
760
784
|
ensure
|
|
761
|
-
if connection
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
785
|
+
# Do not check in if the connection is pinned (the session or cursor
|
|
786
|
+
# owns it and will check it in later when unpinning) or was already
|
|
787
|
+
# checked in during the block (e.g. by Session#unpin after an error
|
|
788
|
+
# on the first operation).
|
|
789
|
+
check_in_if_checked_out(connection) if connection
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
# Check out a connection for the initial command of a cursor-returning
|
|
793
|
+
# operation in load-balanced topology and yield it to the block.
|
|
794
|
+
#
|
|
795
|
+
# On success the connection remains checked out: the cursor assumes
|
|
796
|
+
# ownership and checks it in when drained or closed. If the block
|
|
797
|
+
# raises, no cursor exists to do that, so the connection is checked
|
|
798
|
+
# back in here before the error propagates, unless another owner
|
|
799
|
+
# already claimed it (it is pinned to a transaction, or Session#unpin
|
|
800
|
+
# checked it in while handling a transient transaction error).
|
|
801
|
+
#
|
|
802
|
+
# If the operation context is pinned to a connection (e.g. inside a
|
|
803
|
+
# transaction), the pinned connection is reused.
|
|
804
|
+
#
|
|
805
|
+
# @param [ Mongo::Operation::Context | nil ] :context Context of the
|
|
806
|
+
# operation the connection is requested for, if any.
|
|
807
|
+
#
|
|
808
|
+
# @return [ Object ] The result of the block.
|
|
809
|
+
#
|
|
810
|
+
# @api private
|
|
811
|
+
def with_cursor_connection(context:)
|
|
812
|
+
if context&.connection_global_id
|
|
813
|
+
connection = check_out_pinned_connection(context.connection_global_id)
|
|
770
814
|
end
|
|
815
|
+
connection ||= check_out(context: context)
|
|
816
|
+
succeeded = false
|
|
817
|
+
result = yield(connection)
|
|
818
|
+
succeeded = true
|
|
819
|
+
result
|
|
820
|
+
ensure
|
|
821
|
+
check_in_if_checked_out(connection) if connection && !succeeded
|
|
771
822
|
end
|
|
772
823
|
|
|
773
824
|
# Close sockets that have been open for longer than the max idle time,
|
data/lib/mongo/session.rb
CHANGED
|
@@ -297,6 +297,13 @@ module Mongo
|
|
|
297
297
|
# @api private
|
|
298
298
|
attr_accessor :recovery_token
|
|
299
299
|
|
|
300
|
+
# @return [ Array<Integer, String> | nil ] The transaction number and the
|
|
301
|
+
# OpenTelemetry tracer's key for that transaction. Built once by the
|
|
302
|
+
# tracer and reused for every operation in the transaction.
|
|
303
|
+
#
|
|
304
|
+
# @api private
|
|
305
|
+
attr_accessor :tracing_transaction_key
|
|
306
|
+
|
|
300
307
|
# Error message indicating that the session was retrieved from a client with a different cluster than that of the
|
|
301
308
|
# client through which it is currently being used.
|
|
302
309
|
#
|
|
@@ -23,12 +23,8 @@ module Mongo
|
|
|
23
23
|
class CommandTracer
|
|
24
24
|
include Mongo::Monitoring::Event::Secure
|
|
25
25
|
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
# the Command Logging and Monitoring spec. We additionally skip hello /
|
|
29
|
-
# legacy hello in all forms — these are handshake/heartbeat traffic and
|
|
30
|
-
# would only add noise to traces.
|
|
31
|
-
HELLO_COMMANDS = %w[hello ismaster isMaster].freeze
|
|
26
|
+
# Upper bound of the lsid string cache (see #lsid).
|
|
27
|
+
LSID_CACHE_MAX = 128
|
|
32
28
|
|
|
33
29
|
# Initializes a new CommandTracer.
|
|
34
30
|
#
|
|
@@ -41,6 +37,8 @@ module Mongo
|
|
|
41
37
|
@otel_tracer = otel_tracer
|
|
42
38
|
@parent_tracer = parent_tracer
|
|
43
39
|
@query_text_max_length = query_text_max_length
|
|
40
|
+
@lsid_cache = {}
|
|
41
|
+
@lsid_cache_mutex = Mutex.new
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
# Starts a span for a MongoDB command.
|
|
@@ -64,55 +62,84 @@ module Mongo
|
|
|
64
62
|
# @yield the block representing the command to be traced.
|
|
65
63
|
#
|
|
66
64
|
# @return [ Object ] the result of the command.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
def trace_command(message, _operation_context, connection, &block)
|
|
66
|
+
# Read the command document and name once and pass them on: every
|
|
67
|
+
# read of message.documents allocates.
|
|
68
|
+
doc = message.documents.first
|
|
69
|
+
name = command_name(doc)
|
|
70
|
+
return yield if skip_tracing?(doc, name)
|
|
70
71
|
|
|
71
72
|
# Commands should always be nested under their operation span, not directly under
|
|
72
73
|
# the transaction span. Don't pass with_parent to use automatic parent resolution
|
|
73
74
|
# from the currently active span (the operation span).
|
|
74
|
-
span = create_command_span(
|
|
75
|
-
|
|
75
|
+
span = create_command_span(name, doc, connection)
|
|
76
|
+
# With no SDK and no valid parent, the API returns a span with an
|
|
77
|
+
# invalid context. It cannot be made current, recorded or
|
|
78
|
+
# propagated, so it gets no work at all, not even finishing it.
|
|
79
|
+
return yield unless span.context.valid?
|
|
80
|
+
|
|
81
|
+
trace_with_span(span, message, name, doc, &block)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# Runs the command with the span made current, and finishes the span.
|
|
87
|
+
# A span that is not recording is still made current, so that its
|
|
88
|
+
# context is propagated, but gets no further attributes and no
|
|
89
|
+
# outcome: the SDK would discard them.
|
|
90
|
+
#
|
|
91
|
+
# @param span [ OpenTelemetry::Trace::Span ] a span with a valid context.
|
|
92
|
+
# @param message [ Mongo::Protocol::Message ] the command message.
|
|
93
|
+
# @param name [ String ] the command name.
|
|
94
|
+
# @param doc [ Hash ] the command document.
|
|
95
|
+
#
|
|
96
|
+
# @return [ Object ] the result of the command.
|
|
97
|
+
#
|
|
98
|
+
# rubocop:disable Lint/RescueException
|
|
99
|
+
def trace_with_span(span, message, name, doc)
|
|
100
|
+
recording = span.recording?
|
|
101
|
+
apply_deferred_attributes(span, message, name, doc) if recording
|
|
102
|
+
::OpenTelemetry::Trace.with_span(span) do |s, _c|
|
|
76
103
|
yield.tap do |result|
|
|
77
|
-
process_command_result(result,
|
|
104
|
+
process_command_result(result, s) if recording
|
|
78
105
|
end
|
|
79
106
|
end
|
|
80
107
|
rescue Exception => e
|
|
81
|
-
handle_command_exception(span, e)
|
|
108
|
+
handle_command_exception(span, e) if recording
|
|
82
109
|
raise e
|
|
83
110
|
ensure
|
|
84
|
-
span
|
|
111
|
+
span.finish
|
|
85
112
|
end
|
|
86
113
|
# rubocop:enable Lint/RescueException
|
|
87
114
|
|
|
88
|
-
private
|
|
89
|
-
|
|
90
115
|
# Determines whether the command must not be traced. Sensitive auth
|
|
91
116
|
# commands carry credentials in their payloads (SCRAM proofs, cleartext
|
|
92
117
|
# passwords, etc.) and the OpenTelemetry spec requires drivers to skip
|
|
93
|
-
# command spans for them. Hello / legacy hello
|
|
94
|
-
#
|
|
118
|
+
# command spans for them. Hello / legacy hello in all forms
|
|
119
|
+
# (Secure::HELLO_COMMANDS) are also skipped: they are handshake and
|
|
120
|
+
# heartbeat traffic and would only add noise to traces.
|
|
95
121
|
#
|
|
96
|
-
# @param
|
|
122
|
+
# @param doc [ Hash ] the command document.
|
|
123
|
+
# @param name [ String ] the command name.
|
|
97
124
|
#
|
|
98
125
|
# @return [ Boolean ] true when no command span should be created.
|
|
99
|
-
def skip_tracing?(
|
|
100
|
-
name = command_name(message)
|
|
126
|
+
def skip_tracing?(doc, name)
|
|
101
127
|
return true if HELLO_COMMANDS.include?(name)
|
|
102
128
|
|
|
103
|
-
sensitive?(command_name: name, document:
|
|
129
|
+
sensitive?(command_name: name, document: doc)
|
|
104
130
|
end
|
|
105
131
|
|
|
106
132
|
# Creates a span for a command.
|
|
107
133
|
#
|
|
108
|
-
# @param
|
|
134
|
+
# @param name [ String ] the command name.
|
|
135
|
+
# @param doc [ Hash ] the command document.
|
|
109
136
|
# @param connection [ Mongo::Server::Connection ] the connection.
|
|
110
137
|
#
|
|
111
138
|
# @return [ OpenTelemetry::Trace::Span ] the created span.
|
|
112
|
-
def create_command_span(
|
|
139
|
+
def create_command_span(name, doc, connection)
|
|
113
140
|
@otel_tracer.start_span(
|
|
114
|
-
|
|
115
|
-
attributes: span_attributes(
|
|
141
|
+
name,
|
|
142
|
+
attributes: span_attributes(doc, name, connection),
|
|
116
143
|
kind: :client
|
|
117
144
|
)
|
|
118
145
|
end
|
|
@@ -120,21 +147,17 @@ module Mongo
|
|
|
120
147
|
# Processes the command result and updates span attributes.
|
|
121
148
|
#
|
|
122
149
|
# @param result [ Object ] the command result.
|
|
123
|
-
# @param cursor_id [ Integer | nil ] the cursor ID.
|
|
124
|
-
# @param context [ OpenTelemetry::Context ] the context.
|
|
125
150
|
# @param span [ OpenTelemetry::Trace::Span ] the current span.
|
|
126
|
-
def process_command_result(result,
|
|
127
|
-
process_cursor_context(result,
|
|
151
|
+
def process_command_result(result, span)
|
|
152
|
+
process_cursor_context(result, span)
|
|
128
153
|
maybe_trace_error(result, span)
|
|
129
154
|
end
|
|
130
155
|
|
|
131
156
|
# Handles exceptions that occur during command execution.
|
|
132
157
|
#
|
|
133
|
-
# @param span [ OpenTelemetry::Trace::Span
|
|
158
|
+
# @param span [ OpenTelemetry::Trace::Span ] the span.
|
|
134
159
|
# @param exception [ Exception ] the exception that occurred.
|
|
135
160
|
def handle_command_exception(span, exception)
|
|
136
|
-
return unless span
|
|
137
|
-
|
|
138
161
|
if exception.is_a?(Mongo::Error::OperationFailure)
|
|
139
162
|
span.set_attribute('db.response.status_code', exception.code.to_s)
|
|
140
163
|
end
|
|
@@ -142,70 +165,68 @@ module Mongo
|
|
|
142
165
|
span.status = ::OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{exception.class}")
|
|
143
166
|
end
|
|
144
167
|
|
|
145
|
-
# Builds
|
|
168
|
+
# Builds the attributes passed at span creation: the cheap set the
|
|
169
|
+
# OpenTelemetry spec recommends. They are built for every span,
|
|
170
|
+
# recorded or not, and are the only ones the sampler sees. The rest
|
|
171
|
+
# are set by apply_deferred_attributes once the span is known to
|
|
172
|
+
# record.
|
|
146
173
|
#
|
|
147
|
-
# @param
|
|
174
|
+
# @param doc [ Hash ] the command document.
|
|
175
|
+
# @param name [ String ] the command name.
|
|
148
176
|
# @param connection [ Mongo::Server::Connection ] the connection.
|
|
149
177
|
#
|
|
150
178
|
# @return [ Hash ] OpenTelemetry span attributes following MongoDB semantic conventions.
|
|
151
|
-
def span_attributes(
|
|
152
|
-
|
|
153
|
-
.
|
|
154
|
-
.
|
|
155
|
-
.
|
|
179
|
+
def span_attributes(doc, name, connection)
|
|
180
|
+
{
|
|
181
|
+
'db.system.name' => 'mongodb',
|
|
182
|
+
'db.namespace' => database(doc),
|
|
183
|
+
'db.collection.name' => collection_name(name, doc),
|
|
184
|
+
'db.command.name' => name
|
|
185
|
+
}.compact.merge(connection_attributes(connection))
|
|
156
186
|
end
|
|
157
187
|
|
|
158
|
-
#
|
|
188
|
+
# Sets the remaining attributes on a span that is recording. They are
|
|
189
|
+
# not visible to the sampler; only custom samplers keying on them are
|
|
190
|
+
# affected.
|
|
159
191
|
#
|
|
192
|
+
# @param span [ OpenTelemetry::Trace::Span ] the span.
|
|
160
193
|
# @param message [ Mongo::Protocol::Message ] the command message.
|
|
161
|
-
#
|
|
162
|
-
# @
|
|
163
|
-
def
|
|
194
|
+
# @param name [ String ] the command name.
|
|
195
|
+
# @param doc [ Hash ] the command document.
|
|
196
|
+
def apply_deferred_attributes(span, message, name, doc)
|
|
164
197
|
{
|
|
165
|
-
'db.
|
|
166
|
-
'db.
|
|
167
|
-
'db.
|
|
168
|
-
'db.
|
|
169
|
-
'db.
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
'db.query.summary' => query_summary(name, doc),
|
|
199
|
+
'db.query.text' => query_text(message),
|
|
200
|
+
'db.mongodb.cursor_id' => cursor_id(name, doc),
|
|
201
|
+
'db.mongodb.lsid' => lsid(doc),
|
|
202
|
+
'db.mongodb.txn_number' => txn_number(doc)
|
|
203
|
+
}.each do |key, value|
|
|
204
|
+
span.set_attribute(key, value) unless value.nil?
|
|
205
|
+
end
|
|
172
206
|
end
|
|
173
207
|
|
|
174
|
-
# Returns connection-related attributes
|
|
208
|
+
# Returns connection-related attributes, built once per connection,
|
|
209
|
+
# frozen and kept on the connection. Two threads may build them at
|
|
210
|
+
# the same time; they build the same hash, so either result is fine.
|
|
175
211
|
#
|
|
176
212
|
# @param connection [ Mongo::Server::Connection ] the connection.
|
|
177
213
|
#
|
|
178
214
|
# @return [ Hash ] connection span attributes.
|
|
179
215
|
def connection_attributes(connection)
|
|
180
|
-
{
|
|
216
|
+
connection.tracing_attributes ||= {
|
|
181
217
|
'server.port' => connection.address.port,
|
|
182
218
|
'server.address' => connection.address.host,
|
|
183
219
|
'network.transport' => connection.transport.to_s,
|
|
184
220
|
'db.mongodb.server_connection_id' => connection.server.description.server_connection_id,
|
|
185
221
|
'db.mongodb.driver_connection_id' => connection.id
|
|
186
|
-
}
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# Returns session and transaction attributes.
|
|
190
|
-
#
|
|
191
|
-
# @param message [ Mongo::Protocol::Message ] the command message.
|
|
192
|
-
#
|
|
193
|
-
# @return [ Hash ] session span attributes.
|
|
194
|
-
def session_attributes(message)
|
|
195
|
-
{
|
|
196
|
-
'db.mongodb.cursor_id' => cursor_id(message),
|
|
197
|
-
'db.mongodb.lsid' => lsid(message),
|
|
198
|
-
'db.mongodb.txn_number' => txn_number(message)
|
|
199
|
-
}
|
|
222
|
+
}.freeze
|
|
200
223
|
end
|
|
201
224
|
|
|
202
225
|
# Processes cursor context from the command result.
|
|
203
226
|
#
|
|
204
227
|
# @param result [ Object ] the command result.
|
|
205
|
-
# @param _cursor_id [ Integer | nil ] the cursor ID (unused).
|
|
206
|
-
# @param _context [ OpenTelemetry::Context ] the context (unused).
|
|
207
228
|
# @param span [ OpenTelemetry::Trace::Span ] the current span.
|
|
208
|
-
def process_cursor_context(result,
|
|
229
|
+
def process_cursor_context(result, span)
|
|
209
230
|
cursor_id = normalize_cursor_id(result.cursor_id)
|
|
210
231
|
return unless cursor_id.positive?
|
|
211
232
|
|
|
@@ -242,51 +263,56 @@ module Mongo
|
|
|
242
263
|
|
|
243
264
|
# Generates a summary string for the query.
|
|
244
265
|
#
|
|
245
|
-
# @param
|
|
266
|
+
# @param name [ String ] the command name.
|
|
267
|
+
# @param doc [ Hash ] the command document.
|
|
246
268
|
#
|
|
247
269
|
# @return [ String ] summary in format "command_name db.collection" or "command_name db".
|
|
248
|
-
def query_summary(
|
|
249
|
-
if (coll_name = collection_name(
|
|
250
|
-
"#{
|
|
270
|
+
def query_summary(name, doc)
|
|
271
|
+
if (coll_name = collection_name(name, doc))
|
|
272
|
+
"#{name} #{database(doc)}.#{coll_name}"
|
|
251
273
|
else
|
|
252
|
-
"#{
|
|
274
|
+
"#{name} #{database(doc)}"
|
|
253
275
|
end
|
|
254
276
|
end
|
|
255
277
|
|
|
256
|
-
# Extracts the collection name from the command
|
|
278
|
+
# Extracts the collection name from the command document.
|
|
257
279
|
#
|
|
258
|
-
# @param
|
|
280
|
+
# @param name [ String ] the command name.
|
|
281
|
+
# @param doc [ Hash ] the command document.
|
|
259
282
|
#
|
|
260
283
|
# @return [ String | nil ] the collection name, or nil if not applicable.
|
|
261
|
-
def collection_name(
|
|
262
|
-
case
|
|
284
|
+
def collection_name(name, doc)
|
|
285
|
+
case name
|
|
263
286
|
when 'getMore'
|
|
264
|
-
|
|
287
|
+
doc['collection'].to_s
|
|
265
288
|
when 'listCollections', 'listDatabases', 'commitTransaction', 'abortTransaction'
|
|
266
289
|
nil
|
|
267
290
|
else
|
|
268
|
-
value
|
|
291
|
+
# The first value, read without building an array of all of them.
|
|
292
|
+
value = doc.each_value { |v| break v } # rubocop:disable Lint/UnreachableLoop -- first value only
|
|
269
293
|
# Return nil if the value is not a string (e.g., for admin commands that have numeric values)
|
|
270
294
|
value.is_a?(String) ? value : nil
|
|
271
295
|
end
|
|
272
296
|
end
|
|
273
297
|
|
|
274
|
-
# Extracts the command name
|
|
298
|
+
# Extracts the command name, the first key of the command document,
|
|
299
|
+
# without building an array of all keys.
|
|
275
300
|
#
|
|
276
|
-
# @param
|
|
301
|
+
# @param doc [ Hash ] the command document.
|
|
277
302
|
#
|
|
278
303
|
# @return [ String ] the command name.
|
|
279
|
-
def command_name(
|
|
280
|
-
|
|
304
|
+
def command_name(doc)
|
|
305
|
+
doc.each_key { |key| return key.to_s } # rubocop:disable Lint/UnreachableLoop -- first key only
|
|
306
|
+
''
|
|
281
307
|
end
|
|
282
308
|
|
|
283
|
-
# Extracts the database name from the
|
|
309
|
+
# Extracts the database name from the command document.
|
|
284
310
|
#
|
|
285
|
-
# @param
|
|
311
|
+
# @param doc [ Hash ] the command document.
|
|
286
312
|
#
|
|
287
313
|
# @return [ String ] the database name.
|
|
288
|
-
def database(
|
|
289
|
-
|
|
314
|
+
def database(doc)
|
|
315
|
+
doc['$db'].to_s
|
|
290
316
|
end
|
|
291
317
|
|
|
292
318
|
# Checks if query text capture is enabled.
|
|
@@ -298,34 +324,47 @@ module Mongo
|
|
|
298
324
|
|
|
299
325
|
# Extracts the cursor ID from getMore commands.
|
|
300
326
|
#
|
|
301
|
-
# @param
|
|
327
|
+
# @param name [ String ] the command name.
|
|
328
|
+
# @param doc [ Hash ] the command document.
|
|
302
329
|
#
|
|
303
330
|
# @return [ Integer | nil ] the cursor ID, or nil if not a getMore command.
|
|
304
|
-
def cursor_id(
|
|
305
|
-
return unless
|
|
331
|
+
def cursor_id(name, doc)
|
|
332
|
+
return unless name == 'getMore'
|
|
306
333
|
|
|
307
|
-
|
|
334
|
+
doc['getMore'].value
|
|
308
335
|
end
|
|
309
336
|
|
|
310
|
-
# Extracts the logical session ID from the command.
|
|
337
|
+
# Extracts the logical session ID from the command, as a UUID string.
|
|
338
|
+
# The string is formatted once per session id and kept in a small
|
|
339
|
+
# cache, cleared when it reaches LSID_CACHE_MAX entries.
|
|
311
340
|
#
|
|
312
|
-
# @param
|
|
341
|
+
# @param doc [ Hash ] the command document.
|
|
313
342
|
#
|
|
314
|
-
# @return [
|
|
315
|
-
def lsid(
|
|
316
|
-
lsid_doc =
|
|
343
|
+
# @return [ String | nil ] the session ID, or nil if not present.
|
|
344
|
+
def lsid(doc)
|
|
345
|
+
lsid_doc = doc['lsid']
|
|
317
346
|
return unless lsid_doc
|
|
318
347
|
|
|
319
|
-
lsid_doc['id']
|
|
348
|
+
binary = lsid_doc['id']
|
|
349
|
+
key = binary.data
|
|
350
|
+
cached = @lsid_cache_mutex.synchronize { @lsid_cache[key] }
|
|
351
|
+
return cached if cached
|
|
352
|
+
|
|
353
|
+
uuid = binary.to_uuid
|
|
354
|
+
@lsid_cache_mutex.synchronize do
|
|
355
|
+
@lsid_cache.clear if @lsid_cache.size >= LSID_CACHE_MAX
|
|
356
|
+
@lsid_cache[key] = uuid
|
|
357
|
+
end
|
|
358
|
+
uuid
|
|
320
359
|
end
|
|
321
360
|
|
|
322
361
|
# Extracts the transaction number from the command.
|
|
323
362
|
#
|
|
324
|
-
# @param
|
|
363
|
+
# @param doc [ Hash ] the command document.
|
|
325
364
|
#
|
|
326
365
|
# @return [ Integer | nil ] the transaction number, or nil if not present.
|
|
327
|
-
def txn_number(
|
|
328
|
-
txn_num =
|
|
366
|
+
def txn_number(doc)
|
|
367
|
+
txn_num = doc['txnNumber']
|
|
329
368
|
return unless txn_num
|
|
330
369
|
|
|
331
370
|
txn_num.value
|
|
@@ -37,6 +37,8 @@ module Mongo
|
|
|
37
37
|
def initialize(otel_tracer, parent_tracer)
|
|
38
38
|
@otel_tracer = otel_tracer
|
|
39
39
|
@parent_tracer = parent_tracer
|
|
40
|
+
@operation_names = {}
|
|
41
|
+
@operation_names_mutex = Mutex.new
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
# Trace a MongoDB operation.
|
|
@@ -53,21 +55,43 @@ module Mongo
|
|
|
53
55
|
# @yield the block representing the operation to be traced.
|
|
54
56
|
#
|
|
55
57
|
# @return [ Object ] the result of the operation.
|
|
56
|
-
#
|
|
57
|
-
# rubocop:disable Lint/RescueException
|
|
58
58
|
def trace_operation(operation, operation_context, op_name: nil, &block)
|
|
59
59
|
span = create_operation_span(operation, operation_context, op_name)
|
|
60
|
-
|
|
60
|
+
# With no SDK and no valid parent, the API returns a span with an
|
|
61
|
+
# invalid context. It cannot be made current, recorded or
|
|
62
|
+
# propagated, so it gets no work at all, not even finishing it.
|
|
63
|
+
return yield unless span.context.valid?
|
|
64
|
+
|
|
65
|
+
trace_with_span(span, operation, &block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Runs the operation with the span made current, and finishes the span.
|
|
71
|
+
# A span that is not recording is still made current, so that the
|
|
72
|
+
# command spans created within it nest under it, but gets no further
|
|
73
|
+
# attributes and no outcome: the SDK would discard them.
|
|
74
|
+
#
|
|
75
|
+
# @param span [ OpenTelemetry::Trace::Span ] a span with a valid context.
|
|
76
|
+
# @param operation [ Mongo::Operation ] the operation.
|
|
77
|
+
#
|
|
78
|
+
# @return [ Object ] the result of the operation.
|
|
79
|
+
#
|
|
80
|
+
# rubocop:disable Lint/RescueException
|
|
81
|
+
def trace_with_span(span, operation, &block)
|
|
82
|
+
recording = span.recording?
|
|
83
|
+
if recording && !operation.cursor_id.nil?
|
|
84
|
+
span.set_attribute('db.mongodb.cursor_id', operation.cursor_id)
|
|
85
|
+
end
|
|
86
|
+
execute_with_span(span, operation, recording, &block)
|
|
61
87
|
rescue Exception => e
|
|
62
|
-
handle_span_exception(span, e)
|
|
88
|
+
handle_span_exception(span, e) if recording
|
|
63
89
|
raise e
|
|
64
90
|
ensure
|
|
65
|
-
span
|
|
91
|
+
span.finish
|
|
66
92
|
end
|
|
67
93
|
# rubocop:enable Lint/RescueException
|
|
68
94
|
|
|
69
|
-
private
|
|
70
|
-
|
|
71
95
|
# Creates an OpenTelemetry span for the operation.
|
|
72
96
|
#
|
|
73
97
|
# @param operation [ Mongo::Operation ] the operation.
|
|
@@ -89,14 +113,15 @@ module Mongo
|
|
|
89
113
|
#
|
|
90
114
|
# @param span [ OpenTelemetry::Trace::Span ] the span.
|
|
91
115
|
# @param operation [ Mongo::Operation ] the operation.
|
|
116
|
+
# @param recording [ true | false ] whether the span is recording.
|
|
92
117
|
#
|
|
93
118
|
# @yield the block to execute.
|
|
94
119
|
#
|
|
95
120
|
# @return [ Object ] the result of the block.
|
|
96
|
-
def execute_with_span(span, operation)
|
|
121
|
+
def execute_with_span(span, operation, recording)
|
|
97
122
|
::OpenTelemetry::Trace.with_span(span) do |s, c|
|
|
98
123
|
yield.tap do |result|
|
|
99
|
-
process_cursor_context(result, operation.cursor_id, c, s)
|
|
124
|
+
process_cursor_context(result, operation.cursor_id, c, s, recording)
|
|
100
125
|
end
|
|
101
126
|
end
|
|
102
127
|
end
|
|
@@ -106,8 +131,6 @@ module Mongo
|
|
|
106
131
|
# @param span [ OpenTelemetry::Trace::Span ] the span.
|
|
107
132
|
# @param exception [ Exception ] the exception.
|
|
108
133
|
def handle_span_exception(span, exception)
|
|
109
|
-
return unless span
|
|
110
|
-
|
|
111
134
|
span.record_exception(exception)
|
|
112
135
|
span.status = ::OpenTelemetry::Trace::Status.error(
|
|
113
136
|
"Unhandled exception of type: #{exception.class}"
|
|
@@ -115,16 +138,28 @@ module Mongo
|
|
|
115
138
|
end
|
|
116
139
|
|
|
117
140
|
# Returns the operation name from the provided name or operation class.
|
|
141
|
+
# The name derived from the class is cached per class. There are few
|
|
142
|
+
# operation classes, so the cache needs no bound. It is guarded by a
|
|
143
|
+
# mutex because not every Ruby runtime makes Hash writes thread-safe.
|
|
118
144
|
#
|
|
119
145
|
# @param operation [ Mongo::Operation ] the operation.
|
|
120
146
|
# @param op_name [ String | nil ] optional operation name.
|
|
121
147
|
#
|
|
122
148
|
# @return [ String ] the operation name in lowercase.
|
|
123
149
|
def operation_name(operation, op_name = nil)
|
|
124
|
-
op_name
|
|
150
|
+
return op_name if op_name
|
|
151
|
+
|
|
152
|
+
klass = operation.class
|
|
153
|
+
@operation_names_mutex.synchronize do
|
|
154
|
+
@operation_names[klass] ||= klass.name.split('::').last.downcase
|
|
155
|
+
end
|
|
125
156
|
end
|
|
126
157
|
|
|
127
|
-
# Builds
|
|
158
|
+
# Builds the attributes passed at span creation. The cursor id is set
|
|
159
|
+
# by trace_with_span once the span is known to record.
|
|
160
|
+
# db.operation.summary is outside the spec's recommended creation set,
|
|
161
|
+
# but it is the span name, which is built anyway, and an attribute
|
|
162
|
+
# passed at creation is cheaper than one set later.
|
|
128
163
|
#
|
|
129
164
|
# @param operation [ Mongo::Operation ] the operation.
|
|
130
165
|
# @param op_name [ String | nil ] optional operation name.
|
|
@@ -136,8 +171,7 @@ module Mongo
|
|
|
136
171
|
'db.namespace' => operation.db_name.to_s,
|
|
137
172
|
'db.collection.name' => collection_name(operation),
|
|
138
173
|
'db.operation.name' => operation_name(operation, op_name),
|
|
139
|
-
'db.operation.summary' => operation_span_name(operation, op_name)
|
|
140
|
-
'db.mongodb.cursor_id' => operation.cursor_id,
|
|
174
|
+
'db.operation.summary' => operation_span_name(operation, op_name)
|
|
141
175
|
}.compact
|
|
142
176
|
end
|
|
143
177
|
|
|
@@ -150,7 +184,8 @@ module Mongo
|
|
|
150
184
|
# @param cursor_id [ Integer | nil ] the cursor ID before the operation.
|
|
151
185
|
# @param context [ OpenTelemetry::Context ] the OpenTelemetry context.
|
|
152
186
|
# @param span [ OpenTelemetry::Trace::Span ] the current span.
|
|
153
|
-
|
|
187
|
+
# @param recording [ true | false ] whether the span is recording.
|
|
188
|
+
def process_cursor_context(result, cursor_id, context, span, recording)
|
|
154
189
|
return unless result.is_a?(Cursor)
|
|
155
190
|
|
|
156
191
|
if result.id.zero?
|
|
@@ -159,7 +194,7 @@ module Mongo
|
|
|
159
194
|
elsif result.id && cursor_id.nil?
|
|
160
195
|
# New cursor created, store its context.
|
|
161
196
|
cursor_context_map[result.id] = context
|
|
162
|
-
span.set_attribute('db.mongodb.cursor_id', result.id)
|
|
197
|
+
span.set_attribute('db.mongodb.cursor_id', result.id) if recording
|
|
163
198
|
end
|
|
164
199
|
end
|
|
165
200
|
|
|
@@ -200,6 +200,9 @@ module Mongo
|
|
|
200
200
|
# Generates a unique key for transaction tracking.
|
|
201
201
|
#
|
|
202
202
|
# Returns nil for implicit sessions or sessions not in a transaction.
|
|
203
|
+
# Every operation in a transaction needs the key, so it is cached on
|
|
204
|
+
# the session together with the transaction number it was built for.
|
|
205
|
+
# Two threads may build it at the same time; they build the same key.
|
|
203
206
|
#
|
|
204
207
|
# @param session [ Mongo::Session ] the session.
|
|
205
208
|
#
|
|
@@ -207,7 +210,13 @@ module Mongo
|
|
|
207
210
|
def transaction_map_key(session)
|
|
208
211
|
return if session.nil? || session.implicit? || !session.in_transaction?
|
|
209
212
|
|
|
210
|
-
|
|
213
|
+
txn_num = session.txn_num
|
|
214
|
+
cached = session.tracing_transaction_key
|
|
215
|
+
return cached[1] if cached && cached[0] == txn_num
|
|
216
|
+
|
|
217
|
+
key = "#{session.session_id['id'].to_uuid}-#{txn_num}"
|
|
218
|
+
session.tracing_transaction_key = [ txn_num, key ].freeze
|
|
219
|
+
key
|
|
211
220
|
end
|
|
212
221
|
|
|
213
222
|
private
|
data/lib/mongo/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mongo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.26.
|
|
4
|
+
version: 2.26.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The MongoDB Ruby Team
|
|
@@ -561,7 +561,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
561
561
|
- !ruby/object:Gem::Version
|
|
562
562
|
version: '0'
|
|
563
563
|
requirements: []
|
|
564
|
-
rubygems_version: 4.0.
|
|
564
|
+
rubygems_version: 4.0.23
|
|
565
565
|
specification_version: 4
|
|
566
566
|
summary: Ruby driver for MongoDB
|
|
567
567
|
test_files: []
|