graphql 2.4.5 → 2.5.21

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis/analyzer.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +87 -7
  6. data/lib/graphql/analysis/visitor.rb +37 -40
  7. data/lib/graphql/analysis.rb +12 -9
  8. data/lib/graphql/autoload.rb +1 -0
  9. data/lib/graphql/backtrace/table.rb +118 -55
  10. data/lib/graphql/backtrace.rb +1 -19
  11. data/lib/graphql/current.rb +6 -1
  12. data/lib/graphql/dashboard/application_controller.rb +41 -0
  13. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  14. data/lib/graphql/dashboard/installable.rb +22 -0
  15. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  16. data/lib/graphql/dashboard/limiters.rb +93 -0
  17. data/lib/graphql/dashboard/operation_store.rb +199 -0
  18. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  19. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  20. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  21. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  22. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  23. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  24. data/lib/graphql/dashboard/statics/icon.png +0 -0
  25. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  26. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  42. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  43. data/lib/graphql/dashboard.rb +96 -0
  44. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  45. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  46. data/lib/graphql/dataloader/async_dataloader.rb +38 -15
  47. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  48. data/lib/graphql/dataloader/source.rb +18 -6
  49. data/lib/graphql/dataloader.rb +110 -26
  50. data/lib/graphql/date_encoding_error.rb +1 -1
  51. data/lib/graphql/dig.rb +2 -1
  52. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  53. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
  54. data/lib/graphql/execution/interpreter/runtime.rb +229 -93
  55. data/lib/graphql/execution/interpreter.rb +15 -24
  56. data/lib/graphql/execution/multiplex.rb +7 -6
  57. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  58. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  59. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  60. data/lib/graphql/execution/next/runner.rb +389 -0
  61. data/lib/graphql/execution/next/selections_step.rb +37 -0
  62. data/lib/graphql/execution/next.rb +69 -0
  63. data/lib/graphql/execution.rb +1 -0
  64. data/lib/graphql/execution_error.rb +13 -10
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +7 -3
  67. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  68. data/lib/graphql/introspection/entry_points.rb +11 -3
  69. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  70. data/lib/graphql/introspection/field_type.rb +13 -5
  71. data/lib/graphql/introspection/input_value_type.rb +21 -13
  72. data/lib/graphql/introspection/type_type.rb +64 -28
  73. data/lib/graphql/invalid_name_error.rb +1 -1
  74. data/lib/graphql/invalid_null_error.rb +25 -16
  75. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  76. data/lib/graphql/language/lexer.rb +16 -5
  77. data/lib/graphql/language/nodes.rb +8 -1
  78. data/lib/graphql/language/parser.rb +16 -8
  79. data/lib/graphql/language/static_visitor.rb +37 -33
  80. data/lib/graphql/language/visitor.rb +59 -55
  81. data/lib/graphql/language.rb +21 -12
  82. data/lib/graphql/pagination/connection.rb +2 -0
  83. data/lib/graphql/pagination/connections.rb +32 -0
  84. data/lib/graphql/query/context.rb +6 -10
  85. data/lib/graphql/query/null_context.rb +9 -3
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query.rb +64 -64
  88. data/lib/graphql/railtie.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +3 -1
  90. data/lib/graphql/schema/always_visible.rb +1 -0
  91. data/lib/graphql/schema/argument.rb +24 -8
  92. data/lib/graphql/schema/build_from_definition.rb +113 -54
  93. data/lib/graphql/schema/directive/flagged.rb +2 -0
  94. data/lib/graphql/schema/directive.rb +52 -2
  95. data/lib/graphql/schema/enum.rb +36 -1
  96. data/lib/graphql/schema/enum_value.rb +1 -1
  97. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  98. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  99. data/lib/graphql/schema/field.rb +101 -51
  100. data/lib/graphql/schema/field_extension.rb +33 -0
  101. data/lib/graphql/schema/input_object.rb +45 -38
  102. data/lib/graphql/schema/interface.rb +2 -1
  103. data/lib/graphql/schema/list.rb +1 -1
  104. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  105. data/lib/graphql/schema/member/has_arguments.rb +56 -19
  106. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  107. data/lib/graphql/schema/member/has_dataloader.rb +79 -0
  108. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  109. data/lib/graphql/schema/member/has_directives.rb +1 -1
  110. data/lib/graphql/schema/member/has_fields.rb +81 -5
  111. data/lib/graphql/schema/member/has_interfaces.rb +3 -3
  112. data/lib/graphql/schema/member/scoped.rb +1 -1
  113. data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
  114. data/lib/graphql/schema/member.rb +6 -0
  115. data/lib/graphql/schema/object.rb +18 -8
  116. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  117. data/lib/graphql/schema/resolver.rb +52 -6
  118. data/lib/graphql/schema/scalar.rb +1 -6
  119. data/lib/graphql/schema/subscription.rb +50 -4
  120. data/lib/graphql/schema/timeout.rb +19 -2
  121. data/lib/graphql/schema/validator/required_validator.rb +71 -14
  122. data/lib/graphql/schema/visibility/migration.rb +3 -2
  123. data/lib/graphql/schema/visibility/profile.rb +115 -23
  124. data/lib/graphql/schema/visibility.rb +49 -32
  125. data/lib/graphql/schema/warden.rb +23 -2
  126. data/lib/graphql/schema.rb +333 -68
  127. data/lib/graphql/static_validation/all_rules.rb +2 -2
  128. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  129. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  130. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  131. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  132. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  133. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  134. data/lib/graphql/static_validation/validator.rb +6 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  136. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  137. data/lib/graphql/subscriptions/event.rb +12 -1
  138. data/lib/graphql/subscriptions/serialize.rb +1 -1
  139. data/lib/graphql/subscriptions.rb +1 -1
  140. data/lib/graphql/testing/helpers.rb +17 -11
  141. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  142. data/lib/graphql/testing.rb +1 -0
  143. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  144. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  145. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  146. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  147. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  148. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  149. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  150. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  151. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  152. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  153. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  154. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  155. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  156. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  157. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  158. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  159. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  160. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  161. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  162. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  163. data/lib/graphql/tracing/null_trace.rb +9 -0
  164. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  165. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  166. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  167. data/lib/graphql/tracing/platform_trace.rb +5 -0
  168. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  169. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  170. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  171. data/lib/graphql/tracing/scout_trace.rb +32 -55
  172. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  173. data/lib/graphql/tracing/sentry_trace.rb +64 -94
  174. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  175. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  176. data/lib/graphql/tracing/trace.rb +111 -1
  177. data/lib/graphql/tracing.rb +31 -30
  178. data/lib/graphql/type_kinds.rb +1 -0
  179. data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
  180. data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
  181. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  182. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  183. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  184. data/lib/graphql/unauthorized_error.rb +5 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +12 -31
  187. metadata +174 -11
  188. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  189. data/lib/graphql/backtrace/trace.rb +0 -93
  190. data/lib/graphql/backtrace/tracer.rb +0 -80
  191. data/lib/graphql/schema/null_mask.rb +0 -11
  192. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -1,11 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql/tracing/notifications_trace'
