mongo 2.25.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/bulk_write/combineable.rb +0 -7
- data/lib/mongo/bulk_write/validatable.rb +0 -4
- data/lib/mongo/bulk_write.rb +0 -34
- data/lib/mongo/client.rb +35 -5
- data/lib/mongo/client_encryption.rb +36 -11
- data/lib/mongo/cluster.rb +1 -2
- data/lib/mongo/collection/queryable_encryption.rb +7 -6
- 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/collection/view/writable.rb +0 -35
- data/lib/mongo/crypt/auto_encrypter.rb +5 -1
- data/lib/mongo/crypt/binding.rb +66 -1
- data/lib/mongo/crypt/explicit_encrypter.rb +27 -12
- data/lib/mongo/crypt/explicit_encryption_context.rb +51 -12
- data/lib/mongo/crypt/handle.rb +18 -0
- 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/error/unsupported_option.rb +0 -40
- data/lib/mongo/grid/fs_bucket.rb +2 -2
- data/lib/mongo/grid/stream/read.rb +2 -2
- data/lib/mongo/grid/stream/write.rb +1 -1
- 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/result.rb +9 -0
- data/lib/mongo/operation/shared/executable.rb +1 -6
- data/lib/mongo/operation/shared/result/aggregatable.rb +3 -3
- data/lib/mongo/protocol/message.rb +0 -4
- data/lib/mongo/protocol/msg.rb +1 -12
- data/lib/mongo/protocol/serializers.rb +1 -9
- data/lib/mongo/retryable/backpressure.rb +17 -2
- data/lib/mongo/retryable/read_worker.rb +1 -1
- data/lib/mongo/retryable/retry_policy.rb +2 -2
- data/lib/mongo/retryable/write_worker.rb +2 -2
- data/lib/mongo/retryable.rb +1 -1
- 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/server/description/features.rb +2 -3
- data/lib/mongo/server/description.rb +0 -1
- data/lib/mongo/server/pending_connection.rb +0 -5
- data/lib/mongo/server.rb +1 -2
- data/lib/mongo/session.rb +10 -3
- 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/uri/options_mapper.rb +15 -4
- data/lib/mongo/version.rb +1 -1
- metadata +2 -2
|
@@ -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,
|
|
@@ -24,7 +24,6 @@ module Mongo
|
|
|
24
24
|
# List of features and the wire protocol version they appear in.
|
|
25
25
|
#
|
|
26
26
|
# Wire protocol versions map to server releases as follows:
|
|
27
|
-
# - 8 => 4.2
|
|
28
27
|
# - 9 => 4.4
|
|
29
28
|
# - 13 => 5.0
|
|
30
29
|
# - 14 => 5.1
|
|
@@ -61,7 +60,7 @@ module Mongo
|
|
|
61
60
|
# The wire protocol versions that this version of the driver supports.
|
|
62
61
|
#
|
|
63
62
|
# @since 2.0.0
|
|
64
|
-
DRIVER_WIRE_VERSIONS =
|
|
63
|
+
DRIVER_WIRE_VERSIONS = 9..29
|
|
65
64
|
|
|
66
65
|
# The wire protocol versions that are deprecated in this version of the
|
|
67
66
|
# driver. Support for these versions will be removed in the future.
|
|
@@ -74,7 +73,7 @@ module Mongo
|
|
|
74
73
|
#
|
|
75
74
|
# If there are no currently-deprecated wire versions, this should be
|
|
76
75
|
# set to an empty range (e.g. the EMPTY_RANGE constant).
|
|
77
|
-
DEPRECATED_WIRE_VERSIONS =
|
|
76
|
+
DEPRECATED_WIRE_VERSIONS = EMPTY_RANGE
|
|
78
77
|
|
|
79
78
|
# make sure the deprecated versions are valid
|
|
80
79
|
if DEPRECATED_WIRE_VERSIONS.min && (DRIVER_WIRE_VERSIONS.min > DEPRECATED_WIRE_VERSIONS.max)
|
|
@@ -52,11 +52,6 @@ module Mongo
|
|
|
52
52
|
# SCRAM-SHA-1. The use of SCRAM-SHA-256 by default is thus
|
|
53
53
|
# sensible, and it is also mandated by the speculative auth spec.
|
|
54
54
|
#
|
|
55
|
-
# If no mechanism was specified and we are talking to a 4.2
|
|
56
|
-
# server, we'll send speculative auth document, the server will
|
|
57
|
-
# ignore it and we'll perform authentication using explicit
|
|
58
|
-
# command.
|
|
59
|
-
#
|
|
60
55
|
# If no mechanism was specified and we are talking to a 4.4+
|
|
61
56
|
# server and the user account doesn't allow SCRAM-SHA-256, we will
|
|
62
57
|
# authenticate in a separate command with SCRAM-SHA-1 after
|
data/lib/mongo/server.rb
CHANGED
|
@@ -567,8 +567,7 @@ module Mongo
|
|
|
567
567
|
# @option options [ true | false ] :keep_connection_pool Usually when the
|
|
568
568
|
# new server description is unknown, the connection pool on the
|
|
569
569
|
# respective server is cleared. Set this option to true to keep the
|
|
570
|
-
# existing connection pool (required when handling not master errors
|
|
571
|
-
# on 4.2+ servers).
|
|
570
|
+
# existing connection pool (required when handling not master errors).
|
|
572
571
|
# @option options [ TopologyVersion ] :topology_version Topology version
|
|
573
572
|
# of the error response that is causing the server to be marked unknown.
|
|
574
573
|
# @option options [ true | false ] :stop_push_monitor Set when the server
|
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
|
#
|
|
@@ -471,7 +478,7 @@ module Mongo
|
|
|
471
478
|
loop do
|
|
472
479
|
if transaction_attempt > 0
|
|
473
480
|
if overload_encountered
|
|
474
|
-
delay = @client.retry_policy.backoff_delay(overload_error_count)
|
|
481
|
+
delay = @client.retry_policy.backoff_delay(overload_error_count, err: last_error)
|
|
475
482
|
if backoff_would_exceed_deadline?(deadline, delay)
|
|
476
483
|
make_timeout_error_from(last_error, 'CSOT timeout expired waiting to retry withTransaction')
|
|
477
484
|
end
|
|
@@ -562,7 +569,7 @@ module Mongo
|
|
|
562
569
|
end
|
|
563
570
|
|
|
564
571
|
if overload_encountered
|
|
565
|
-
delay = @client.retry_policy.backoff_delay(overload_error_count)
|
|
572
|
+
delay = @client.retry_policy.backoff_delay(overload_error_count, err: e)
|
|
566
573
|
if backoff_would_exceed_deadline?(deadline, delay)
|
|
567
574
|
transaction_in_progress = false
|
|
568
575
|
make_timeout_error_from(e, 'CSOT timeout expired during withTransaction commit')
|
|
@@ -1429,7 +1436,7 @@ module Mongo
|
|
|
1429
1436
|
private_constant :BACKOFF_INITIAL, :BACKOFF_MAX
|
|
1430
1437
|
|
|
1431
1438
|
def backoff_seconds_for_retry(transaction_attempt)
|
|
1432
|
-
exponential = BACKOFF_INITIAL * (1.5**
|
|
1439
|
+
exponential = BACKOFF_INITIAL * (1.5**transaction_attempt)
|
|
1433
1440
|
Random.rand * [ exponential, BACKOFF_MAX ].min
|
|
1434
1441
|
end
|
|
1435
1442
|
|
|
@@ -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
|