graphql 2.4.3 → 2.5.2

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.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +38 -41
  4. data/lib/graphql/analysis.rb +15 -12
  5. data/lib/graphql/autoload.rb +38 -0
  6. data/lib/graphql/backtrace/table.rb +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +6 -1
  9. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  10. data/lib/graphql/dashboard/installable.rb +22 -0
  11. data/lib/graphql/dashboard/limiters.rb +93 -0
  12. data/lib/graphql/dashboard/operation_store.rb +199 -0
  13. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  15. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  16. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  17. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  18. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  19. data/lib/graphql/dashboard/statics/icon.png +0 -0
  20. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  36. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  37. data/lib/graphql/dashboard.rb +158 -0
  38. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  39. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  40. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +3 -3
  43. data/lib/graphql/dataloader.rb +43 -14
  44. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  45. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  46. data/lib/graphql/execution/interpreter/runtime.rb +94 -51
  47. data/lib/graphql/execution/interpreter.rb +16 -7
  48. data/lib/graphql/execution/multiplex.rb +1 -5
  49. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  50. data/lib/graphql/invalid_name_error.rb +1 -1
  51. data/lib/graphql/invalid_null_error.rb +5 -15
  52. data/lib/graphql/language/cache.rb +13 -0
  53. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  54. data/lib/graphql/language/lexer.rb +11 -4
  55. data/lib/graphql/language/nodes.rb +3 -0
  56. data/lib/graphql/language/parser.rb +15 -8
  57. data/lib/graphql/language/printer.rb +8 -8
  58. data/lib/graphql/language/static_visitor.rb +37 -33
  59. data/lib/graphql/language/visitor.rb +59 -55
  60. data/lib/graphql/pagination/connection.rb +1 -1
  61. data/lib/graphql/query/context/scoped_context.rb +1 -1
  62. data/lib/graphql/query/context.rb +6 -5
  63. data/lib/graphql/query/variable_validation_error.rb +1 -1
  64. data/lib/graphql/query.rb +19 -23
  65. data/lib/graphql/railtie.rb +7 -0
  66. data/lib/graphql/schema/addition.rb +1 -1
  67. data/lib/graphql/schema/argument.rb +7 -8
  68. data/lib/graphql/schema/build_from_definition.rb +99 -53
  69. data/lib/graphql/schema/directive/flagged.rb +3 -1
  70. data/lib/graphql/schema/directive.rb +2 -2
  71. data/lib/graphql/schema/enum.rb +36 -1
  72. data/lib/graphql/schema/enum_value.rb +1 -1
  73. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  74. data/lib/graphql/schema/field.rb +27 -13
  75. data/lib/graphql/schema/field_extension.rb +1 -1
  76. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  77. data/lib/graphql/schema/input_object.rb +77 -40
  78. data/lib/graphql/schema/interface.rb +3 -2
  79. data/lib/graphql/schema/loader.rb +1 -1
  80. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  81. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  82. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  83. data/lib/graphql/schema/member/has_directives.rb +4 -4
  84. data/lib/graphql/schema/member/has_fields.rb +19 -1
  85. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  86. data/lib/graphql/schema/member/has_validators.rb +1 -1
  87. data/lib/graphql/schema/member/scoped.rb +1 -1
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/member.rb +1 -0
  90. data/lib/graphql/schema/object.rb +25 -8
  91. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  92. data/lib/graphql/schema/resolver.rb +12 -10
  93. data/lib/graphql/schema/subscription.rb +52 -6
  94. data/lib/graphql/schema/union.rb +1 -1
  95. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  96. data/lib/graphql/schema/validator.rb +1 -1
  97. data/lib/graphql/schema/visibility/migration.rb +1 -0
  98. data/lib/graphql/schema/visibility/profile.rb +95 -243
  99. data/lib/graphql/schema/visibility/visit.rb +190 -0
  100. data/lib/graphql/schema/visibility.rb +169 -28
  101. data/lib/graphql/schema/warden.rb +18 -5
  102. data/lib/graphql/schema.rb +93 -44
  103. data/lib/graphql/static_validation/all_rules.rb +1 -1
  104. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  105. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  106. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  107. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  108. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  109. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  110. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  111. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  112. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  113. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  114. data/lib/graphql/static_validation/validation_context.rb +1 -0
  115. data/lib/graphql/static_validation/validator.rb +6 -1
  116. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  117. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  118. data/lib/graphql/subscriptions/event.rb +12 -1
  119. data/lib/graphql/subscriptions/serialize.rb +1 -1
  120. data/lib/graphql/subscriptions.rb +1 -1
  121. data/lib/graphql/testing/helpers.rb +7 -4
  122. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  123. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  124. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  125. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  126. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  127. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  128. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  129. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  130. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  131. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  132. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  133. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  134. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  135. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  136. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  137. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  138. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  139. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  140. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  141. data/lib/graphql/tracing/null_trace.rb +9 -0
  142. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  143. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  144. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  145. data/lib/graphql/tracing/platform_trace.rb +5 -0
  146. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  147. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  148. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  149. data/lib/graphql/tracing/scout_trace.rb +32 -55
  150. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  151. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  152. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  153. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  154. data/lib/graphql/tracing/trace.rb +111 -1
  155. data/lib/graphql/tracing.rb +31 -30
  156. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  157. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  158. data/lib/graphql/types.rb +18 -11
  159. data/lib/graphql/version.rb +1 -1
  160. data/lib/graphql.rb +55 -47
  161. metadata +146 -11
  162. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  163. data/lib/graphql/backtrace/trace.rb +0 -93
  164. data/lib/graphql/backtrace/tracer.rb +0 -80
  165. data/lib/graphql/schema/null_mask.rb +0 -11
  166. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -0,0 +1,283 @@
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
+ def platform_field_key(field)
94
+ "#{field.path}.graphql"
95
+ end
96
+
97
+ def platform_authorized_key(type)
98
+ "#{type.graphql_name}.authorized.graphql"
99
+ end
100
+
101
+ def platform_resolve_type_key(type)
102
+ "#{type.graphql_name}.resolve_type.graphql"
103
+ end
104
+
105
+ def platform_source_class_key(source_class)
106
+ "#{source_class.name.gsub("::", "_")}.fetch.graphql"
107
+ end
108
+ end
109
+
110
+ module GraphQLPrefixNames
111
+ PARSE_NAME = "graphql.parse"
112
+ LEX_NAME = "graphql.lex"
113
+ VALIDATE_NAME = "graphql.validate"
114
+ EXECUTE_NAME = "graphql.execute"
115
+ ANALYZE_NAME = "graphql.analyze"
116
+
117
+ def platform_field_key(field)
118
+ "graphql.#{field.path}"
119
+ end
120
+
121
+ def platform_authorized_key(type)
122
+ "graphql.authorized.#{type.graphql_name}"
123
+ end
124
+
125
+ def platform_resolve_type_key(type)
126
+ "graphql.resolve_type.#{type.graphql_name}"
127
+ end
128
+
129
+ def platform_source_class_key(source_class)
130
+ "graphql.fetch.#{source_class.name.gsub("::", "_")}"
131
+ end
132
+ end
133
+ end
134
+
135
+ def self.create_module(monitor_name)
136
+ if !monitor_name.match?(/[a-z]+/)
137
+ raise ArgumentError, "monitor name must be [a-z]+, not: #{monitor_name.inspect}"
138
+ end
139
+
140
+ trace_module = Module.new
141
+ code = MODULE_TEMPLATE % {
142
+ monitor: monitor_name,
143
+ monitor_class: monitor_name.capitalize + "Monitor",
144
+ }
145
+ trace_module.module_eval(code, __FILE__, __LINE__ + 5) # rubocop:disable Development/NoEvalCop This is build-time with a validated string
146
+ trace_module
147
+ end
148
+
149
+ MODULE_TEMPLATE = <<~RUBY
150
+ # @param set_transaction_name [Boolean] If `true`, use the GraphQL operation name as the request name on the monitoring platform
151
+ # @param trace_scalars [Boolean] If `true`, leaf fields will be traced too (Scalars _and_ Enums)
152
+ # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
153
+ # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
154
+ def initialize(...)
155
+ setup_%{monitor}_monitor(...)
156
+ super
157
+ end
158
+
159
+ def setup_%{monitor}_monitor(trace_scalars: false, trace_authorized: true, trace_resolve_type: true, set_transaction_name: false, **kwargs)
160
+ @trace_scalars = trace_scalars
161
+ @trace_authorized = trace_authorized
162
+ @trace_resolve_type = trace_resolve_type
163
+ @set_transaction_name = set_transaction_name
164
+ @%{monitor} = %{monitor_class}.new(trace: self, set_transaction_name: @set_transaction_name, **kwargs)
165
+ end
166
+
167
+ def parse(query_string:)
168
+ @%{monitor}.instrument(:parse, query_string) do
169
+ super
170
+ end
171
+ end
172
+
173
+ def lex(query_string:)
174
+ @%{monitor}.instrument(:lex, query_string) do
175
+ super
176
+ end
177
+ end
178
+
179
+ def validate(query:, validate:)
180
+ @%{monitor}.instrument(:validate, query) do
181
+ super
182
+ end
183
+ end
184
+
185
+ def begin_analyze_multiplex(multiplex, analyzers)
186
+ begin_%{monitor}_event(:analyze, nil)
187
+ super
188
+ end
189
+
190
+ def end_analyze_multiplex(multiplex, analyzers)
191
+ finish_%{monitor}_event
192
+ super
193
+ end
194
+
195
+ def execute_multiplex(multiplex:)
196
+ @%{monitor}.instrument(:execute, multiplex) do
197
+ super
198
+ end
199
+ end
200
+
201
+ def begin_execute_field(field, object, arguments, query)
202
+ return_type = field.type.unwrap
203
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
204
+ (field.trace.nil? && @trace_scalars) || field.trace
205
+ else
206
+ true
207
+ end
208
+
209
+ if trace_field
210
+ begin_%{monitor}_event(:execute_field, field)
211
+ end
212
+ super
213
+ end
214
+
215
+ def end_execute_field(field, object, arguments, query, result)
216
+ finish_%{monitor}_event
217
+ super
218
+ end
219
+
220
+ def dataloader_fiber_yield(source)
221
+ Fiber[PREVIOUS_EV_KEY] = finish_%{monitor}_event
222
+ super
223
+ end
224
+
225
+ def dataloader_fiber_resume(source)
226
+ prev_ev = Fiber[PREVIOUS_EV_KEY]
227
+ if prev_ev
228
+ begin_%{monitor}_event(prev_ev.keyword, prev_ev.object)
229
+ end
230
+ super
231
+ end
232
+
233
+ def begin_authorized(type, object, context)
234
+ @trace_authorized && begin_%{monitor}_event(:authorized, type)
235
+ super
236
+ end
237
+
238
+ def end_authorized(type, object, context, result)
239
+ finish_%{monitor}_event
240
+ super
241
+ end
242
+
243
+ def begin_resolve_type(type, value, context)
244
+ @trace_resolve_type && begin_%{monitor}_event(:resolve_type, type)
245
+ super
246
+ end
247
+
248
+ def end_resolve_type(type, value, context, resolved_type)
249
+ finish_%{monitor}_event
250
+ super
251
+ end
252
+
253
+ def begin_dataloader_source(source)
254
+ begin_%{monitor}_event(:dataloader_source, source)
255
+ super
256
+ end
257
+
258
+ def end_dataloader_source(source)
259
+ finish_%{monitor}_event
260
+ super
261
+ end
262
+
263
+ CURRENT_EV_KEY = :__graphql_%{monitor}_trace_event
264
+ PREVIOUS_EV_KEY = :__graphql_%{monitor}_trace_previous_event
265
+
266
+ private
267
+
268
+ def begin_%{monitor}_event(keyword, object)
269
+ Fiber[CURRENT_EV_KEY] = @%{monitor}.start_event(keyword, object)
270
+ end
271
+
272
+ def finish_%{monitor}_event
273
+ if ev = Fiber[CURRENT_EV_KEY]
274
+ ev.finish
275
+ # Use `false` to prevent grabbing an event from a parent fiber
276
+ Fiber[CURRENT_EV_KEY] = false
277
+ ev
278
+ end
279
+ end
280
+ RUBY
281
+ end
282
+ end
283
+ end
@@ -1,74 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/monitor_trace"
4
+
3
5
  module GraphQL
