123456789_123456789_123456789_123456789_123456789_

Class: Mongo::Tracing::OpenTelemetry::CommandTracer Private

Do not use. This class is for internal use only.
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

::Mongo::Monitoring::Event::Secure - Included

REDACTED_COMMANDS

Class Method Summary

Instance Attribute Summary

Instance Method Summary

::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.

Instance Attribute Details

#query_text?Boolean (readonly, private)

Checks if query text capture is enabled.

Returns:

  • (Boolean)

    true if query text should be captured.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 281

def query_text?
  @query_text_max_length.positive?
end

Instance Method Details

#base_attributes(message) ⇒ Hash (private)

Returns base database and command attributes.

Parameters:

Returns:

  • (Hash)

    base span attributes.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 163

def base_attributes(message)
  {
    'db.system.name' => 'mongodb',
    'db.namespace' => database(message),
    'db.collection.name' => collection_name(message),
    'db.command.name' => command_name(message),
    'db.query.summary' => query_summary(message),
    'db.query.text' => query_text(message)
  }
end

#collection_name(message) ⇒ String | nil (private)

Extracts the collection name from the command message.

Parameters:

Returns:

  • (String | nil)

    the collection name, or nil if not applicable.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 247

def collection_name(message)
  case command_name(message)
  when 'getMore'
    message.documents.first['collection'].to_s
  when 'listCollections', 'listDatabases', 'commitTransaction', 'abortTransaction'
    nil
  else
    value = message.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.

Parameters:

Returns:

  • (String)

    the command name.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 265

def command_name(message)
  message.documents.first.keys.first.to_s
end

#connection_attributes(connection) ⇒ Hash (private)

Returns connection-related attributes.

Parameters:

Returns:

  • (Hash)

    connection span attributes.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 179

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.

Parameters:

Returns:

  • (OpenTelemetry::Trace::Span)

    the created span.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 112

def create_command_span(message, connection)
  @otel_tracer.start_span(
    command_name(message),
    attributes: span_attributes(message, connection),
    kind: :client
  )
end

#cursor_id(message) ⇒ Integer | nil (private)

Extracts the cursor ID from getMore commands.

Parameters:

Returns:

  • (Integer | nil)

    the cursor ID, or nil if not a getMore command.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 290

def cursor_id(message)
  return unless command_name(message) == 'getMore'

  message.documents.first['getMore'].value
end

#database(message) ⇒ String (private)

Extracts the database name from the message.

Parameters:

Returns:

  • (String)

    the database name.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 274

def database(message)
  message.documents.first['$db'].to_s
end

#handle_command_exception(span, exception) (private)

Handles exceptions that occur during command execution.

Parameters:

  • span (OpenTelemetry::Trace::Span | nil)

    the span.

  • exception (Exception)

    the exception that occurred.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 135

def handle_command_exception(span, exception)
  return unless span

  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.

Parameters:

Returns:

  • (BSON::Binary | nil)

    the session ID, or nil if not present.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 301

def lsid(message)
  lsid_doc = message.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.

Parameters:

  • result (Object)

    the command result.

  • span (OpenTelemetry::Trace::Span)

    the current span.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 218

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

#process_command_result(result, cursor_id, context, span) (private)

Processes the command result and updates span attributes.

Parameters:

  • result (Object)

    the command result.

  • cursor_id (Integer | nil)

    the cursor ID.

  • context (OpenTelemetry::Context)

    the context.

  • span (OpenTelemetry::Trace::Span)

    the current span.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 126

def process_command_result(result, cursor_id, context, span)
  process_cursor_context(result, cursor_id, context, span)
  maybe_trace_error(result, span)
end

#process_cursor_context(result, _cursor_id, _context, span) (private)

Processes cursor context from the command result.

Parameters:

  • result (Object)

    the command result.

  • _cursor_id (Integer | nil)

    the cursor ID (unused).

  • _context (OpenTelemetry::Context)

    the context (unused).

  • span (OpenTelemetry::Trace::Span)

    the current span.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 208

def process_cursor_context(result, _cursor_id, _context, span)
  return unless result.has_cursor_id? && result.cursor_id.positive?

  span.set_attribute('db.mongodb.cursor_id', result.cursor_id)
end

#query_summary(message) ⇒ String (private)

Generates a summary string for the query.

Parameters:

Returns:

  • (String)

    summary in format "command_name db.collection" or "command_name db".

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 234

def query_summary(message)
  if (coll_name = collection_name(message))
    "#{command_name(message)} #{database(message)}.#{coll_name}"
  else
    "#{command_name(message)} #{database(message)}"
  end
end

#query_text(message) ⇒ String | nil (readonly, private)

Extracts and formats the query text from the command.

Parameters:

Returns:

  • (String | nil)

    JSON representation of the command, truncated if necessary, or nil if disabled.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 331

def query_text(message)
  return unless query_text?

  text = message
         .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

#session_attributes(message) ⇒ Hash (private)

Returns session and transaction attributes.

Parameters:

Returns:

  • (Hash)

    session span attributes.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 194

def session_attributes(message)
  {
    'db.mongodb.cursor_id' => cursor_id(message),
    'db.mongodb.lsid' => lsid(message),
    'db.mongodb.txn_number' => txn_number(message)
  }
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.

Parameters:

Returns:

  • (Boolean)

    true when no command span should be created.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 99

def skip_tracing?(message)
  name = command_name(message)
  return true if HELLO_COMMANDS.include?(name)

  sensitive?(command_name: name, document: message.documents.first)
end

#span_attributes(message, connection) ⇒ Hash (private)

Builds span attributes for the command.

Parameters:

Returns:

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 151

def span_attributes(message, connection)
  base_attributes(message)
    .merge(connection_attributes(connection))
    .merge(session_attributes(message))
    .compact
end

#start_span(message, operation_context, connection)

Starts a span for a MongoDB command.

Parameters:

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 51

def start_span(message, 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.

Parameters:

Yields:

  • the block representing the command to be traced.

Returns:

  • (Object)

    the result of the command.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 68

def trace_command(message, _operation_context, connection)
  return yield if skip_tracing?(message)

  # 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(message, connection)
  ::OpenTelemetry::Trace.with_span(span) do |s, c|
    yield.tap do |result|
      process_command_result(result, cursor_id(message), c, s)
    end
  end
rescue Exception => e
  handle_command_exception(span, e)
  raise e
ensure
  span&.finish
end

#txn_number(message) ⇒ Integer | nil (private)

Extracts the transaction number from the command.

Parameters:

Returns:

  • (Integer | nil)

    the transaction number, or nil if not present.

[ GitHub ]

  
# File 'lib/mongo/tracing/open_telemetry/command_tracer.rb', line 313

def txn_number(message)
  txn_num = message.documents.first['txnNumber']
  return unless txn_num

  txn_num.value
end