3
+ require "graphql/tracing/notifications_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
- # This implementation forwards events to ActiveSupport::Notifications
8
- # with a `graphql` suffix.
7
+ # This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix.
8
+ #
9
+ # @example Sending execution events to ActiveSupport::Notifications
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
12
+ # end
13
+ #
14
+ # @example Subscribing to GraphQL events with ActiveSupport::Notifications
15
+ # ActiveSupport::Notifications.subscribe(/graphql/) do |event|
16
+ # pp event.name
17
+ # pp event.payload
18
+ # end
19
+ #
9
20
  module ActiveSupportNotificationsTrace
10
21
  include NotificationsTrace
11
22
  def initialize(engine: ActiveSupport::Notifications, **rest)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql/tracing/notifications_tracing'
3
+ require "graphql/tracing/notifications_tracing"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/platform_trace"
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
 
@@ -20,14 +22,18 @@ module GraphQL
20
22
  # These GraphQL events will show up as 'graphql.execute' spans
21
23
  EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
22
24
 
25
+
23
26
  # During auto-instrumentation this version of AppOpticsTracing is compared
24
27
  # with the version provided in the appoptics_apm gem, so that the newer
25
28
  # version of the class can be used
26
29
 
30
+
27
31
  def self.version
28
32
  Gem::Version.new('1.0.0')