4
6
  module Tracing
7
+ # A tracer for reporting GraphQL-Ruby time to New Relic
8
+ #
9
+ # @example Installing the tracer
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with GraphQL::Tracing::NewRelicTrace
12
+ #
13
+ # # Optional, use the operation name to set the new relic transaction name:
14
+ # # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true
15
+ # end
16
+ #
17
+ # @example Installing without trace events for `authorized?` or `resolve_type` calls
18
+ # trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false
19
+ NewRelicTrace = MonitorTrace.create_module("newrelic")
5
20
  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
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"
15
27
 
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
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))
39
34
  end
40
35
  end
41
- RUBY
42
- end
36
+ ::NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(name_for(keyword, payload), &block)
37
+ end
43
38
 
44
- def platform_execute_field(platform_key)
45
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
46
- yield
39
+ def platform_source_class_key(source_class)
40
+ "GraphQL/Source/#{source_class.name}"
47
41
  end
48
- end
49
42
 
50
- def platform_authorized(platform_key)
51
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
52
- yield
43
+ def platform_field_key(field)
44
+ "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
53
45
  end
54
- end
55
46
 
56
- def platform_resolve_type(platform_key)
57
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
58
- yield
47
+ def platform_authorized_key(type)
48
+ "GraphQL/Authorized/#{type.graphql_name}"
59
49
  end
