graphql 2.4.16 → 2.5.0

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.
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ # This module is the basis for Ruby-level integration with third-party monitoring platforms.
6
+ # Platform-specific traces include this module and implement an adapter.
7
+ #
8
+ # @see ActiveSupportNotificationsTrace Integration via ActiveSupport::Notifications, an alternative approach.
9
+ module MonitorTrace
10
+ class Monitor
11
+ def initialize(trace:, set_transaction_name:, **_rest)
12
+ @trace = trace
13
+ @set_transaction_name = set_transaction_name
14
+ @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }.compare_by_identity
15
+ @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }.compare_by_identity
16
+ @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }.compare_by_identity
17
+ @platform_source_class_key_cache = Hash.new { |h, source_cls| h[source_cls] = platform_source_class_key(source_cls) }.compare_by_identity
18
+ end
19
+
20
+ def instrument(keyword, object, &block)
21
+ raise "Implement #{self.class}#instrument to measure the block"
22
+ end
23
+
24
+ def start_event(keyword, object)
25
+ ev = self.class::Event.new(self, keyword, object)
26
+ ev.start
27
+ ev
28
+ end
29
+
30
+ # Get the transaction name based on the operation type and name if possible, or fall back to a user provided
31
+ # one. Useful for anonymous queries.
32
+ def transaction_name(query)
33
+ selected_op = query.selected_operation
34
+ txn_name = if selected_op
35
+ op_type = selected_op.operation_type
36
+ op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
37
+ "#{op_type}.#{op_name}"
38
+ else
39
+ "query.anonymous"
40
+ end
41
+ "GraphQL/#{txn_name}"
42
+ end
43
+
44
+ def fallback_transaction_name(context)
45
+ context[:tracing_fallback_transaction_name]
46
+ end
47
+
48
+ def name_for(keyword, object)
49
+ case keyword
50
+ when :execute_field
51
+ @platform_field_key_cache[object]
52
+ when :authorized
53
+ @platform_authorized_key_cache[object]
54
+ when :resolve_type
55
+ @platform_resolve_type_key_cache[object]
56
+ when :dataloader_source
57
+ @platform_source_class_key_cache[object.class]
58
+ when :parse then self.class::PARSE_NAME
59
+ when :lex then self.class::LEX_NAME
60
+ when :execute then self.class::EXECUTE_NAME
61
+ when :analyze then self.class::ANALYZE_NAME
62
+ when :validate then self.class::VALIDATE_NAME
63
+ else
64
+ raise "No name for #{keyword.inspect}"
65
+ end
66
+ end
67
+
68
+ class Event
69
+ def initialize(monitor, keyword, object)
70
+ @monitor = monitor
71
+ @keyword = keyword
72
+ @object = object
73
+ end
74
+
75
+ attr_reader :keyword, :object
76
+
77
+ def start
78
+ raise "Implement #{self.class}#start to begin a new event (#{inspect})"
79
+ end
80
+
81
+ def finish
82
+ raise "Implement #{self.class}#finish to end this event (#{inspect})"
83
+ end
84
+ end
85
+
86
+ module GraphQLSuffixNames
87
+ PARSE_NAME = "parse.graphql"
88
+ LEX_NAME = "lex.graphql"
89
+ VALIDATE_NAME = "validate.graphql"
90
+ EXECUTE_NAME = "execute.graphql"
91
+ ANALYZE_NAME = "analyze.graphql"
92
+
93
+ private
94
+
95
+ def platform_field_key(field)
96
+ "#{field.path}.graphql"
97
+ end
98
+
99
+ def platform_authorized_key(type)
100
+ "#{type.graphql_name}.authorized.graphql"
101
+ end
102
+
103
+ def platform_resolve_type_key(type)
104
+ "#{type.graphql_name}.resolve_type.graphql"
105
+ end
106
+
107
+ def platform_source_key(source_class)
108
+ "#{source_class.name.gsub("::", "_").name.underscore}.fetch.graphql"
109
+ end
110
+ end
111
+
112
+ module GraphQLPrefixNames
113
+ PARSE_NAME = "graphql.parse"
114
+ LEX_NAME = "graphql.lex"
115
+ VALIDATE_NAME = "graphql.validate"
116
+ EXECUTE_NAME = "graphql.execute"
117
+ ANALYZE_NAME = "graphql.analyze"
118
+
119
+ def platform_field_key(field)
120
+ "graphql.#{field.path}"
121
+ end
122
+
123
+ def platform_authorized_key(type)
124
+ "graphql.authorized.#{type.graphql_name}"
125
+ end
126
+
127
+ def platform_resolve_type_key(type)
128
+ "graphql.resolve_type.#{type.graphql_name}"
129
+ end
130
+
131
+ def platform_source_class_key(source_class)
132
+ "graphql.fetch.#{source_class.name.gsub("::", "_")}"
133
+ end
134
+ end
135
+ end
136
+
137
+ def self.create_module(monitor_name)
138
+ if !monitor_name.match?(/[a-z]+/)
139
+ raise ArgumentError, "monitor name must be [a-z]+, not: #{monitor_name.inspect}"
140
+ end
141
+
142
+ trace_module = Module.new
143
+ code = MODULE_TEMPLATE % {
144
+ monitor: monitor_name,
145
+ monitor_class: monitor_name.capitalize + "Monitor",
146
+ }
147
+ trace_module.module_eval(code, __FILE__, __LINE__ + 5) # rubocop:disable Development/NoEvalCop This is build-time with a validated string
148
+ trace_module
149
+ end
150
+
151
+ MODULE_TEMPLATE = <<~RUBY
152
+ # @param set_transaction_name [Boolean] If `true`, use the GraphQL operation name as the request name on the monitoring platform
153
+ # @param trace_scalars [Boolean] If `true`, leaf fields will be traced too (Scalars _and_ Enums)
154
+ # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
155
+ # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
156
+ def initialize(...)
157
+ setup_%{monitor}_monitor(...)
158
+ super
159
+ end
160
+
161
+ def setup_%{monitor}_monitor(trace_scalars: false, trace_authorized: true, trace_resolve_type: true, set_transaction_name: false, **kwargs)
162
+ @trace_scalars = trace_scalars
163
+ @trace_authorized = trace_authorized
164
+ @trace_resolve_type = trace_resolve_type
165
+ @set_transaction_name = set_transaction_name
166
+ @%{monitor} = %{monitor_class}.new(trace: self, set_transaction_name: @set_transaction_name, **kwargs)
167
+ end
168
+
169
+ def parse(query_string:)
170
+ @%{monitor}.instrument(:parse, query_string) do
171
+ super
172
+ end
173
+ end
174
+
175
+ def lex(query_string:)
176
+ @%{monitor}.instrument(:lex, query_string) do
177
+ super
178
+ end
179
+ end
180
+
181
+ def validate(query:, validate:)
182
+ @%{monitor}.instrument(:validate, query) do
183
+ super
184
+ end
185
+ end
186
+
187
+ def begin_analyze_multiplex(multiplex, analyzers)
188
+ begin_%{monitor}_event(:analyze, nil)
189
+ super
190
+ end
191
+
192
+ def end_analyze_multiplex(multiplex, analyzers)
193
+ finish_%{monitor}_event
194
+ super
195
+ end
196
+
197
+ def execute_multiplex(multiplex:)
198
+ @%{monitor}.instrument(:execute, multiplex) do
199
+ super
200
+ end
201
+ end
202
+
203
+ def begin_execute_field(field, object, arguments, query)
204
+ return_type = field.type.unwrap
205
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
206
+ (field.trace.nil? && @trace_scalars) || field.trace
207
+ else
208
+ true
209
+ end
210
+
211
+ if trace_field
212
+ begin_%{monitor}_event(:execute_field, field)
213
+ end
214
+ super
215
+ end
216
+
217
+ def end_execute_field(field, object, arguments, query, result)
218
+ finish_%{monitor}_event
219
+ super
220
+ end
221
+
222
+ def dataloader_fiber_yield(source)
223
+ Fiber[PREVIOUS_EV_KEY] = finish_%{monitor}_event
224
+ super
225
+ end
226
+
227
+ def dataloader_fiber_resume(source)
228
+ prev_ev = Fiber[PREVIOUS_EV_KEY]
229
+ if prev_ev
230
+ begin_%{monitor}_event(prev_ev.keyword, prev_ev.object)
231
+ end
232
+ super
233
+ end
234
+
235
+ def begin_authorized(type, object, context)
236
+ @trace_authorized && begin_%{monitor}_event(:authorized, type)
237
+ super
238
+ end
239
+
240
+ def end_authorized(type, object, context, result)
241
+ finish_%{monitor}_event
242
+ super
243
+ end
244
+
245
+ def begin_resolve_type(type, value, context)
246
+ @trace_resolve_type && begin_%{monitor}_event(:resolve_type, type)
247
+ super
248
+ end
249
+
250
+ def end_resolve_type(type, value, context, resolved_type)
251
+ finish_%{monitor}_event
252
+ super
253
+ end
254
+
255
+ def begin_dataloader_source(source)
256
+ begin_%{monitor}_event(:dataloader_source, source)
257
+ super
258
+ end
259
+
260
+ def end_dataloader_source(source)
261
+ finish_%{monitor}_event
262
+ super
263
+ end
264
+
265
+ CURRENT_EV_KEY = :__graphql_%{monitor}_trace_event
266
+ PREVIOUS_EV_KEY = :__graphql_%{monitor}_trace_previous_event
267
+
268
+ private
269
+
270
+ def begin_%{monitor}_event(keyword, object)
271
+ Fiber[CURRENT_EV_KEY] = @%{monitor}.start_event(keyword, object)
272
+ end
273
+
274
+ def finish_%{monitor}_event
275
+ if ev = Fiber[CURRENT_EV_KEY]
276
+ ev.finish
277
+ # Use `false` to prevent grabbing an event from a parent fiber
278
+ Fiber[CURRENT_EV_KEY] = false
279
+ ev
280
+ end
281
+ end
282
+ RUBY
283
+ end
284
+ end
285
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "graphql/tracing/platform_trace"
3
+ require "graphql/tracing/monitor_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
@@ -16,184 +16,52 @@ module GraphQL
16
16
  #