29
33
  end
30
34
 
35
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
36
+
31
37
  [
32
38
  'lex',
33
39
  'parse',
@@ -55,6 +61,8 @@ module GraphQL
55
61
  RUBY
56
62
  end
57
63
 
64
+ # rubocop:enable Development/NoEvalCop
65
+
58
66
  def execute_field(query:, field:, ast_node:, arguments:, object:)
59
67
  return_type = field.type.unwrap
60
68
  trace_field = if return_type.kind.scalar? || return_type.kind.enum?
@@ -81,7 +89,7 @@ module GraphQL
81
89
  end
82
90
  end
83
91
 
84
- def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
92
+ def execute_field_lazy(query:, field:, ast_node:, arguments:, object:) # rubocop:disable Development/TraceCallsSuperCop
85
93
  execute_field(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
86
94
  end
87
95
 
@@ -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
 
@@ -20,6 +22,11 @@ module GraphQL
20
22
  # These GraphQL events will show up as 'graphql.execute' spans
21
23
  EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
22
24
 
25
+ def initialize(...)
26
+ warn "GraphQL::Tracing::AppOptics tracing is deprecated; update to SolarWindsAPM instead, which uses OpenTelemetry."
27
+ super
28
+ end
29
+
23
30
  # During auto-instrumentation this version of AppOpticsTracing is compared
24
31
  # with the version provided in the appoptics_apm gem, so that the newer
25
32
  # version of the class can be used
@@ -1,77 +1,54 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/tracing/monitor_trace"
2
3
 
3
4
  module GraphQL
4
5
  module Tracing
6
+ # Instrumentation for reporting GraphQL-Ruby times to Appsignal.
7
+ #
8
+ # @example Installing the tracer
9
+ # class MySchema < GraphQL::Schema
10
+ # trace_with GraphQL::Tracing::AppsignalTrace
11
+ # end
12
+ AppsignalTrace = MonitorTrace.create_module("appsignal")
5
13
  module AppsignalTrace
6
- include PlatformTrace
7
-
8
14
  # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
9
15
  # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
10
16
  # It can also be specified per-query with `context[:set_appsignal_action_name]`.
11
17
  def initialize(set_action_name: false, **rest)
12
- @set_action_name = set_action_name
18
+ rest[:set_transaction_name] ||= set_action_name
19
+ setup_appsignal_monitor(**rest)
13
20
  super
14
21
  end
15
22
 
16
- {
17
- "lex" => "lex.graphql",
18
- "parse" => "parse.graphql",
19
- "validate" => "validate.graphql",
20
- "analyze_query" => "analyze.graphql",
21
- "analyze_multiplex" => "analyze.graphql",
22
- "execute_multiplex" => "execute.graphql",
23
- "execute_query" => "execute.graphql",
24
- "execute_query_lazy" => "execute.graphql",
25
- }.each do |trace_method, platform_key|
26
- module_eval <<-RUBY, __FILE__, __LINE__
27
- def #{trace_method}(**data)
28
- #{
29
- if trace_method == "execute_query"
30
- <<-RUBY
31
- set_this_txn_name = data[:query].context[:set_appsignal_action_name]
32
- if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name)
33
- Appsignal::Transaction.current.set_action(transaction_name(data[:query]))
34
- end
35
- RUBY
36
- end
37
- }
38
-
39
- Appsignal.instrument("#{platform_key}") do
40
- super
23
+ class AppsignalMonitor < MonitorTrace::Monitor
24
+ def instrument(keyword, object)
25
+ if keyword == :execute
26
+ query = object.queries.first
27
+ set_this_txn_name = query.context[:set_appsignal_action_name]
28
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name)
29
+ Appsignal::Transaction.current.set_action(transaction_name(query))
41
30
  end