60
- end
61
50
 
62
- def platform_field_key(field)
63
- "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
64
- end
51
+ def platform_resolve_type_key(type)
52
+ "GraphQL/ResolveType/#{type.graphql_name}"
53
+ end
65
54
 
66
- def platform_authorized_key(type)
67
- "GraphQL/Authorize/#{type.graphql_name}"
68
- 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
69
60
 
70
- def platform_resolve_type_key(type)
71
- "GraphQL/ResolveType/#{type.graphql_name}"
61
+ def finish
62
+ @nr_ev.finish
63
+ end
64
+ end
72
65
  end
73
66
  end
74
67
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/platform_tracing"
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
  class NewRelicTracing < PlatformTracing
@@ -1,45 +1,193 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "graphql/tracing/platform_trace"
4
-
5
3
  module GraphQL
6
4
  module Tracing
7
- # This implementation forwards events to a notification handler (i.e.
8
- # ActiveSupport::Notifications or Dry::Monitor::Notifications)
9
- # with a `graphql` suffix.
5
+ # This implementation forwards events to a notification handler
6
+ # (i.e. ActiveSupport::Notifications or Dry::Monitor::Notifications) with a `graphql` suffix.
7
+ #
8
+ # @see ActiveSupportNotificationsTrace ActiveSupport::Notifications integration
10
9
  module NotificationsTrace
