module PostgresExtension

Constants

COMPONENTS_REGEX_MAP

From: github.com/newrelic/newrelic-ruby-agent/blob/9787095d4b5b2d8fcaf2fdbd964ed07c731a8b6b/lib/new_relic/agent/database/obfuscation_helpers.rb#L9-L34

EXEC_ISH_METHODS

These are all alike in that they will have a SQL statement as the first parameter. That statement may possibly be parameterized, but we can still use it - the obfuscation code will just transform $1 -> $? in that case (which is fine enough).

EXEC_PREPARED_ISH_METHODS

The following methods take a prepared statement name as their first parameter - everything after that is either potentially quite sensitive (an array of bind params) or not useful to us. We trace them all alike.

POSTGRES_COMPONENTS
PREPARE_ISH_METHODS

The following methods all take a statement name as the first parameter, and a SQL statement as the second - and possibly further parameters after that. We can trace them all alike.

SQL_COMMANDS

A list of SQL commands, from: www.postgresql.org/docs/current/sql-commands.html Commands are truncated to their first word, and all duplicates are removed, This favors brevity and low-cardinality over descriptiveness.

UNMATCHED_PAIRS_REGEX

Public Instance Methods

client_attributes() click to toggle source
# File lib/instrumentation/postgres.rb, line 221
def client_attributes
  attributes = {
    'db.system' => 'postgresql',
    'db.user' => conninfo_hash[:user]&.to_s,
    'db.name' => database_name,
    'net.peer.name' => conninfo_hash[:host]&.to_s
  }
  # attributes['peer.service'] = config[:peer_service] # if config[:peer_service]

  attributes.merge(transport_attrs).reject { |_, v| v.nil? }
end
config() click to toggle source
# File lib/instrumentation/postgres.rb, line 142
def config
  EpsagonPostgresInstrumentation.instance.config
end
database_name() click to toggle source
# File lib/instrumentation/postgres.rb, line 217
def database_name
  conninfo_hash[:dbname]&.to_s
end
extract_operation(sql) click to toggle source
# File lib/instrumentation/postgres.rb, line 208
def extract_operation(sql)
  # From: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/9244a08a8d014afe26b82b91cf86e407c2599d73/plugins/node/opentelemetry-instrumentation-pg/src/utils.ts#L35
  sql.to_s.split[0].to_s.upcase
end
generated_postgres_regex() click to toggle source
# File lib/instrumentation/postgres.rb, line 213
def generated_postgres_regex
  @generated_postgres_regex ||= Regexp.union(PostgresExtension::POSTGRES_COMPONENTS.map { |component| PostgresExtension::COMPONENTS_REGEX_MAP[component] })
end
lru_cache() click to toggle source
# File lib/instrumentation/postgres.rb, line 150
def lru_cache
  # When SQL is being sanitized, we know that this cache will
  # never be more than 50 entries * 2000 characters (so, presumably
  # 100k bytes - or 97k). When not sanitizing SQL, then this cache
  # could grow much larger - but the small cache size should otherwise
  # help contain memory growth. The intended use here is to cache
  # prepared SQL statements, so that we can attach a reasonable
  # `db.sql.statement` value to spans when those prepared statements
  # are executed later on.
  @lru_cache ||= LruCache.new(50)
end
span_attrs(kind, *args) click to toggle source

Rubocop is complaining about 19.31/18 for Metrics/AbcSize. But, getting that metric in line would force us over the module size limit! We can't win here unless we want to start abstracting things into a million pieces.

# File lib/instrumentation/postgres.rb, line 166
def span_attrs(kind, *args) # rubocop:disable Metrics/AbcSize
  if kind == :query
    operation = extract_operation(args[0])
    sql = args[0]
  else
    statement_name = args[0]

    if kind == :prepare
      sql = args[1]
      lru_cache[statement_name] = sql
      operation = 'PREPARE'
    else
      sql = lru_cache[statement_name]
      operation = 'EXECUTE'
    end
  end

  attrs = { 'db.operation' => validated_operation(operation), 'db.postgresql.prepared_statement_name' => statement_name }
  attrs['db.statement'] = sql if config[:epsagon][:metadata_only] == false
  attrs['db.sql.table'] = table_name(sql)
  attrs.reject! { |_, v| v.nil? }

  [database_name, client_attributes.merge(attrs)]
end
table_name(sql) click to toggle source
# File lib/instrumentation/postgres.rb, line 191
def table_name(sql)
  return '' if sql.nil?

  parsed_query = PgQuery.parse(sql)
  if parsed_query.tables.length == 0
    ''
  else
    parsed_query.tables[0]
  end
rescue PgQuery::ParseError
  ''
end
tracer() click to toggle source
# File lib/instrumentation/postgres.rb, line 146
def tracer
  EpsagonPostgresInstrumentation.instance.tracer
end
transport_attrs() click to toggle source
# File lib/instrumentation/postgres.rb, line 233
def transport_attrs
  if conninfo_hash[:host]&.start_with?('/')
    { 'net.transport' => 'Unix' }
  else
    {
      'net.transport' => 'IP.TCP',
      'net.peer.ip' => conninfo_hash[:hostaddr]&.to_s,
      'net.peer.port' => conninfo_hash[:port]&.to_s
    }
  end
end
validated_operation(operation) click to toggle source
# File lib/instrumentation/postgres.rb, line 204
def validated_operation(operation)
  operation if PostgresExtension::SQL_COMMANDS.include?(operation)
end