17
17
  # @example Installing without trace events for `authorized?` or `resolve_type` calls
18
18
  # trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false
19
+ NewRelicTrace = MonitorTrace.create_module("newrelic")
19
20
  module NewRelicTrace
20
- # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
21
- # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
22
- # It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
23
- # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
24
- # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
25
- # @param trace_scalars [Boolean] If `true`, Enum and Scalar fields will be traced by default
26
- def initialize(set_transaction_name: false, trace_authorized: true, trace_resolve_type: true, trace_scalars: false, **_rest)
27
- @set_transaction_name = set_transaction_name
28
- @trace_authorized = trace_authorized
29
- @trace_resolve_type = trace_resolve_type
30
- @trace_scalars = trace_scalars
31
- @nr_field_names = Hash.new do |h, field|
32
- h[field] = "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
33
- end.compare_by_identity
34
-
35
- @nr_authorized_names = Hash.new do |h, type|
36
- h[type] = "GraphQL/Authorized/#{type.graphql_name}"
37
- end.compare_by_identity
38
-
39
- @nr_resolve_type_names = Hash.new do |h, type|
40
- h[type] = "GraphQL/ResolveType/#{type.graphql_name}"
41
- end.compare_by_identity
42
-
43
- @nr_source_names = Hash.new do |h, source_inst|
44
- h[source_inst] = "GraphQL/Source/#{source_inst.class.name}"
45
- end.compare_by_identity
46
-
47
- @nr_parse = @nr_validate = @nr_analyze = @nr_execute = nil
48
- super
49
- end
50
-
51
- def begin_parse(query_str)
52
- @nr_parse = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/parse", category: :web)
53
- super
54
- end
55
-
56
- def end_parse(query_str)
57
- @nr_parse.finish
58
- super
59
- end
60
-
61
- def begin_validate(query, validate)
62
- @nr_validate = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/validate", category: :web)
63
- super
64
- end
65
-
66
- def end_validate(query, validate, validation_errors)
67
- @nr_validate.finish
68
- super
69
- end
70
-
71
- def begin_analyze_multiplex(multiplex, analyzers)
72
- @nr_analyze = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/analyze", category: :web)
73
- super
74
- end
75
-
76
- def end_analyze_multiplex(multiplex, analyzers)
77
- @nr_analyze.finish
78
- super
79
- end
80
-
81
- def begin_execute_multiplex(multiplex)
82
- query = multiplex.queries.first
83
- set_this_txn_name = query.context[:set_new_relic_transaction_name]
84
- if set_this_txn_name || (set_this_txn_name.nil? && @set_transaction_name)
85
- NewRelic::Agent.set_transaction_name(transaction_name(query))
21
+ class NewrelicMonitor < MonitorTrace::Monitor
22
+ PARSE_NAME = "GraphQL/parse"
23
+ LEX_NAME = "GraphQL/lex"
24
+ VALIDATE_NAME = "GraphQL/validate"
25
+ EXECUTE_NAME = "GraphQL/execute"
26
+ ANALYZE_NAME = "GraphQL/analyze"
27
+
28
+ def instrument(keyword, payload, &block)
29
+ if keyword == :execute
30
+ query = payload.queries.first
31
+ set_this_txn_name = query.context[:set_new_relic_transaction_name]
32
+ if set_this_txn_name || (set_this_txn_name.nil? && @set_transaction_name)
33
+ NewRelic::Agent.set_transaction_name(transaction_name(query))
34
+ end
35
+ end
36
+ ::NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(name_for(keyword, payload), &block)
86
37
  end
