graphql 2.0.17 → 2.0.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast.rb +2 -2
  3. data/lib/graphql/backtrace/tracer.rb +1 -1
  4. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  5. data/lib/graphql/execution/interpreter/runtime.rb +96 -88
  6. data/lib/graphql/execution/interpreter.rb +8 -13
  7. data/lib/graphql/execution/lazy.rb +2 -4
  8. data/lib/graphql/execution/multiplex.rb +2 -1
  9. data/lib/graphql/language/lexer.rb +216 -1505
  10. data/lib/graphql/language/lexer.ri +744 -0
  11. data/lib/graphql/language/nodes.rb +2 -2
  12. data/lib/graphql/language/parser.rb +39 -38
  13. data/lib/graphql/language/parser.y +38 -37
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  15. data/lib/graphql/query/context.rb +57 -27
  16. data/lib/graphql/query.rb +15 -2
  17. data/lib/graphql/schema/field.rb +31 -19
  18. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  19. data/lib/graphql/schema/member/has_fields.rb +6 -1
  20. data/lib/graphql/schema/object.rb +2 -4
  21. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  22. data/lib/graphql/schema/timeout.rb +24 -28
  23. data/lib/graphql/schema/warden.rb +8 -1
  24. data/lib/graphql/schema.rb +42 -0
  25. data/lib/graphql/static_validation/validator.rb +1 -1
  26. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  27. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  28. data/lib/graphql/tracing/appsignal_trace.rb +66 -0
  29. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  30. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  31. data/lib/graphql/tracing/notifications_trace.rb +41 -0
  32. data/lib/graphql/tracing/platform_trace.rb +107 -0
  33. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  34. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  35. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  36. data/lib/graphql/tracing/scout_trace.rb +72 -0
  37. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  38. data/lib/graphql/tracing.rb +136 -39
  39. data/lib/graphql/type_kinds.rb +6 -3
  40. data/lib/graphql/types/relay/base_connection.rb +1 -1
  41. data/lib/graphql/version.rb +1 -1
  42. data/lib/graphql.rb +10 -7
  43. metadata +31 -7
  44. data/lib/graphql/language/lexer.rl +0 -280
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+
6
+ # This class uses the AppopticsAPM SDK from the appoptics_apm gem to create
7
+ # traces for GraphQL.
8
+ #
9
+ # There are 4 configurations available. They can be set in the
10
+ # appoptics_apm config file or in code. Please see:
11
+ # {https://docs.appoptics.com/kb/apm_tracing/ruby/configure}
12
+ #
13
+ # AppOpticsAPM::Config[:graphql][:enabled] = true|false
14
+ # AppOpticsAPM::Config[:graphql][:transaction_name] = true|false
15
+ # AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false
16
+ # AppOpticsAPM::Config[:graphql][:remove_comments] = true|false
17
+ module AppOpticsTrace
18
+ # These GraphQL events will show up as 'graphql.prep' spans
19
+ PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze
20
+ # These GraphQL events will show up as 'graphql.execute' spans
21
+ EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
22
+
23
+ # During auto-instrumentation this version of AppOpticsTracing is compared
24
+ # with the version provided in the appoptics_apm gem, so that the newer
25
+ # version of the class can be used
26
+
27
+ def self.version
28
+ Gem::Version.new('1.0.0')
29
+ end
30
+
31
+ [
32
+ 'lex',
33
+ 'parse',
34
+ 'validate',
35
+ 'analyze_query',
36
+ 'analyze_multiplex',
37
+ 'execute_multiplex',
38
+ 'execute_query',
39
+ 'execute_query_lazy',
40
+ ].each do |trace_method|
41
+ module_eval <<-RUBY, __FILE__, __LINE__
42
+ def #{trace_method}(**data)
43
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
44
+ layer = span_name("#{trace_method}")
45
+ kvs = metadata(data, layer)
46
+ kvs[:Key] = "#{trace_method}" if (PREP_KEYS + EXEC_KEYS).include?("#{trace_method}")
47
+
48
+ transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute'
49
+
50
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
51
+ kvs.clear # we don't have to send them twice
52
+ super
53
+ end
54
+ end
55
+ RUBY
56
+ end
57
+
58
+ def platform_execute_field(platform_key, data)
59
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
60
+ layer = platform_key
61
+ kvs = metadata(data, layer)
62
+
63
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
64
+ kvs.clear # we don't have to send them twice
65
+ yield
66
+ end
67
+ end
68
+
69
+ def authorized(**data)
70
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
71
+ layer = @platform_authorized_key_cache[data[:type]]
72
+ kvs = metadata(data, layer)
73
+
74
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
75
+ kvs.clear # we don't have to send them twice
76
+ super
77
+ end
78
+ end
79
+
80
+ def authorized_lazy(**data)
81
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
82
+ layer = @platform_authorized_key_cache[data[:type]]
83
+ kvs = metadata(data, layer)
84
+
85
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
86
+ kvs.clear # we don't have to send them twice
87
+ super
88
+ end
89
+ end
90
+
91
+ def resolve_type(**data)
92
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
93
+ layer = @platform_resolve_type_key_cache[data[:type]]
94
+ kvs = metadata(data, layer)
95
+
96
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
97
+ kvs.clear # we don't have to send them twice
98
+ super
99
+ end
100
+ end
101
+
102
+ def resolve_type_lazy(**data)
103
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
104
+ layer = @platform_resolve_type_key_cache[data[:type]]
105
+ kvs = metadata(data, layer)
106
+
107
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
108
+ kvs.clear # we don't have to send them twice
109
+ super
110
+ end
111
+ end
112
+
113
+ include PlatformTrace
114
+
115
+ def platform_field_key(field)
116
+ "graphql.#{field.owner.graphql_name}.#{field.graphql_name}"
117
+ end
118
+
119
+ def platform_authorized_key(type)
120
+ "graphql.authorized.#{type.graphql_name}"
121
+ end
122
+
123
+ def platform_resolve_type_key(type)
124
+ "graphql.resolve_type.#{type.graphql_name}"
125
+ end
126
+
127
+ private
128
+
129
+ def gql_config
130
+ ::AppOpticsAPM::Config[:graphql] ||= {}
131
+ end
132
+
133
+ def transaction_name(query)
134
+ return if gql_config[:transaction_name] == false ||
135
+ ::AppOpticsAPM::SDK.get_transaction_name
136
+
137
+ split_query = query.strip.split(/\W+/, 3)
138
+ split_query[0] = 'query' if split_query[0].empty?
139
+ name = "graphql.#{split_query[0..1].join('.')}"
140
+
141
+ ::AppOpticsAPM::SDK.set_transaction_name(name)
142
+ end
143
+
144
+ def multiplex_transaction_name(names)
145
+ return if gql_config[:transaction_name] == false ||
146
+ ::AppOpticsAPM::SDK.get_transaction_name
147
+
148
+ name = "graphql.multiplex.#{names.join('.')}"
149
+ name = "#{name[0..251]}..." if name.length > 254
150
+
151
+ ::AppOpticsAPM::SDK.set_transaction_name(name)
152
+ end
153
+
154
+ def span_name(key)
155
+ return 'graphql.prep' if PREP_KEYS.include?(key)
156
+ return 'graphql.execute' if EXEC_KEYS.include?(key)
157
+
158
+ key[/^graphql\./] ? key : "graphql.#{key}"
159
+ end
160
+
161
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
162
+ def metadata(data, layer)
163
+ data.keys.map do |key|
164
+ case key
165
+ when :context
166
+ graphql_context(data[key], layer)
167
+ when :query
168
+ graphql_query(data[key])
169
+ when :query_string
170
+ graphql_query_string(data[key])
171
+ when :multiplex
172
+ graphql_multiplex(data[key])
173
+ when :path
174
+ [key, data[key].join(".")]
175
+ else
176
+ [key, data[key]]
177
+ end
178
+ end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
179
+ end
180
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
181
+
182
+ def graphql_context(context, layer)
183
+ context.errors && context.errors.each do |err|
184
+ AppOpticsAPM::API.log_exception(layer, err)
185
+ end
186
+
187
+ [[:Path, context.path.join('.')]]
188
+ end
189
+
190
+ def graphql_query(query)
191
+ return [] unless query
192
+
193
+ query_string = query.query_string
194
+ query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
195
+ query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
196
+
197
+ [[:InboundQuery, query_string],
198
+ [:Operation, query.selected_operation_name]]
199
+ end
200
+
201
+ def graphql_query_string(query_string)
202
+ query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
203
+ query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
204
+
205
+ [:InboundQuery, query_string]
206
+ end
207
+
208
+ def graphql_multiplex(data)
209
+ names = data.queries.map(&:operations).map(&:keys).flatten.compact
210
+ multiplex_transaction_name(names) if names.size > 1
211
+
212
+ [:Operations, names.join(', ')]
213
+ end
214
+
215
+ def sanitize(query)
216
+ return unless query
217
+
218
+ # remove arguments
219
+ query.gsub(/"[^"]*"/, '"?"') # strings
220
+ .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats
221
+ .gsub(/\[[^\]]*\]/, '[?]') # arrays
222
+ end
223
+
224
+ def remove_comments(query)
225
+ return unless query
226
+
227
+ query.gsub(/#[^\n\r]*/, '')
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ module AppsignalTrace
6
+ include PlatformTrace
7
+
8
+
9
+ # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
10
+ # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
11
+ # It can also be specified per-query with `context[:set_appsignal_action_name]`.
12
+ def initialize(set_action_name: false, **rest)
13
+ @set_action_name = set_action_name
14
+ super
15
+ end
16
+
17
+ {
18
+ "lex" => "lex.graphql",
19
+ "parse" => "parse.graphql",
20
+ "validate" => "validate.graphql",
21
+ "analyze_query" => "analyze.graphql",
22
+ "analyze_multiplex" => "analyze.graphql",
23
+ "execute_multiplex" => "execute.graphql",
24
+ "execute_query" => "execute.graphql",
25
+ "execute_query_lazy" => "execute.graphql",
26
+ }.each do |trace_method, platform_key|
27
+ module_eval <<-RUBY, __FILE__, __LINE__
28
+ def #{trace_method}(**data)
29
+ #{
30
+ if trace_method == "execute_query"
31
+ <<-RUBY
32
+ set_this_txn_name = data[:query].context[:set_appsignal_action_name]
33
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name)
34
+ Appsignal::Transaction.current.set_action(transaction_name(data[:query]))
35
+ end
36
+ RUBY
37
+ end
38
+ }
39
+
40
+ Appsignal.instrument("#{platform_key}") do
41
+ super
42
+ end
43
+ end
44
+ RUBY
45
+ end
46
+
47
+ def platform_execute_field(platform_key)
48
+ Appsignal.instrument(platform_key) do
49
+ super
50
+ end
51
+ end
52
+
53
+ def platform_field_key(field)
54
+ "#{field.owner.graphql_name}.#{field.graphql_name}.graphql"
55
+ end
56
+
57
+ def platform_authorized_key(type)
58
+ "#{type.graphql_name}.authorized.graphql"
59
+ end
60
+
61
+ def platform_resolve_type_key(type)
62
+ "#{type.graphql_name}.resolve_type.graphql"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ module DataDogTrace
6
+ # @param analytics_enabled [Boolean] Deprecated
7
+ # @param analytics_sample_rate [Float] Deprecated
8
+ def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: "ruby-graphql", **rest)
9
+ if tracer.nil?
10
+ tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
11
+ end
12
+ @tracer = tracer
13
+
14
+ analytics_available = defined?(Datadog::Contrib::Analytics) \
15
+ && Datadog::Contrib::Analytics.respond_to?(:enabled?) \
16
+ && Datadog::Contrib::Analytics.respond_to?(:set_sample_rate)
17
+
18
+ @analytics_enabled = analytics_available && Datadog::Contrib::Analytics.enabled?(analytics_enabled)
19
+ @analytics_sample_rate = analytics_sample_rate
20
+ @service_name = service
21
+ super
22
+ end
23
+
24
+ {
25
+ 'lex' => 'lex.graphql',
26
+ 'parse' => 'parse.graphql',
27
+ 'validate' => 'validate.graphql',
28
+ 'analyze_query' => 'analyze.graphql',
29
+ 'analyze_multiplex' => 'analyze.graphql',
30
+ 'execute_multiplex' => 'execute.graphql',
31
+ 'execute_query' => 'execute.graphql',
32
+ 'execute_query_lazy' => 'execute.graphql',
33
+ }.each do |trace_method, trace_key|
34
+ module_eval <<-RUBY, __FILE__, __LINE__
35
+ def #{trace_method}(**data)
36
+ @tracer.trace("#{trace_key}", service: @service_name) do |span|
37
+ span.span_type = 'custom'
38
+ if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
39
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
40
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, '#{trace_method}')
41
+ end
42
+
43
+ #{
44
+ if trace_method == 'execute_multiplex'
45
+ <<-RUBY
46
+ operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
47
+
48
+ resource = if operations.empty?
49
+ first_query = data[:multiplex].queries.first
50
+ fallback_transaction_name(first_query && first_query.context)
51
+ else
52
+ operations
53
+ end
54
+ span.resource = resource if resource
55
+
56
+ # For top span of query, set the analytics sample rate tag, if available.
57
+ if @analytics_enabled
58
+ Datadog::Contrib::Analytics.set_sample_rate(span, @analytics_sample_rate)
59
+ end
60
+ RUBY
61
+ elsif trace_method == 'execute_query'
62
+ <<-RUBY
63
+ span.set_tag(:selected_operation_name, data[:query].selected_operation_name)
64
+ span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type)
65
+ span.set_tag(:query_string, data[:query].query_string)
66
+ RUBY
67
+ end
68
+ }
69
+ prepare_span("#{trace_method.sub("platform_", "")}", data, span)
70
+ super
71
+ end
72
+ end
73
+ RUBY
74
+ end
75
+
76
+ def platform_execute_field(platform_key, data, span_key = "execute_field")
77
+ @tracer.trace(platform_key, service: @service_name) do |span|
78
+ span.span_type = 'custom'
79
+ if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
80
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
81
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
82
+ end
83
+ prepare_span(span_key, data, span)
84
+ yield
85
+ end
86
+ end
87
+
88
+ def platform_execute_field_lazy(platform_key, data, &block)
89
+ platform_execute_field(platform_key, data, "execute_field_lazy", &block)
90
+ end
91
+
92
+ def authorized(object:, type:, query:, span_key: "authorized")
93
+ platform_key = @platform_authorized_key_cache[type]
94
+ @tracer.trace(platform_key, service: @service_name) do |span|
95
+ span.span_type = 'custom'
96
+ if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
97
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
98
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
99
+ end
100
+ prepare_span(span_key, {object: object, type: type, query: query}, span)
101
+ super(query: query, type: type, object: object)
102
+ end
103
+ end
104
+
105
+ def authorized_lazy(**kwargs, &block)
106
+ authorized(span_key: "authorized_lazy", **kwargs, &block)
107
+ end
108
+
109
+ def resolve_type(object:, type:, query:, span_key: "resolve_type")
110
+ platform_key = @platform_resolve_type_key_cache[type]
111
+ @tracer.trace(platform_key, service: @service_name) do |span|
112
+ span.span_type = 'custom'
113
+ if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
114
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
115
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
116
+ end
117
+ prepare_span(span_key, {object: object, type: type, query: query}, span)
118
+ super(query: query, type: type, object: object)
119
+ end
120
+ end
121
+
122
+ def resolve_type_lazy(**kwargs, &block)
123
+ resolve_type(span_key: "resolve_type_lazy", **kwargs, &block)
124
+ end
125
+
126
+ include PlatformTrace
127
+
128
+ # Implement this method in a subclass to apply custom tags to datadog spans
129
+ # @param key [String] The event being traced
130
+ # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
131
+ # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
132
+ def prepare_span(key, data, span)
133
+ end
134
+
135
+ def platform_field_key(field)
136
+ field.path
137
+ end
138
+
139
+ def platform_authorized_key(type)
140
+ "#{type.graphql_name}.authorized"
141
+ end
142
+
143
+ def platform_resolve_type_key(type)
144
+ "#{type.graphql_name}.resolve_type"
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ module NewRelicTrace
6
+ include PlatformTrace
7
+
8
+ # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
9
+ # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
10
+ # It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
11
+ def initialize(set_transaction_name: false, **_rest)
12
+ @set_transaction_name = set_transaction_name
13
+ super
14
+ end
15
+
16
+ def execute_query(query:)
17
+ set_this_txn_name = query.context[:set_new_relic_transaction_name]
18
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name)
19
+ NewRelic::Agent.set_transaction_name(transaction_name(query))
20
+ end
21
+ NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped("GraphQL/execute") do
22
+ super
23
+ end
24
+ end
25
+
26
+ {
27
+ "lex" => "GraphQL/lex",
28
+ "parse" => "GraphQL/parse",
29
+ "validate" => "GraphQL/validate",
30
+ "analyze_query" => "GraphQL/analyze",
31
+ "analyze_multiplex" => "GraphQL/analyze",
32
+ "execute_multiplex" => "GraphQL/execute",
33
+ "execute_query_lazy" => "GraphQL/execute",
34
+ }.each do |trace_method, platform_key|
35
+ module_eval <<-RUBY, __FILE__, __LINE__
36
+ def #{trace_method}(**_keys)
37
+ NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped("#{platform_key}") do
38
+ super
39
+ end
40
+ end
41
+ RUBY
42
+ end
43
+
44
+ def platform_execute_field(platform_key)
45
+ NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
46
+ yield
47
+ end
48
+ end
49
+
50
+ def platform_authorized(platform_key)
51
+ NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
52
+ yield
53
+ end
54
+ end
55
+
56
+ def platform_resolve_type(platform_key)
57
+ NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
58
+ yield
59
+ end
60
+ end
61
+
62
+ def platform_field_key(field)
63
+ "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
64
+ end
65
+
66
+ def platform_authorized_key(type)
67
+ "GraphQL/Authorize/#{type.graphql_name}"
68
+ end
69
+
70
+ def platform_resolve_type_key(type)
71
+ "GraphQL/ResolveType/#{type.graphql_name}"
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ # This implementation forwards events to a notification handler (i.e.
6
+ # ActiveSupport::Notifications or Dry::Monitor::Notifications)
7
+ # with a `graphql` suffix.
8
+ module NotificationsTrace
9
+ include PlatformTrace
10
+ # Initialize a new NotificationsTracing instance
11
+ #
12
+ # @param engine [#instrument(key, metadata, block)] The notifications engine to use
13
+ def initialize(engine:, **rest)
14
+ @notifications_engine = engine
15
+ super
16
+ end
17
+
18
+ {
19
+ "lex" => "lex.graphql",
20
+ "parse" => "parse.graphql",
21
+ "validate" => "validate.graphql",
22
+ "analyze_multiplex" => "analyze_multiplex.graphql",
23
+ "analyze_query" => "analyze_query.graphql",
24
+ "execute_query" => "execute_query.graphql",
25
+ "execute_query_lazy" => "execute_query_lazy.graphql",
26
+ "execute_field" => "execute_field.graphql",
27
+ "execute_field_lazy" => "execute_field_lazy.graphql",
28
+ "authorized" => "authorized.graphql",
29
+ "authorized_lazy" => "authorized_lazy.graphql",
30
+ "resolve_type" => "resolve_type.graphql",
31
+ "resolve_type_lazy" => "resolve_type.graphql",
32
+ }.each do |trace_method, platform_key|
33
+ module_eval <<-RUBY, __FILE__, __LINE__
34
+ def #{trace_method}(**metadata, &blk)
35
+ @notifications_engine.instrument("#{platform_key}", metadata, &blk)
36
+ end
37
+ RUBY
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ module PlatformTrace
6
+ def initialize(trace_scalars: false, **_options)
7
+ @trace_scalars = trace_scalars
8
+ @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }
9
+ @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }
10
+ @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }
11
+ super
12
+ end
13
+
14
+ def platform_execute_field_lazy(*args, &block)
15
+ platform_execute_field(*args, &block)
16
+ end
17
+
18
+ def platform_authorized_lazy(key, &block)
19
+ platform_authorized(key, &block)
20
+ end
21
+
22
+ def platform_resolve_type_lazy(key, &block)
23
+ platform_resolve_type(key, &block)
24
+ end
25
+
26
+ def self.included(child_class)
27
+ # Don't gather this unless necessary
28
+ pass_data_to_execute_field = child_class.method_defined?(:platform_execute_field) &&
29
+ child_class.instance_method(:platform_execute_field).arity != 1
30
+
31
+ [:execute_field, :execute_field_lazy].each do |field_trace_method|
32
+ child_class.module_eval <<-RUBY, __FILE__, __LINE__
33
+ def #{field_trace_method}(query:, field:, ast_node:, arguments:, object:)
34
+ return_type = field.type.unwrap
35
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
36
+ (field.trace.nil? && @trace_scalars) || field.trace
37
+ else
38
+ true
39
+ end
40
+ platform_key = if trace_field
41
+ @platform_field_key_cache[field]
42
+ else
43
+ nil
44
+ end
45
+ if platform_key && trace_field
46
+ platform_#{field_trace_method}(platform_key#{pass_data_to_execute_field ? ", { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }" : ""}) do
47
+ super
48
+ end
49
+ else
50
+ super
51
+ end
52
+ end
53
+ RUBY
54
+ end
55
+
56
+
57
+ [:authorized, :authorized_lazy].each do |auth_trace_method|
58
+ if !child_class.method_defined?(auth_trace_method)
59
+ child_class.module_eval <<-RUBY, __FILE__, __LINE__
60
+ def #{auth_trace_method}(type:, query:, object:)
61
+ platform_key = @platform_authorized_key_cache[type]
62
+ platform_#{auth_trace_method}(platform_key) do
63
+ super
64
+ end
65
+ end
66
+ RUBY
67
+ end
68
+ end
69
+
70
+ [:resolve_type, :resolve_type_lazy].each do |rt_trace_method|
71
+ if !child_class.method_defined?(rt_trace_method)
72
+ child_class.module_eval <<-RUBY, __FILE__, __LINE__
73
+ def #{rt_trace_method}(query:, type:, object:)
74
+ platform_key = @platform_resolve_type_key_cache[type]
75
+ platform_#{rt_trace_method}(platform_key) do
76
+ super
77
+ end
78
+ end
79
+ RUBY
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+
86
+ private
87
+
88
+ # Get the transaction name based on the operation type and name if possible, or fall back to a user provided
89
+ # one. Useful for anonymous queries.
90
+ def transaction_name(query)
91
+ selected_op = query.selected_operation
92
+ txn_name = if selected_op
93
+ op_type = selected_op.operation_type
94
+ op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
95
+ "#{op_type}.#{op_name}"
96
+ else
97
+ "query.anonymous"
98
+ end
99
+ "GraphQL/#{txn_name}"
100
+ end
101
+
102
+ def fallback_transaction_name(context)
103
+ context[:tracing_fallback_transaction_name]
104
+ end
105
+ end
106
+ end
107
+ end