module OpenTelemetry::Instrumentation::Mysql2::Patches::Client

Module to prepend to Mysql2::Client for instrumentation

Constants

COMPONENTS_REGEX_MAP
MYSQL_COMPONENTS
QUERY_NAMES
QUERY_NAME_RE

Public Instance Methods

query(sql, options = {}) click to toggle source
Calls superclass method
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 53
def query(sql, options = {})
  attributes = client_attributes
  case config[:db_statement]
  when :include
    attributes['db.statement'] = sql
  when :obfuscate
    attributes['db.statement'] = obfuscate_sql(sql)
  end
  tracer.in_span(
    database_span_name(sql),
    attributes: attributes,
    kind: :client
  ) do
    super(sql, options)
  end
end

Private Instance Methods

client_attributes() click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 116
def client_attributes
  # The client specific attributes can be found via the query_options instance variable
  # exposed on the mysql2 Client
  # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26
  host = (query_options[:host] || query_options[:hostname]).to_s
  port = query_options[:port].to_s

  attributes = {
    'db.system' => 'mysql',
    'net.peer.name' => host,
    'net.peer.port' => port
  }
  attributes['db.name'] = database_name if database_name
  attributes['peer.service'] = config[:peer_service] if config[:peer_service]
  attributes
end
config() click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 137
def config
  Mysql2::Instrumentation.instance.config
end
database_name() click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 111
def database_name
  # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L78
  (query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s
end
database_span_name(sql) click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 95
def database_span_name(sql)
  # Setting span name to the SQL query without obfuscation would
  # result in PII + cardinality issues.
  # First attempt to infer the statement type then fallback to
  # current Otel approach {database.component_name}.{database_instance_name}
  # https://github.com/open-telemetry/opentelemetry-python/blob/39fa078312e6f41c403aa8cad1868264011f7546/ext/opentelemetry-ext-dbapi/tests/test_dbapi_integration.py#L53
  # This creates span names like mysql.default, mysql.replica, postgresql.staging etc.

  statement_type = extract_statement_type(sql)

  return statement_type unless statement_type.nil?

  # fallback
  database_name ? "mysql.#{database_name}" : 'mysql'
end
detect_unmatched_pairs(obfuscated) click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 86
def detect_unmatched_pairs(obfuscated)
  # We use this to check whether the query contains any quote characters
  # after obfuscation. If so, that's a good indication that the original
  # query was malformed, and so our obfuscation can't reliably find
  # literals. In such a case, we'll replace the entire query with a
  # placeholder.
  %r{'|"|\/\*|\*\/}.match(obfuscated)
end
extract_statement_type(sql) click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 141
def extract_statement_type(sql)
  QUERY_NAME_RE.match(sql) { |match| match[1].downcase } unless sql.nil?
rescue StandardError => e
  OpenTelemetry.logger.debug("Error extracting sql statement type: #{e.message}")
end
generated_mysql_regex() click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 82
def generated_mysql_regex
  @generated_mysql_regex ||= Regexp.union(MYSQL_COMPONENTS.map { |component| COMPONENTS_REGEX_MAP[component] })
end
obfuscate_sql(sql) click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 72
def obfuscate_sql(sql)
  if sql.size > 2000
    'SQL query too large to remove sensitive data ...'
  else
    obfuscated = sql.gsub(generated_mysql_regex, '?')
    obfuscated = 'Failed to obfuscate SQL query - quote characters remained after obfuscation' if detect_unmatched_pairs(obfuscated)
    obfuscated
  end
end
tracer() click to toggle source
# File lib/opentelemetry/instrumentation/mysql2/patches/client.rb, line 133
def tracer
  Mysql2::Instrumentation.instance.tracer
end