class GraphQL::Tracing::AppOpticsTracing

This class uses the AppopticsAPM SDK from the appoptics_apm gem to create traces for GraphQL.

There are 4 configurations available. They can be set in the appoptics_apm config file or in code. Please see: {docs.appoptics.com/kb/apm_tracing/ruby/configure}

AppOpticsAPM::Config[:graphql][:enabled] = true|false
AppOpticsAPM::Config[:graphql][:transaction_name]  = true|false
AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false
AppOpticsAPM::Config[:graphql][:remove_comments] = true|false

Constants

EXEC_KEYS

These GraphQL events will show up as 'graphql.execute' spans

PREP_KEYS

These GraphQL events will show up as 'graphql.prep' spans

Public Class Methods

version() click to toggle source

During auto-instrumentation this version of AppOpticsTracing is compared with the version provided in the appoptics_apm gem, so that the newer version of the class can be used

# File lib/graphql/tracing/appoptics_tracing.rb, line 27
def self.version
  Gem::Version.new('1.0.0')
end

Public Instance Methods

platform_authorized_key(type) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 61
def platform_authorized_key(type)
  "graphql.authorized.#{type.graphql_name}"
end
platform_field_key(type, field) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 57
def platform_field_key(type, field)
  "graphql.#{type.graphql_name}.#{field.graphql_name}"
end
platform_resolve_type_key(type) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 65
def platform_resolve_type_key(type)
  "graphql.resolve_type.#{type.graphql_name}"
end
platform_trace(platform_key, _key, data) { || ... } click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 42
def platform_trace(platform_key, _key, data)
  return yield if !defined?(AppOpticsAPM) || gql_config[:enabled] == false

  layer = span_name(platform_key)
  kvs = metadata(data, layer)
  kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key)

  transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute'

  ::AppOpticsAPM::SDK.trace(layer, kvs) do
    kvs.clear # we don't have to send them twice
    yield
  end
end

Private Instance Methods

gql_config() click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 71
def gql_config
  ::AppOpticsAPM::Config[:graphql] ||= {}
end
graphql_context(context, layer) click to toggle source

rubocop:enable Metrics/AbcSize, Metrics/MethodLength

# File lib/graphql/tracing/appoptics_tracing.rb, line 124
def graphql_context(context, layer)
  context.errors && context.errors.each do |err|
    AppOpticsAPM::API.log_exception(layer, err)
  end

  [[:Path, context.path.join('.')]]
end
graphql_multiplex(data) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 150
def graphql_multiplex(data)
  names = data.queries.map(&:operations).map(&:keys).flatten.compact
  multiplex_transaction_name(names) if names.size > 1

  [:Operations, names.join(', ')]
end
graphql_query(query) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 132
def graphql_query(query)
  return [] unless query

  query_string = query.query_string
  query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
  query_string = sanitize(query_string) if gql_config[:sanitize_query] != false

  [[:InboundQuery, query_string],
   [:Operation, query.selected_operation_name]]
end
graphql_query_string(query_string) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 143
def graphql_query_string(query_string)
  query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
  query_string = sanitize(query_string) if gql_config[:sanitize_query] != false

  [:InboundQuery, query_string]
end
metadata(data, layer) click to toggle source

rubocop:disable Metrics/AbcSize, Metrics/MethodLength

# File lib/graphql/tracing/appoptics_tracing.rb, line 104
def metadata(data, layer)
  data.keys.map do |key|
    case key
    when :context
      graphql_context(data[key], layer)
    when :query
      graphql_query(data[key])
    when :query_string
      graphql_query_string(data[key])
    when :multiplex
      graphql_multiplex(data[key])
    when :path
      [key, data[key].join(".")]
    else
      [key, data[key]]
    end
  end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
end
multiplex_transaction_name(names) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 86
def multiplex_transaction_name(names)
  return if gql_config[:transaction_name] == false ||
    ::AppOpticsAPM::SDK.get_transaction_name

  name = "graphql.multiplex.#{names.join('.')}"
  name = "#{name[0..251]}..." if name.length > 254

  ::AppOpticsAPM::SDK.set_transaction_name(name)
end
remove_comments(query) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 166
def remove_comments(query)
  return unless query

  query.gsub(/#[^\n\r]*/, '')
end
sanitize(query) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 157
def sanitize(query)
  return unless query

  # remove arguments
  query.gsub(/"[^"]*"/, '"?"')                 # strings
    .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats
    .gsub(/\[[^\]]*\]/, '[?]')              # arrays
end
span_name(key) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 96
def span_name(key)
  return 'graphql.prep' if PREP_KEYS.include?(key)
  return 'graphql.execute' if EXEC_KEYS.include?(key)

  key[/^graphql\./] ? key : "graphql.#{key}"
end
transaction_name(query) click to toggle source
# File lib/graphql/tracing/appoptics_tracing.rb, line 75
def transaction_name(query)
  return if gql_config[:transaction_name] == false ||
    ::AppOpticsAPM::SDK.get_transaction_name

  split_query = query.strip.split(/\W+/, 3)
  split_query[0] = 'query' if split_query[0].empty?
  name = "graphql.#{split_query[0..1].join('.')}"

  ::AppOpticsAPM::SDK.set_transaction_name(name)
end