graphql 2.4.3 → 2.5.3

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/query_complexity.rb +87 -7
  4. data/lib/graphql/analysis/visitor.rb +38 -41
  5. data/lib/graphql/analysis.rb +15 -12
  6. data/lib/graphql/autoload.rb +38 -0
  7. data/lib/graphql/backtrace/table.rb +118 -55
  8. data/lib/graphql/backtrace.rb +1 -19
  9. data/lib/graphql/current.rb +7 -2
  10. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  11. data/lib/graphql/dashboard/installable.rb +22 -0
  12. data/lib/graphql/dashboard/limiters.rb +93 -0
  13. data/lib/graphql/dashboard/operation_store.rb +199 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  15. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  16. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  17. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  18. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  19. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  20. data/lib/graphql/dashboard/statics/icon.png +0 -0
  21. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  37. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  38. data/lib/graphql/dashboard.rb +158 -0
  39. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  40. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  41. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  42. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  43. data/lib/graphql/dataloader/source.rb +3 -3
  44. data/lib/graphql/dataloader.rb +43 -14
  45. data/lib/graphql/dig.rb +2 -1
  46. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  47. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  48. data/lib/graphql/execution/interpreter/runtime.rb +96 -52
  49. data/lib/graphql/execution/interpreter.rb +16 -7
  50. data/lib/graphql/execution/multiplex.rb +6 -5
  51. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  52. data/lib/graphql/invalid_name_error.rb +1 -1
  53. data/lib/graphql/invalid_null_error.rb +19 -16
  54. data/lib/graphql/language/cache.rb +13 -0
  55. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  56. data/lib/graphql/language/lexer.rb +11 -4
  57. data/lib/graphql/language/nodes.rb +3 -0
  58. data/lib/graphql/language/parser.rb +15 -8
  59. data/lib/graphql/language/printer.rb +8 -8
  60. data/lib/graphql/language/static_visitor.rb +37 -33
  61. data/lib/graphql/language/visitor.rb +59 -55
  62. data/lib/graphql/pagination/connection.rb +1 -1
  63. data/lib/graphql/query/context/scoped_context.rb +1 -1
  64. data/lib/graphql/query/context.rb +7 -5
  65. data/lib/graphql/query/variable_validation_error.rb +1 -1
  66. data/lib/graphql/query.rb +22 -32
  67. data/lib/graphql/railtie.rb +7 -0
  68. data/lib/graphql/schema/addition.rb +1 -1
  69. data/lib/graphql/schema/always_visible.rb +1 -0
  70. data/lib/graphql/schema/argument.rb +7 -8
  71. data/lib/graphql/schema/build_from_definition.rb +99 -53
  72. data/lib/graphql/schema/directive/flagged.rb +3 -1
  73. data/lib/graphql/schema/directive.rb +2 -2
  74. data/lib/graphql/schema/enum.rb +36 -1
  75. data/lib/graphql/schema/enum_value.rb +1 -1
  76. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  77. data/lib/graphql/schema/field.rb +27 -13
  78. data/lib/graphql/schema/field_extension.rb +1 -1
  79. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  80. data/lib/graphql/schema/input_object.rb +77 -40
  81. data/lib/graphql/schema/interface.rb +3 -2
  82. data/lib/graphql/schema/list.rb +1 -1
  83. data/lib/graphql/schema/loader.rb +1 -1
  84. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  85. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  86. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  87. data/lib/graphql/schema/member/has_directives.rb +4 -4
  88. data/lib/graphql/schema/member/has_fields.rb +19 -1
  89. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  90. data/lib/graphql/schema/member/has_validators.rb +1 -1
  91. data/lib/graphql/schema/member/scoped.rb +1 -1
  92. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  93. data/lib/graphql/schema/member.rb +1 -0
  94. data/lib/graphql/schema/object.rb +25 -8
  95. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  96. data/lib/graphql/schema/resolver.rb +12 -10
  97. data/lib/graphql/schema/subscription.rb +52 -6
  98. data/lib/graphql/schema/union.rb +1 -1
  99. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  100. data/lib/graphql/schema/validator.rb +1 -1
  101. data/lib/graphql/schema/visibility/migration.rb +1 -0
  102. data/lib/graphql/schema/visibility/profile.rb +98 -244
  103. data/lib/graphql/schema/visibility/visit.rb +190 -0
  104. data/lib/graphql/schema/visibility.rb +178 -38
  105. data/lib/graphql/schema/warden.rb +18 -5
  106. data/lib/graphql/schema.rb +266 -54
  107. data/lib/graphql/static_validation/all_rules.rb +1 -1
  108. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  109. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  110. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  111. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  112. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  113. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  114. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  115. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  116. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  117. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  118. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  119. data/lib/graphql/static_validation/validation_context.rb +1 -0
  120. data/lib/graphql/static_validation/validator.rb +6 -1
  121. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  122. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  123. data/lib/graphql/subscriptions/event.rb +12 -1
  124. data/lib/graphql/subscriptions/serialize.rb +1 -1
  125. data/lib/graphql/subscriptions.rb +1 -1
  126. data/lib/graphql/testing/helpers.rb +7 -4
  127. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  128. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  129. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  130. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  131. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  132. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  133. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  134. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  135. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  136. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  137. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  138. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  139. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  140. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  141. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  142. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  143. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  144. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  145. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  146. data/lib/graphql/tracing/null_trace.rb +9 -0
  147. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  148. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  149. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  150. data/lib/graphql/tracing/platform_trace.rb +5 -0
  151. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  152. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  153. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  154. data/lib/graphql/tracing/scout_trace.rb +32 -55
  155. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  156. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  157. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  158. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  159. data/lib/graphql/tracing/trace.rb +111 -1
  160. data/lib/graphql/tracing.rb +31 -30
  161. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  162. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  163. data/lib/graphql/types.rb +18 -11
  164. data/lib/graphql/version.rb +1 -1
  165. data/lib/graphql.rb +55 -47
  166. metadata +146 -11
  167. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  168. data/lib/graphql/backtrace/trace.rb +0 -93
  169. data/lib/graphql/backtrace/tracer.rb +0 -80
  170. data/lib/graphql/schema/null_mask.rb +0 -11
  171. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -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,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
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ class RedisBackend
7
+ KEY_PREFIX = "gql:trace:"
8
+ def initialize(redis:, limit: nil)
9
+ @redis = redis
10
+ @key = KEY_PREFIX + "traces"
11
+ @remrangebyrank_limit = limit ? -limit - 1 : nil
12
+ end
13
+
14
+ def traces(last:, before:)
15
+ before = case before
16
+ when Numeric
17
+ "(#{before}"
18
+ when nil
19
+ "+inf"
20
+ end
21
+ str_pairs = @redis.zrange(@key, before, 0, byscore: true, rev: true, limit: [0, last || 100], withscores: true)
22
+ str_pairs.map do |(str_data, score)|
23
+ entry_to_trace(score, str_data)
24
+ end
25
+ end
26
+
27
+ def delete_trace(id)
28
+ @redis.zremrangebyscore(@key, id, id)
29
+ nil
30
+ end
31
+
32
+ def delete_all_traces
33
+ @redis.del(@key)
34
+ end
35
+
36
+ def find_trace(id)
37
+ str_data = @redis.zrange(@key, id, id, byscore: true).first
38
+ if str_data.nil?
39
+ nil
40
+ else
41
+ entry_to_trace(id, str_data)
42
+ end
43
+ end
44
+
45
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
46
+ id = begin_ms
47
+ data = JSON.dump({ "o" => operation_name, "d" => duration_ms, "b" => begin_ms, "t" => Base64.encode64(trace_data) })
48
+ @redis.pipelined do |pipeline|
49
+ pipeline.zadd(@key, id, data)
50
+ if @remrangebyrank_limit
51
+ pipeline.zremrangebyrank(@key, 0, @remrangebyrank_limit)
52
+ end
53
+ end
54
+ id
55
+ end
56
+
57
+ private
58
+
59
+ def entry_to_trace(id, json_str)
60
+ data = JSON.parse(json_str)
61
+ StoredTrace.new(
62
+ id: id,
63
+ operation_name: data["o"],
64
+ duration_ms: data["d"].to_f,
65
+ begin_ms: data["b"].to_i,
66
+ trace_data: Base64.decode64(data["t"]),
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/tracing/detailed_trace/memory_backend"
3
+ require "graphql/tracing/detailed_trace/redis_backend"
4
+
5
+ module GraphQL
6
+ module Tracing
7
+ # `DetailedTrace` can make detailed profiles for a subset of production traffic.
8
+ #
9
+ # When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
10
+ # overriding the one in `context[:trace_mode]`.
11
+ #
12
+ # __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
13
+ # You can configure this database to retail all data (persistent) or to expire data according to your rules.
14
+ # If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
15
+ #
16
+ # @example Adding the sampler to your schema
17
+ # class MySchema < GraphQL::Schema
18
+ # # Add the sampler:
19
+ # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100
20
+ #
21
+ # # And implement this hook to tell it when to take a sample:
22
+ # def self.detailed_trace?(query)
23
+ # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
24
+ # # Could call out to Flipper, etc
25
+ # rand <= 0.000_1 # one in ten thousand
26
+ # end
27
+ # end
28
+ #
29
+ # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
30
+ class DetailedTrace
31
+ # @param redis [Redis] If provided, profiles will be stored in Redis for later review
32
+ # @param limit [Integer] A maximum number of profiles to store
33
+ def self.use(schema, trace_mode: :profile_sample, memory: false, redis: nil, limit: nil)
34
+ storage = if redis
35
+ RedisBackend.new(redis: redis, limit: limit)
36
+ elsif memory
37
+ MemoryBackend.new(limit: limit)
38
+ else
39
+ raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
40
+ end
41
+ schema.detailed_trace = self.new(storage: storage, trace_mode: trace_mode)
42
+ schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
43
+ end
44
+
45
+ def initialize(storage:, trace_mode:)
46
+ @storage = storage
47
+ @trace_mode = trace_mode
48
+ end
49
+
50
+ # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
51
+ attr_reader :trace_mode
52
+
53
+ # @return [String] ID of saved trace
54
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
55
+ @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
56
+ end
57
+
58
+ # @param last [Integer]
59
+ # @param before [Integer] Timestamp in milliseconds since epoch
60
+ # @return [Enumerable<StoredTrace>]
61
+ def traces(last: nil, before: nil)
62
+ @storage.traces(last: last, before: before)
63
+ end
64
+
65
+ # @return [StoredTrace, nil]
66
+ def find_trace(id)
67
+ @storage.find_trace(id)
68
+ end
69
+
70
+ # @return [void]
71
+ def delete_trace(id)
72
+ @storage.delete_trace(id)
73
+ end
74
+
75
+ # @return [void]
76
+ def delete_all_traces
77
+ @storage.delete_all_traces
78
+ end
79
+
80
+ class StoredTrace
81
+ def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
82
+ @id = id
83
+ @operation_name = operation_name
84
+ @duration_ms = duration_ms
85
+ @begin_ms = begin_ms
86
+ @trace_data = trace_data
87
+ end
88
+
89
+ attr_reader :id, :operation_name, :duration_ms, :begin_ms, :trace_data
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GraphQL
3
4
  module Tracing
4
5
  module LegacyHooksTrace