# frozen_string_literal: truemoduleGraphQLmoduleTracing# 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:# {https://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|falsemoduleAppOpticsTrace# These GraphQL events will show up as 'graphql.prep' spansPREP_KEYS=['lex','parse','validate','analyze_query','analyze_multiplex'].freeze# These GraphQL events will show up as 'graphql.execute' spansEXEC_KEYS=['execute_multiplex','execute_query','execute_query_lazy'].freeze# 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 useddefself.versionGem::Version.new('1.0.0')end['lex','parse','validate','analyze_query','analyze_multiplex','execute_multiplex','execute_query','execute_query_lazy',].eachdo|trace_method|module_eval<<-RUBY,__FILE__,__LINE__
def #{trace_method}(**data)
return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
layer = span_name("#{trace_method}")
kvs = metadata(data, layer)
kvs[:Key] = "#{trace_method}" if (PREP_KEYS + EXEC_KEYS).include?("#{trace_method}")
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
super
end
end
RUBYenddefplatform_execute_field(platform_key,data)returnsuperif!defined?(AppOpticsAPM)||gql_config[:enabled]==falselayer=platform_keykvs=metadata(data,layer)::AppOpticsAPM::SDK.trace(layer,kvs)dokvs.clear# we don't have to send them twiceyieldendenddefauthorized(**data)returnsuperif!defined?(AppOpticsAPM)||gql_config[:enabled]==falselayer=@platform_authorized_key_cache[data[:type]]kvs=metadata(data,layer)::AppOpticsAPM::SDK.trace(layer,kvs)dokvs.clear# we don't have to send them twicesuperendenddefauthorized_lazy(**data)returnsuperif!defined?(AppOpticsAPM)||gql_config[:enabled]==falselayer=@platform_authorized_key_cache[data[:type]]kvs=metadata(data,layer)::AppOpticsAPM::SDK.trace(layer,kvs)dokvs.clear# we don't have to send them twicesuperendenddefresolve_type(**data)returnsuperif!defined?(AppOpticsAPM)||gql_config[:enabled]==falselayer=@platform_resolve_type_key_cache[data[:type]]kvs=metadata(data,layer)::AppOpticsAPM::SDK.trace(layer,kvs)dokvs.clear# we don't have to send them twicesuperendenddefresolve_type_lazy(**data)returnsuperif!defined?(AppOpticsAPM)||gql_config[:enabled]==falselayer=@platform_resolve_type_key_cache[data[:type]]kvs=metadata(data,layer)::AppOpticsAPM::SDK.trace(layer,kvs)dokvs.clear# we don't have to send them twicesuperendendincludePlatformTracedefplatform_field_key(field)"graphql.#{field.owner.graphql_name}.#{field.graphql_name}"enddefplatform_authorized_key(type)"graphql.authorized.#{type.graphql_name}"enddefplatform_resolve_type_key(type)"graphql.resolve_type.#{type.graphql_name}"endprivatedefgql_config::AppOpticsAPM::Config[:graphql]||={}enddeftransaction_name(query)returnifgql_config[:transaction_name]==false||::AppOpticsAPM::SDK.get_transaction_namesplit_query=query.strip.split(/\W+/,3)split_query[0]='query'ifsplit_query[0].empty?name="graphql.#{split_query[0..1].join('.')}"::AppOpticsAPM::SDK.set_transaction_name(name)enddefmultiplex_transaction_name(names)returnifgql_config[:transaction_name]==false||::AppOpticsAPM::SDK.get_transaction_namename="graphql.multiplex.#{names.join('.')}"name="#{name[0..251]}..."ifname.length>254::AppOpticsAPM::SDK.set_transaction_name(name)enddefspan_name(key)return'graphql.prep'ifPREP_KEYS.include?(key)return'graphql.execute'ifEXEC_KEYS.include?(key)key[/^graphql\./]?key:"graphql.#{key}"end# rubocop:disable Metrics/AbcSize, Metrics/MethodLengthdefmetadata(data,layer)data.keys.mapdo|key|casekeywhen:contextgraphql_context(data[key],layer)when:querygraphql_query(data[key])when:query_stringgraphql_query_string(data[key])when:multiplexgraphql_multiplex(data[key])when:path[key,data[key].join(".")]else[key,data[key]]endend.flatten(2).each_slice(2).to_h.merge(Spec:'graphql')end# rubocop:enable Metrics/AbcSize, Metrics/MethodLengthdefgraphql_context(context,layer)context.errors&&context.errors.eachdo|err|AppOpticsAPM::API.log_exception(layer,err)end[[:Path,context.path.join('.')]]enddefgraphql_query(query)return[]unlessqueryquery_string=query.query_stringquery_string=remove_comments(query_string)ifgql_config[:remove_comments]!=falsequery_string=sanitize(query_string)ifgql_config[:sanitize_query]!=false[[:InboundQuery,query_string],[:Operation,query.selected_operation_name]]enddefgraphql_query_string(query_string)query_string=remove_comments(query_string)ifgql_config[:remove_comments]!=falsequery_string=sanitize(query_string)ifgql_config[:sanitize_query]!=false[:InboundQuery,query_string]enddefgraphql_multiplex(data)names=data.queries.map(&:operations).map(&:keys).flatten.compactmultiplex_transaction_name(names)ifnames.size>1[:Operations,names.join(', ')]enddefsanitize(query)returnunlessquery# remove argumentsquery.gsub(/"[^"]*"/,'"?"')# strings.gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/,'?')# ints + floats.gsub(/\[[^\]]*\]/,'[?]')# arraysenddefremove_comments(query)returnunlessqueryquery.gsub(/#[^\n\r]*/,'')endendendend