42
31
  end
43
- RUBY
44
- end
45
-
46
- def platform_execute_field(platform_key)
47
- Appsignal.instrument(platform_key) do
48
- yield
32
+ Appsignal.instrument(name_for(keyword, object)) do
33
+ yield
34
+ end
49
35
  end
50
- end
51
36
 
52
- def platform_authorized(platform_key)
53
- Appsignal.instrument(platform_key) do
54
- yield
55
- end
56
- end
37
+ include MonitorTrace::Monitor::GraphQLSuffixNames
38
+ class Event < GraphQL::Tracing::MonitorTrace::Monitor::Event
39
+ def start
40
+ Appsignal::Transaction.current.start_event
41
+ end
57
42
 
58
- def platform_resolve_type(platform_key)
59
- Appsignal.instrument(platform_key) do
60
- yield
43
+ def finish
44
+ Appsignal::Transaction.current.finish_event(
45
+ @monitor.name_for(@keyword, @object),
46
+ "",
47
+ ""
48
+ )
49
+ end
61
50
  end
62
51
  end
63
-
64
- def platform_field_key(field)
65
- "#{field.owner.graphql_name}.#{field.graphql_name}.graphql"
66
- end
67
-
68
- def platform_authorized_key(type)
69
- "#{type.graphql_name}.authorized.graphql"
70
- end
71
-
72
- def platform_resolve_type_key(type)
73
- "#{type.graphql_name}.resolve_type.graphql"
74
- end
75
52
  end
76
53
  end
