Class: Mongo::Tracing::OpenTelemetry::CommandTracer Private
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Instance Chain:
|
|
| Inherits: | Object |
| Defined in: | lib/mongo/tracing/open_telemetry/command_tracer.rb |
Overview
CommandTracer is responsible for tracing MongoDB server commands using ::Mongo::Tracing::OpenTelemetry.
Constant Summary
-
ELLIPSIS =
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 348
Ellipsis for truncated query text.
'...' -
EXCLUDED_KEYS =
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 345
Keys to exclude from query text capture.
%w[lsid $db $clusterTime signature].freeze
-
HELLO_COMMANDS =
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 31
Commands for which a span MUST NOT be created. The OpenTelemetry spec requires drivers to skip command spans for sensitive commands listed in the Command Logging and
::Mongo::Monitoringspec. We additionally skip hello / legacy hello in all forms — these are handshake/heartbeat traffic and would only add noise to traces.%w[hello ismaster isMaster].freeze
::Mongo::Monitoring::Event::Secure - Included
Class Method Summary
-
.new(otel_tracer, parent_tracer, query_text_max_length: 0) ⇒ CommandTracer
constructor
Internal use only
Initializes a new
CommandTracer.
Instance Attribute Summary
-
#query_text? ⇒ Boolean
readonly
private
Internal use only
Checks if query text capture is enabled.
Instance Method Summary
-
#start_span(message, operation_context, connection)
Internal use only
Starts a span for a MongoDB command.
-
#trace_command(message, _operation_context, connection) { ... } ⇒ Object
Internal use only
Trace a MongoDB command.
-
#apply_deferred_attributes(span, message)
private
Internal use only
Sets the remaining attributes on a span that is recording.
-
#collection_name(message) ⇒ String | nil
private
Internal use only
Extracts the collection name from the command message.
-
#command_name(message) ⇒ String
private
Internal use only
Extracts the command name from the message.
-
#connection_attributes(connection) ⇒ Hash
private
Internal use only
Returns connection-related attributes.
-
#create_command_span(message, connection) ⇒ OpenTelemetry::Trace::Span
private
Internal use only
Creates a span for a command.
-
#cursor_id(message) ⇒ Integer | nil
private
Internal use only
Extracts the cursor ID from getMore commands.
-
#database(message) ⇒ String
private
Internal use only
Extracts the database name from the message.
-
#handle_command_exception(span, exception)
private
Internal use only
Handles exceptions that occur during command execution.
-
#lsid(message) ⇒ BSON::Binary | nil
private
Internal use only
Extracts the logical session ID from the command.
-
#maybe_trace_error(result, span)
private
Internal use only
Records error status code if the command failed.
-
#normalize_cursor_id(cursor_id) ⇒ Integer
private
Internal use only
Normalizes a cursor id to a plain Integer.
-
#process_command_result(result, span)
private
Internal use only
Processes the command result and updates span attributes.
-
#process_cursor_context(result, span)
private
Internal use only
Processes cursor context from the command result.
-
#query_summary(message) ⇒ String
private
Internal use only
Generates a summary string for the query.
-
#query_text(message) ⇒ String | nil
readonly
private
Internal use only
Extracts and formats the query text from the command.
-
#skip_tracing?(message) ⇒ Boolean
private
Internal use only
Determines whether the command must not be traced.
-
#span_attributes(message, connection) ⇒ Hash
private
Internal use only
Builds the attributes passed at span creation: the cheap set the
::Mongo::Tracing::OpenTelemetryspec recommends. -
#trace_with_span(span, message) ⇒ Object
private
Internal use only
Runs the command with the span made current, and finishes the span.
-
#txn_number(message) ⇒ Integer | nil
private
Internal use only
Extracts the transaction number from the command.
::Mongo::Monitoring::Event::Secure - Included
| #compression_allowed? | Is compression allowed for a given command message. |
| #redacted | Redact secure information from the document if: |
| #sensitive? | Check whether the command is sensitive in terms of command monitoring spec. |
Constructor Details
.new(otel_tracer, parent_tracer, query_text_max_length: 0) ⇒ CommandTracer
Initializes a new CommandTracer.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 40
def initialize(otel_tracer, parent_tracer, query_text_max_length: 0) @otel_tracer = otel_tracer @parent_tracer = parent_tracer @query_text_max_length = query_text_max_length end
Instance Attribute Details
#query_text? ⇒ Boolean (readonly, private)
Checks if query text capture is enabled.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 305
def query_text? @query_text_max_length.positive? end
Instance Method Details
#apply_deferred_attributes(span, message) (private)
Sets the remaining attributes on a span that is recording. They are not visible to the sampler; only custom samplers keying on them are affected.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 187
def apply_deferred_attributes(span, ) { 'db.query.summary' => query_summary(), 'db.query.text' => query_text(), 'db.mongodb.cursor_id' => cursor_id(), 'db.mongodb.lsid' => lsid(), 'db.mongodb.txn_number' => txn_number() }.each do |key, value| span.set_attribute(key, value) unless value.nil? end end
#collection_name(message) ⇒ String | nil (private)
Extracts the collection name from the command message.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 271
def collection_name() case command_name() when 'getMore' .documents.first['collection'].to_s when 'listCollections', 'listDatabases', 'commitTransaction', 'abortTransaction' nil else value = .documents.first.values.first # Return nil if the value is not a string (e.g., for admin commands that have numeric values) value.is_a?(String) ? value : nil end end
#command_name(message) ⇒ String (private)
Extracts the command name from the message.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 289
def command_name() .documents.first.keys.first.to_s end
#connection_attributes(connection) ⇒ Hash (private)
Returns connection-related attributes.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 204
def connection_attributes(connection) { 'server.port' => connection.address.port, 'server.address' => connection.address.host, 'network.transport' => connection.transport.to_s, 'db.mongodb.server_connection_id' => connection.server.description.server_connection_id, 'db.mongodb.driver_connection_id' => connection.id } end
#create_command_span(message, connection) ⇒ OpenTelemetry::Trace::Span (private)
Creates a span for a command.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 133
def create_command_span(, connection) @otel_tracer.start_span( command_name(), attributes: span_attributes(, connection), kind: :client ) end
#cursor_id(message) ⇒ Integer | nil (private)
Extracts the cursor ID from getMore commands.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 314
def cursor_id() return unless command_name() == 'getMore' .documents.first['getMore'].value end
#database(message) ⇒ String (private)
Extracts the database name from the message.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 298
def database() .documents.first['$db'].to_s end
#handle_command_exception(span, exception) (private)
Handles exceptions that occur during command execution.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 154
def handle_command_exception(span, exception) if exception.is_a?(Mongo::Error::OperationFailure) span.set_attribute('db.response.status_code', exception.code.to_s) end span.record_exception(exception) span.status = ::OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{exception.class}") end
#lsid(message) ⇒ BSON::Binary | nil (private)
Extracts the logical session ID from the command.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 325
def lsid() lsid_doc = .documents.first['lsid'] return unless lsid_doc lsid_doc['id'].to_uuid end
#maybe_trace_error(result, span) (private)
Records error status code if the command failed.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 242
def maybe_trace_error(result, span) return if result.successful? span.set_attribute('db.response.status_code', result.error.code.to_s) begin result.validate! rescue Mongo::Error::OperationFailure => e span.record_exception(e) end end
#normalize_cursor_id(cursor_id) ⇒ Integer (private)
Normalizes a cursor id to a plain Integer.
OP_MSG replies deserialized in :bson mode expose the cursor id as a
BSON::Int64, which does not implement Numeric#positive? and is not a
valid ::Mongo::Tracing::OpenTelemetry attribute type.
#process_command_result(result, span) (private)
Processes the command result and updates span attributes.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 145
def process_command_result(result, span) process_cursor_context(result, span) maybe_trace_error(result, span) end
#process_cursor_context(result, span) (private)
Processes cursor context from the command result.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 218
def process_cursor_context(result, span) cursor_id = normalize_cursor_id(result.cursor_id) return unless cursor_id.positive? span.set_attribute('db.mongodb.cursor_id', cursor_id) end
#query_summary(message) ⇒ String (private)
Generates a summary string for the query.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 258
def query_summary() if (coll_name = collection_name()) "#{command_name()} #{database()}.#{coll_name}" else "#{command_name()} #{database()}" end end
#query_text(message) ⇒ String | nil (readonly, private)
Extracts and formats the query text from the command.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 355
def query_text() return unless query_text? text = .payload['command'] .reject { |key, _| EXCLUDED_KEYS.include?(key) } .to_json if text.length > @query_text_max_length "#{text[0...@query_text_max_length]}#{ELLIPSIS}" else text end end
#skip_tracing?(message) ⇒ Boolean (private)
Determines whether the command must not be traced. Sensitive auth
commands carry credentials in their payloads (SCRAM proofs, cleartext
passwords, etc.) and the ::Mongo::Tracing::OpenTelemetry spec requires drivers to skip
command spans for them. Hello / legacy hello are also skipped to keep
handshake traffic out of traces.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 120
def skip_tracing?() name = command_name() return true if HELLO_COMMANDS.include?(name) sensitive?(command_name: name, document: .documents.first) end
#span_attributes(message, connection) ⇒ Hash (private)
Builds the attributes passed at span creation: the cheap set the
::Mongo::Tracing::OpenTelemetry spec recommends. They are built for every span,
recorded or not, and are the only ones the sampler sees. The rest
are set by apply_deferred_attributes once the span is known to
record.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 172
def span_attributes(, connection) { 'db.system.name' => 'mongodb', 'db.namespace' => database(), 'db.collection.name' => collection_name(), 'db.command.name' => command_name() }.compact.merge(connection_attributes(connection)) end
#start_span(message, operation_context, connection)
Starts a span for a MongoDB command.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 51
def start_span(, operation_context, connection); end
#trace_command(message, _operation_context, connection) { ... } ⇒ Object
Trace a MongoDB command.
Creates an ::Mongo::Tracing::OpenTelemetry span for the command, capturing attributes such as
command name, database name, collection name, server address, connection IDs,
and optionally query text. The span is automatically nested under the current
operation span and is finished when the command completes or fails.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 67
def trace_command(, _operation_context, connection, &block) return yield if skip_tracing?() # Commands should always be nested under their operation span, not directly under # the transaction span. Don't pass with_parent to use automatic parent resolution # from the currently active span (the operation span). span = create_command_span(, connection) # With no SDK and no valid parent, the API returns a span with an # invalid context. It cannot be made current, recorded or # propagated, so it gets no work at all, not even finishing it. return yield unless span.context.valid? trace_with_span(span, , &block) end
#trace_with_span(span, message) ⇒ Object (private)
Runs the command with the span made current, and finishes the span. A span that is not recording is still made current, so that its context is propagated, but gets no further attributes and no outcome: the SDK would discard them.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 95
def trace_with_span(span, ) recording = span.recording? apply_deferred_attributes(span, ) if recording ::OpenTelemetry::Trace.with_span(span) do |s, _c| yield.tap do |result| process_command_result(result, s) if recording end end rescue Exception => e handle_command_exception(span, e) if recording raise e ensure span.finish end
#txn_number(message) ⇒ Integer | nil (private)
Extracts the transaction number from the command.
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 337
def txn_number() txn_num = .documents.first['txnNumber'] return unless txn_num txn_num.value end