87
- @nr_execute = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/execute", category: :web)
88
- super
89
- end
90
-
91
- def end_execute_multiplex(multiplex)
92
- @nr_execute.finish
93
- super
94
- end
95
38
 
96
- def begin_execute_field(field, object, arguments, query)
97
- return_type = field.type.unwrap
98
- trace_field = if return_type.kind.scalar? || return_type.kind.enum?
99
- (field.trace.nil? && @trace_scalars) || field.trace
100
- else
101
- true
39
+ def platform_source_class_key(source_class)
40
+ "GraphQL/Source/#{source_class.name}"
102
41
  end
103
- if trace_field
104
- start_segment(partial_name: @nr_field_names[field], category: :web)
105
- end
106
- super
107
- end
108
42
 
109
- def end_execute_field(field, objects, arguments, query, result)
110
- finish_segment
111
- super
112
- end
113
-
114
- def begin_authorized(type, obj, ctx)
115
- if @trace_authorized
116
- start_segment(partial_name: @nr_authorized_names[type], category: :web)
117
- end
118
- super
119
- end
120
-
121
- def end_authorized(type, obj, ctx, is_authed)
122
- if @trace_authorized
123
- finish_segment
43
+ def platform_field_key(field)
44
+ "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
124
45
  end
125
- super
126
- end
127
46
 
