graphql 2.0.17 → 2.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (43) 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/graphql_ext.bundle +0 -0
  10. data/lib/graphql/language/lexer.rb +216 -1505
  11. data/lib/graphql/language/lexer.ri +744 -0
  12. data/lib/graphql/language/parser.rb +9 -9
  13. data/lib/graphql/language/parser.y +9 -9
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  15. data/lib/graphql/query/context.rb +45 -11
  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 +23 -27
  23. data/lib/graphql/schema/warden.rb +8 -1
  24. data/lib/graphql/schema.rb +34 -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/version.rb +1 -1
  41. data/lib/graphql.rb +7 -8
  42. metadata +14 -3
  43. data/lib/graphql/language/lexer.rl +0 -280
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql/tracing/notifications_trace'
4
+
5
+ module GraphQL
6
+ module Tracing
7
+ # This implementation forwards events to ActiveSupport::Notifications
8
+ # with a `graphql` suffix.
9
+ module ActiveSupportNotificationsTrace
10
+ include NotificationsTrace
11
+ def initialize(engine: ActiveSupport::Notifications, **rest)
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
@@ -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