77
54
  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 AppsignalTracing < PlatformTracing
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ # This trace class calls legacy-style tracer with payload hashes.
6
+ # New-style `trace_with` modules significantly reduce the overhead of tracing,
7
+ # but that advantage is lost when legacy-style tracers are also used (since the payload hashes are still constructed).
8
+ module CallLegacyTracers
9
+ def lex(query_string:)
10
+ (@multiplex || @query).trace("lex", { query_string: query_string }) { super }
11
+ end
12
+
13
+ def parse(query_string:)
14
+ (@multiplex || @query).trace("parse", { query_string: query_string }) { super }
15
+ end
16
+
17
+ def validate(query:, validate:)
18
+ query.trace("validate", { validate: validate, query: query }) { super }
19
+ end
20
+
21
+ def analyze_multiplex(multiplex:)
22
+ multiplex.trace("analyze_multiplex", { multiplex: multiplex }) { super }
23
+ end
24
+
25
+ def analyze_query(query:)
26
+ query.trace("analyze_query", { query: query }) { super }
27
+ end
28
+
29
+ def execute_multiplex(multiplex:)
30
+ multiplex.trace("execute_multiplex", { multiplex: multiplex }) { super }
31
+ end
32
+
33
+ def execute_query(query:)
34
+ query.trace("execute_query", { query: query }) { super }
35
+ end
36
+
37
+ def execute_query_lazy(query:, multiplex:)
38
+ multiplex.trace("execute_query_lazy", { multiplex: multiplex, query: query }) { super }
39
+ end
40
+
41
+ def execute_field(field:, query:, ast_node:, arguments:, object:)
42
+ query.trace("execute_field", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super }
43
+ end
44
+
45
+ def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
46
+ query.trace("execute_field_lazy", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super }
47
+ end
48
+
49
+ def authorized(query:, type:, object:)
50
+ query.trace("authorized", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
51
+ end
52
+
53
+ def authorized_lazy(query:, type:, object:)
54
+ query.trace("authorized_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
55
+ end
56
+
57
+ def resolve_type(query:, type:, object:)
58
+ query.trace("resolve_type", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
59
+ end
60
+
61
+ def resolve_type_lazy(query:, type:, object:)
62
+ query.trace("resolve_type_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,183 +1,71 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/tracing/monitor_trace"
2
3
 
3
4
  module GraphQL
4
5
  module Tracing
6
+ # A tracer for reporting to DataDog
7
+ # @example Adding this tracer to your schema
8
+ # class MySchema < GraphQL::Schema
9
+ # trace_with GraphQL::Tracing::DataDogTrace
10
+ # end
11
+ # @example Skipping `resolve_type` and `authorized` events
12
+ # trace_with GraphQL::Tracing::DataDogTrace, trace_authorized: false, trace_resolve_type: false
13
+ DataDogTrace = MonitorTrace.create_module("datadog")
5
14
  module DataDogTrace
6
- # @param tracer [#trace] Deprecated
7
- # @param analytics_enabled [Boolean] Deprecated
8
- # @param analytics_sample_rate [Float] Deprecated
9
- def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest)
10
- if tracer.nil?
11
- tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
15
+ class DatadogMonitor < MonitorTrace::Monitor
16
+ def initialize(set_transaction_name:, service: nil, tracer: nil, **_rest)
17
+ super
18
+ if tracer.nil?
19
+ tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
20
+ end
21
+ @tracer = tracer
22
+ @service_name = service
23
+ @has_prepare_span = @trace.respond_to?(:prepare_span)
12
24
  end
13
- @tracer = tracer
14
-
15
- @analytics_enabled = analytics_enabled
16
- @analytics_sample_rate = analytics_sample_rate
17
-
18
- @service_name = service
19
- @has_prepare_span = respond_to?(:prepare_span)
20
- super
21
- end
22
-
23
- {
24
- 'lex' => 'lex.graphql',
25
- 'parse' => 'parse.graphql',
26
- 'validate' => 'validate.graphql',
27
- 'analyze_query' => 'analyze.graphql',
28
- 'analyze_multiplex' => 'analyze.graphql',
29
- 'execute_multiplex' => 'execute.graphql',
30
- 'execute_query' => 'execute.graphql',
31
- 'execute_query_lazy' => 'execute.graphql',
32
- }.each do |trace_method, trace_key|
33
- module_eval <<-RUBY, __FILE__, __LINE__
34
- def #{trace_method}(**data)
35
- @tracer.trace("#{trace_key}", service: @service_name, type: 'custom') do |span|
36
- span.set_tag('component', 'graphql')
37
- span.set_tag('operation', '#{trace_method}')
38
-
39
- #{
40
- if trace_method == 'execute_multiplex'
41
- <<-RUBY
42
- operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
43
25
 
44
- resource = if operations.empty?
45
- first_query = data[:multiplex].queries.first
46
- fallback_transaction_name(first_query && first_query.context)
47
- else
48
- operations
49
- end
50
- span.resource = resource if resource
26
+ attr_reader :tracer, :service_name
51
27
 
52
- # [Deprecated] will be removed in the future
53
- span.set_metric('_dd1.sr.eausr', @analytics_sample_rate) if @analytics_enabled
54
- RUBY
55
- elsif trace_method == 'execute_query'
56
- <<-RUBY
57
- span.set_tag(:selected_operation_name, data[:query].selected_operation_name)
58
- span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type)
59
- span.set_tag(:query_string, data[:query].query_string)
60
- RUBY
61
- end
62
- }
63
- if @has_prepare_span
64
- prepare_span("#{trace_method.sub("platform_", "")}", data, span)
28
+ def instrument(keyword, object)
29
+ trace_key = name_for(keyword, object)
30
+ @tracer.trace(trace_key, service: @service_name, type: 'custom') do |span|
31
+ span.set_tag('component', 'graphql')
32
+ op_name = keyword.respond_to?(:name) ? keyword.name : keyword.to_s
33
+ span.set_tag('operation', op_name)
34
+
35
+ if keyword == :execute
36
+ operations = object.queries.map(&:selected_operation_name).join(', ')
37
+ first_query = object.queries.first
38
+ resource = if operations.empty?
39
+ fallback_transaction_name(first_query && first_query.context)
40
+ else
41
+ operations
65
42
  end
66
- super
67
- end
68
- end
69
- RUBY
70
- end
43
+ span.resource = resource if resource
71
44
 
72
- def execute_field_span(span_key, query, field, ast_node, arguments, object)
73
- return_type = field.type.unwrap
74
- trace_field = if return_type.kind.scalar? || return_type.kind.enum?
75
- (field.trace.nil? && @trace_scalars) || field.trace
76
- else
77
- true
78
- end
79
- platform_key = if trace_field
80
- @platform_key_cache[DataDogTrace].platform_field_key_cache[field]
81
- else
82
- nil
83
- end
84
- if platform_key && trace_field
85
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
86
- span.set_tag('component', 'graphql')
87
- span.set_tag('operation', span_key)
45
+ span.set_tag("selected_operation_name", first_query.selected_operation_name)
46
+ span.set_tag("selected_operation_type", first_query.selected_operation&.operation_type)
47
+ span.set_tag("query_string", first_query.query_string)
48
+ end
88
49
 
89
50
  if @has_prepare_span
90
- prepare_span_data = { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }
91
- prepare_span(span_key, prepare_span_data, span)
51
+ @trace.prepare_span(keyword, object, span)
92
52
  end
93
53
  yield
94
54
  end
95
- else
96
- yield
97
- end
98
- end
99
- def execute_field(query:, field:, ast_node:, arguments:, object:)
100
- execute_field_span("execute_field", query, field, ast_node, arguments, object) do
101
- super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
102
- end
103
- end
104
-
105
- def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
106
- execute_field_span("execute_field_lazy", query, field, ast_node, arguments, object) do
107
- super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
108
- end
109
- end
110
-
111
- def authorized(query:, type:, object:)
112
- authorized_span("authorized", object, type, query) do
113
- super(query: query, type: type, object: object)
114
55
  end
115
- end
116
-
117
- def authorized_span(span_key, object, type, query)
118
- platform_key = @platform_key_cache[DataDogTrace].platform_authorized_key_cache[type]
119
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
120
- span.set_tag('component', 'graphql')
121
- span.set_tag('operation', span_key)
122
56
 
123
- if @has_prepare_span
124
- prepare_span(span_key, {object: object, type: type, query: query}, span)
57
+ include MonitorTrace::Monitor::GraphQLSuffixNames
58
+ class Event < MonitorTrace::Monitor::Event
59
+ def start
60
+ name = @monitor.name_for(keyword, object)
61
+ @dd_span = @monitor.tracer.trace(name, service: @monitor.service_name, type: 'custom')
125
62
  end
126
- yield
127
- end
128
- end
129
-
130
- def authorized_lazy(object:, type:, query:)
131
- authorized_span("authorized_lazy", object, type, query) do
132
- super(query: query, type: type, object: object)
133
- end
134
- end
135
-
136
- def resolve_type(object:, type:, query:)
137
- resolve_type_span("resolve_type", object, type, query) do
138
- super(object: object, query: query, type: type)
139
- end
140
- end
141
-
142
- def resolve_type_lazy(object:, type:, query:)
143
- resolve_type_span("resolve_type_lazy", object, type, query) do
144
- super(object: object, query: query, type: type)
145
- end
146
- end
147
-
148
- def resolve_type_span(span_key, object, type, query)
149
- platform_key = @platform_key_cache[DataDogTrace].platform_resolve_type_key_cache[type]
150
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
151
- span.set_tag('component', 'graphql')
152
- span.set_tag('operation', span_key)
153
63
 
154
- if @has_prepare_span
155
- prepare_span(span_key, {object: object, type: type, query: query}, span)
64
+ def finish
65
+ @dd_span.finish
156
66
  end
157
- yield
158
67
  end
159
68
  end
160
-
161
- include PlatformTrace
162
-
163
- # Implement this method in a subclass to apply custom tags to datadog spans
164
- # @param key [String] The event being traced
165
- # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
166
- # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
167
- # def prepare_span(key, data, span)
168
- # end
169
-
170
- def platform_field_key(field)
171
- field.path
172
- end
173
-
174
- def platform_authorized_key(type)
175
- "#{type.graphql_name}.authorized"
176
- end
177
-
178
- def platform_resolve_type_key(type)
179
- "#{type.graphql_name}.resolve_type"
180
- end
181
69
  end
182
70
  end
183
71
  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 DataDogTracing < PlatformTracing
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ class ActiveRecordBackend
7
+ class GraphqlDetailedTrace < ActiveRecord::Base
8
+ end
9
+
10
+ def initialize(limit: nil, model_class: nil)
11
+ @limit = limit
12
+ @model_class = model_class || GraphqlDetailedTrace
13
+ end
14
+
15
+ def traces(last:, before:)
16
+ gdts = @model_class.all.order("begin_ms DESC")
17
+ if before
18
+ gdts = gdts.where("begin_ms < ?", before)
19
+ end
20
+ if last
21
+ gdts = gdts.limit(last)
22
+ end
23
+ gdts.map { |gdt| record_to_stored_trace(gdt) }
24
+ end
25
+
26
+ def delete_trace(id)
27
+ @model_class.where(id: id).destroy_all
28
+ nil
29
+ end
30
+
31
+ def delete_all_traces
32
+ @model_class.all.destroy_all
33
+ end
34
+
35
+ def find_trace(id)
36
+ gdt = @model_class.find_by(id: id)
37
+ if gdt
38
+ record_to_stored_trace(gdt)
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
45
+ gdt = @model_class.create!(
46
+ begin_ms: begin_ms,
47
+ operation_name: operation_name,
48
+ duration_ms: duration_ms,
49
+ trace_data: trace_data,
50
+ )
51
+ if @limit
52
+ @model_class
53
+ .where("id NOT IN(SELECT id FROM graphql_detailed_traces ORDER BY begin_ms DESC LIMIT ?)", @limit)
54
+ .delete_all
55
+ end
56
+ gdt.id
57
+ end
58
+
59
+ private
60
+
61
+ def record_to_stored_trace(gdt)
62
+ StoredTrace.new(
63
+ id: gdt.id,
64
+ begin_ms: gdt.begin_ms,
65
+ operation_name: gdt.operation_name,
66
+ duration_ms: gdt.duration_ms,
67
+ trace_data: gdt.trace_data
68
+ )
69
+
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ # An in-memory trace storage backend. Suitable for testing and development only.
7
+ # It won't work for multi-process deployments and everything is erased when the app is restarted.
8
+ class MemoryBackend
9
+ def initialize(limit: nil)
10
+ @limit = limit
11
+ @traces = {}
12
+ @next_id = 0
13
+ end
14
+
15
+ def traces(last:, before:)
16
+ page = []
17
+ @traces.values.reverse_each do |trace|
18
+ if page.size == last
19
+ break
20
+ elsif before.nil? || trace.begin_ms < before
21
+ page << trace
22
+ end
23
+ end
24
+ page
25
+ end
26
+
27
+ def find_trace(id)
28
+ @traces[id]
29
+ end
30
+
31
+ def delete_trace(id)
32
+ @traces.delete(id.to_i)
33
+ nil
34
+ end
35
+
36
+ def delete_all_traces
37
+ @traces.clear
38
+ nil
39
+ end
40
+
41
+ def save_trace(operation_name, duration, begin_ms, trace_data)
42
+ id = @next_id
43
+ @next_id += 1
44
+ @traces[id] = DetailedTrace::StoredTrace.new(
45
+ id: id,
46
+ operation_name: operation_name,
47
+ duration_ms: duration,
48
+ begin_ms: begin_ms,
49
+ trace_data: trace_data
50
+ )
51
+ if @limit && @traces.size > @limit
52
+ del_keys = @traces.keys[0...-@limit]
53
+ del_keys.each { |k| @traces.delete(k) }
54
+ end
55
+ id
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end