128
- def begin_resolve_type(type, value, context)
129
- if @trace_resolve_type
130
- start_segment(partial_name: @nr_resolve_type_names[type], category: :web)
47
+ def platform_authorized_key(type)
48
+ "GraphQL/Authorized/#{type.graphql_name}"
131
49
  end
132
- super
133
- end
134
50
 
135
- def end_resolve_type(type, value, context, resolved_type)
136
- if @trace_resolve_type
137
- finish_segment
51
+ def platform_resolve_type_key(type)
52
+ "GraphQL/ResolveType/#{type.graphql_name}"
138
53
  end
139
- super
140
- end
141
-
142
- def begin_dataloader_source(source)
143
- start_segment(partial_name: @nr_source_names[source], category: :web)
144
- super
145
- end
146
54
 
147
- def end_dataloader_source(source)
148
- finish_segment
149
- super
150
- end
151
-
152
- def dataloader_fiber_yield(source)
153
- prev_segment = finish_segment
154
- Fiber[:graphql_nr_previous_segment] = prev_segment
155
- super
156
- end
55
+ class Event < MonitorTrace::Monitor::Event
56
+ def start
57
+ name = @monitor.name_for(keyword, object)
58
+ @nr_ev = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: name, category: :web)
59
+ end
157
60
 
158
- def dataloader_fiber_resume(source)
159
- prev_segment = Fiber[:graphql_nr_previous_segment]
160
- Fiber[:graphql_nr_previous_segment] = nil
161
- if prev_segment
162
- seg_partial_name = prev_segment.name.sub(/^.*(GraphQL.*)$/, '\1')
163
- start_segment(partial_name: seg_partial_name, category: :web)
61
+ def finish
62
+ @nr_ev.finish
63
+ end
164
64
  end
165
- super
166
- end
167
-
168
- private
169
-
170
- def start_segment(...)
171
- Fiber[:graphql_nr_segment] = NewRelic::Agent::Tracer.start_transaction_or_segment(...)
172
- end
173
-
174
- def finish_segment
175
- segment = Fiber[:graphql_nr_segment]
176
- if segment
177
- segment.finish
178
- Fiber[:graphql_nr_segment] = nil
179
- segment
180
- end
181
- end
182
-
183
- def transaction_name(query)
184
- selected_op = query.selected_operation
185
- txn_name = if selected_op
186
- op_type = selected_op.operation_type
187
- op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
188
- "#{op_type}.#{op_name}"
189
- else
190
- "query.anonymous"
191
- end
192
- "GraphQL/#{txn_name}"
193
- end
194
-
195
- def fallback_transaction_name(context)
196
- context[:tracing_fallback_transaction_name]
197
65
  end
198
66
  end
199
67
  end