11
- # Initialize a new NotificationsTracing instance
12
- #
13
- # @param engine [#instrument(key, metadata, block)] The notifications engine to use
14
- def initialize(engine:, **rest)
15
- @notifications_engine = engine
16
- super
17
- end
18
-
19
- {
20
- "lex" => "lex.graphql",
21
- "parse" => "parse.graphql",
22
- "validate" => "validate.graphql",
23
- "analyze_multiplex" => "analyze_multiplex.graphql",
24
- "analyze_query" => "analyze_query.graphql",
25
- "execute_multiplex" => "execute_multiplex.graphql",
26
- "execute_query" => "execute_query.graphql",
27
- "execute_query_lazy" => "execute_query_lazy.graphql",
28
- "execute_field" => "execute_field.graphql",
29
- "execute_field_lazy" => "execute_field_lazy.graphql",
30
- "authorized" => "authorized.graphql",
31
- "authorized_lazy" => "authorized_lazy.graphql",
32
- "resolve_type" => "resolve_type.graphql",
33
- "resolve_type_lazy" => "resolve_type.graphql",
34
- }.each do |trace_method, platform_key|
35
- module_eval <<-RUBY, __FILE__, __LINE__
36
- def #{trace_method}(**metadata, &block)
37
- @notifications_engine.instrument("#{platform_key}", metadata) { super(**metadata, &block) }
10
+ # @api private
11
+ class Adapter
12
+ def instrument(keyword, payload, &block)
13
+ raise "Implement #{self.class}#instrument to measure the block"
14
+ end
15
+
16
+ def start_event(keyword, payload)
17
+ ev = self.class::Event.new(keyword, payload)
18
+ ev.start
19
+ ev
20
+ end
21
+
22
+ class Event
23
+ def initialize(name, payload)
24
+ @name = name
25
+ @payload = payload
26
+ end
27
+
28
+ attr_reader :name, :payload
29
+
30
+ def start
31
+ raise "Implement #{self.class}#start to begin a new event (#{inspect})"
32
+ end
33
+
34
+ def finish
35
+ raise "Implement #{self.class}#finish to end this event (#{inspect})"
36
+ end
37
+ end
38
+ end
39
+
40
+ # @api private
41
+ class DryMonitorAdapter < Adapter
42
+ def instrument(...)
43
+ Dry::Monitor.instrument(...)
44
+ end
45
+
46
+ class Event < Adapter::Event
47
+ def start
48
+ Dry::Monitor.start(@name, @payload)
49
+ end
50
+
51
+ def finish
52
+ Dry::Monitor.stop(@name, @payload)
53
+ end
54
+ end
55
+ end
56
+
57
+ # @api private
58
+ class ActiveSupportNotificationsAdapter < Adapter
59
+ def instrument(...)
60
+ ActiveSupport::Notifications.instrument(...)
61
+ end
62
+
63
+ class Event < Adapter::Event
64
+ def start
65
+ @asn_event = ActiveSupport::Notifications.instrumenter.new_event(@name, @payload)
66
+ @asn_event.start!
38
67
  end
39
- RUBY
68
+
69
+ def finish
70
+ @asn_event.finish!
71
+ ActiveSupport::Notifications.publish_event(@asn_event)
72
+ end
73
+ end
74
+ end
75
+
76
+ # @param engine [Class] The notifications engine to use, eg `Dry::Monitor` or `ActiveSupport::Notifications`
77
+ def initialize(engine:, **rest)
78
+ adapter = if defined?(Dry::Monitor) && engine == Dry::Monitor
79
+ DryMonitoringAdapter
80
+ elsif defined?(ActiveSupport::Notifications) && engine == ActiveSupport::Notifications
81
+ ActiveSupportNotificationsAdapter
82
+ else
83
+ engine
84
+ end
85
+ @notifications = adapter.new
86
+ super
87
+ end
88
+
89
+ def parse(**payload)
90
+ @notifications.instrument("parse.graphql", payload) do
91
+ super
92
+ end
93
+ end
94
+
95
+ def lex(**payload)
96
+ @notifications.instrument("lex.graphql", payload) do
97
+ super
98
+ end
99
+ end
100
+
101
+ def validate(**payload)
102
+ @notifications.instrument("validate.graphql", payload) do
103
+ super
104
+ end
105
+ end
106
+
107
+ def begin_analyze_multiplex(multiplex, analyzers)
108
+ begin_notifications_event("analyze.graphql", {multiplex: multiplex, analyzers: analyzers})
109
+ super
110
+ end
111
+
112
+ def end_analyze_multiplex(_multiplex, _analyzers)
113
+ finish_notifications_event
114
+ super
40
115
  end
41
116
 
42
- include PlatformTrace
117
+ def execute_multiplex(**payload)
118
+ @notifications.instrument("execute.graphql", payload) do
119
+ super
120
+ end
121
+ end
122
+
123
+ def begin_execute_field(field, object, arguments, query)
124
+ begin_notifications_event("execute_field.graphql", {field: field, object: object, arguments: arguments, query: query})
125
+ super
126
+ end
127
+
128
+ def end_execute_field(_field, _object, _arguments, _query, _result)
129
+ finish_notifications_event
130
+ super
131
+ end
132
+
133
+ def dataloader_fiber_yield(source)
134
+ Fiber[PREVIOUS_EV_KEY] = finish_notifications_event
135
+ super
136
+ end
137
+
138
+ def dataloader_fiber_resume(source)
139
+ prev_ev = Fiber[PREVIOUS_EV_KEY]
140
+ begin_notifications_event(prev_ev.name, prev_ev.payload)
141
+ super
142
+ end
143
+
144
+ def begin_authorized(type, object, context)
145
+ begin_notifications_event("authorized.graphql", {type: type, object: object, context: context})
146
+ super
147
+ end
148
+
149
+ def end_authorized(type, object, context, result)
150
+ finish_notifications_event
151
+ super
152
+ end
153
+
154
+ def begin_resolve_type(type, object, context)
155
+ begin_notifications_event("resolve_type.graphql", {type: type, object: object, context: context})
156
+ super
157
+ end
158
+
159
+ def end_resolve_type(type, object, context, resolved_type)
160
+ finish_notifications_event
161
+ super
162
+ end
163
+
164
+ def begin_dataloader_source(source)
165
+ begin_notifications_event("dataloader_source.graphql", { source: source })
166
+ super
167
+ end
168
+
169
+ def end_dataloader_source(source)
170
+ finish_notifications_event
171
+ super
172
+ end
173
+
174
+ CURRENT_EV_KEY = :__notifications_graphql_trace_event
175
+ PREVIOUS_EV_KEY = :__notifications_graphql_trace_previous_event
176
+
177
+ private
178
+
179
+ def begin_notifications_event(name, payload)
180
+ Fiber[CURRENT_EV_KEY] = @notifications.start_event(name, payload)
181
+ end
182
+
183
+ def finish_notifications_event
184
+ if ev = Fiber[CURRENT_EV_KEY]
185
+ ev.finish
186
+ # Use `false` to prevent grabbing an event from a parent fiber
187
+ Fiber[CURRENT_EV_KEY] = false
188
+ ev
189
+ end
190
+ end
43
191
  end
44
192
  end
45
193
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/platform_tracing"
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
  # This implementation forwards events to a notification handler (i.e.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphql/tracing/trace"
4
+
5
+ module GraphQL
6
+ module Tracing
7
+ NullTrace = Trace.new
8
+ end
9
+ end