graphql 2.4.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) 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 +11 -4
  46. data/lib/graphql/execution/interpreter/runtime.rb +78 -41
  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 +1 -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 +12 -12
  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 +76 -39
  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_directives.rb +4 -4
  83. data/lib/graphql/schema/member/has_fields.rb +19 -1
  84. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  85. data/lib/graphql/schema/member/has_validators.rb +1 -1
  86. data/lib/graphql/schema/member/scoped.rb +1 -1
  87. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  88. data/lib/graphql/schema/member.rb +1 -0
  89. data/lib/graphql/schema/object.rb +25 -8
  90. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  91. data/lib/graphql/schema/resolver.rb +11 -10
  92. data/lib/graphql/schema/subscription.rb +52 -6
  93. data/lib/graphql/schema/union.rb +1 -1
  94. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  95. data/lib/graphql/schema/validator.rb +1 -1
  96. data/lib/graphql/schema/visibility/migration.rb +1 -0
  97. data/lib/graphql/schema/visibility/profile.rb +69 -237
  98. data/lib/graphql/schema/visibility/visit.rb +190 -0
  99. data/lib/graphql/schema/visibility.rb +169 -28
  100. data/lib/graphql/schema/warden.rb +18 -5
  101. data/lib/graphql/schema.rb +93 -44
  102. data/lib/graphql/static_validation/all_rules.rb +1 -1
  103. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  104. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  105. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  106. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  107. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  108. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  109. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  110. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  111. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  112. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  113. data/lib/graphql/static_validation/validation_context.rb +1 -0
  114. data/lib/graphql/static_validation/validator.rb +6 -1
  115. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  116. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  117. data/lib/graphql/subscriptions/event.rb +12 -1
  118. data/lib/graphql/subscriptions/serialize.rb +1 -1
  119. data/lib/graphql/subscriptions.rb +1 -1
  120. data/lib/graphql/testing/helpers.rb +7 -4
  121. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  122. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  123. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  124. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  125. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  126. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  127. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  128. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  129. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  130. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  131. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  132. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  133. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  134. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  135. data/lib/graphql/tracing/monitor_trace.rb +285 -0
  136. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  137. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  138. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  139. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  140. data/lib/graphql/tracing/null_trace.rb +9 -0
  141. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  142. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  143. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  144. data/lib/graphql/tracing/platform_trace.rb +5 -0
  145. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  146. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  147. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  148. data/lib/graphql/tracing/scout_trace.rb +32 -55
  149. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  150. data/lib/graphql/tracing/sentry_trace.rb +63 -94
  151. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  152. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  153. data/lib/graphql/tracing/trace.rb +111 -1
  154. data/lib/graphql/tracing.rb +31 -30
  155. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  156. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  157. data/lib/graphql/types.rb +18 -11
  158. data/lib/graphql/version.rb +1 -1
  159. data/lib/graphql.rb +55 -47
  160. metadata +146 -11
  161. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  162. data/lib/graphql/backtrace/trace.rb +0 -93
  163. data/lib/graphql/backtrace/tracer.rb +0 -80
  164. data/lib/graphql/schema/null_mask.rb +0 -11
  165. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -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
@@ -1,67 +1,10 @@
1
1
  # frozen_string_literal: true
2
- module GraphQL
3
- module Tracing
4
- # This trace class calls legacy-style tracer with payload hashes.
5
- # New-style `trace_with` modules significantly reduce the overhead of tracing,
6
- # but that advantage is lost when legacy-style tracers are also used (since the payload hashes are still constructed).
7
- module CallLegacyTracers
8
- def lex(query_string:)
9
- (@multiplex || @query).trace("lex", { query_string: query_string }) { super }
10
- end
11
-
12
- def parse(query_string:)
13
- (@multiplex || @query).trace("parse", { query_string: query_string }) { super }
14
- end
15
-
16
- def validate(query:, validate:)
17
- query.trace("validate", { validate: validate, query: query }) { super }
18
- end
19
-
20
- def analyze_multiplex(multiplex:)
21
- multiplex.trace("analyze_multiplex", { multiplex: multiplex }) { super }
22
- end
23
-
24
- def analyze_query(query:)
25
- query.trace("analyze_query", { query: query }) { super }
26
- end
27
-
28
- def execute_multiplex(multiplex:)
29
- multiplex.trace("execute_multiplex", { multiplex: multiplex }) { super }
30
- end
31
-
32
- def execute_query(query:)
33
- query.trace("execute_query", { query: query }) { super }
34
- end
35
-
36
- def execute_query_lazy(query:, multiplex:)
37
- multiplex.trace("execute_query_lazy", { multiplex: multiplex, query: query }) { super }
38
- end
39
2
 
40
- def execute_field(field:, query:, ast_node:, arguments:, object:)
41
- 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 }
42
- end
43
-
44
- def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
45
- 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 }
46
- end
47
-
48
- def authorized(query:, type:, object:)
49
- query.trace("authorized", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
50
- end
51
-
52
- def authorized_lazy(query:, type:, object:)
53
- query.trace("authorized_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
54
- end
55
-
56
- def resolve_type(query:, type:, object:)
57
- query.trace("resolve_type", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
58
- end
59
-
60
- def resolve_type_lazy(query:, type:, object:)
61
- query.trace("resolve_type_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
62
- end
63
- end
3
+ require "graphql/tracing/trace"
4
+ require "graphql/tracing/call_legacy_tracers"
64
5
 
6
+ module GraphQL
7
+ module Tracing
65
8
  class LegacyTrace < Trace
66
9
  include CallLegacyTracers
67
10
  end
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ # This module is the basis for Ruby-level integration with third-party monitoring platforms.
6
+ # Platform-specific traces include this module and implement an adapter.
7
+ #
8
+ # @see ActiveSupportNotificationsTrace Integration via ActiveSupport::Notifications, an alternative approach.
9
+ module MonitorTrace
10
+ class Monitor
11
+ def initialize(trace:, set_transaction_name:, **_rest)
12
+ @trace = trace
13
+ @set_transaction_name = set_transaction_name
14
+ @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }.compare_by_identity
15
+ @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }.compare_by_identity
16
+ @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }.compare_by_identity
17
+ @platform_source_class_key_cache = Hash.new { |h, source_cls| h[source_cls] = platform_source_class_key(source_cls) }.compare_by_identity
18
+ end
19
+
20
+ def instrument(keyword, object, &block)
21
+ raise "Implement #{self.class}#instrument to measure the block"
22
+ end
23
+
24
+ def start_event(keyword, object)
25
+ ev = self.class::Event.new(self, keyword, object)
26
+ ev.start
27
+ ev
28
+ end
29
+
30
+ # Get the transaction name based on the operation type and name if possible, or fall back to a user provided
31
+ # one. Useful for anonymous queries.
32
+ def transaction_name(query)
33
+ selected_op = query.selected_operation
34
+ txn_name = if selected_op
35
+ op_type = selected_op.operation_type
36
+ op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
37
+ "#{op_type}.#{op_name}"
38
+ else
39
+ "query.anonymous"
40
+ end
41
+ "GraphQL/#{txn_name}"
42
+ end
43
+
44
+ def fallback_transaction_name(context)
45
+ context[:tracing_fallback_transaction_name]
46
+ end
47
+
48
+ def name_for(keyword, object)
49
+ case keyword
50
+ when :execute_field
51
+ @platform_field_key_cache[object]
52
+ when :authorized
53
+ @platform_authorized_key_cache[object]
54
+ when :resolve_type
55
+ @platform_resolve_type_key_cache[object]
56
+ when :dataloader_source
57
+ @platform_source_class_key_cache[object.class]
58
+ when :parse then self.class::PARSE_NAME
59
+ when :lex then self.class::LEX_NAME
60
+ when :execute then self.class::EXECUTE_NAME
61
+ when :analyze then self.class::ANALYZE_NAME
62
+ when :validate then self.class::VALIDATE_NAME
63
+ else
64
+ raise "No name for #{keyword.inspect}"
65
+ end
66
+ end
67
+
68
+ class Event
69
+ def initialize(monitor, keyword, object)
70
+ @monitor = monitor
71
+ @keyword = keyword
72
+ @object = object
73
+ end
74
+
75
+ attr_reader :keyword, :object
76
+
77
+ def start
78
+ raise "Implement #{self.class}#start to begin a new event (#{inspect})"
79
+ end
80
+
81
+ def finish
82
+ raise "Implement #{self.class}#finish to end this event (#{inspect})"
83
+ end
84
+ end
85
+
86
+ module GraphQLSuffixNames
87
+ PARSE_NAME = "parse.graphql"
88
+ LEX_NAME = "lex.graphql"
89
+ VALIDATE_NAME = "validate.graphql"
90
+ EXECUTE_NAME = "execute.graphql"
91
+ ANALYZE_NAME = "analyze.graphql"
92
+
93
+ private
94
+
95
+ def platform_field_key(field)
96
+ "#{field.path}.graphql"
97
+ end
98
+
99
+ def platform_authorized_key(type)
100
+ "#{type.graphql_name}.authorized.graphql"
101
+ end
102
+
103
+ def platform_resolve_type_key(type)
104
+ "#{type.graphql_name}.resolve_type.graphql"
105
+ end
106
+
107
+ def platform_source_key(source_class)
108
+ "#{source_class.name.gsub("::", "_").name.underscore}.fetch.graphql"
109
+ end
110
+ end
111
+
112
+ module GraphQLPrefixNames
113
+ PARSE_NAME = "graphql.parse"
114
+ LEX_NAME = "graphql.lex"
115
+ VALIDATE_NAME = "graphql.validate"
116
+ EXECUTE_NAME = "graphql.execute"
117
+ ANALYZE_NAME = "graphql.analyze"
118
+
119
+ def platform_field_key(field)
120
+ "graphql.#{field.path}"
121
+ end
122
+
123
+ def platform_authorized_key(type)
124
+ "graphql.authorized.#{type.graphql_name}"
125
+ end
126
+
127
+ def platform_resolve_type_key(type)
128
+ "graphql.resolve_type.#{type.graphql_name}"
129
+ end
130
+
131
+ def platform_source_class_key(source_class)
132
+ "graphql.fetch.#{source_class.name.gsub("::", "_")}"
133
+ end
134
+ end
135
+ end
136
+
137
+ def self.create_module(monitor_name)
138
+ if !monitor_name.match?(/[a-z]+/)
139
+ raise ArgumentError, "monitor name must be [a-z]+, not: #{monitor_name.inspect}"
140
+ end
141
+
142
+ trace_module = Module.new
143
+ code = MODULE_TEMPLATE % {
144
+ monitor: monitor_name,
145
+ monitor_class: monitor_name.capitalize + "Monitor",
146
+ }
147
+ trace_module.module_eval(code, __FILE__, __LINE__ + 5) # rubocop:disable Development/NoEvalCop This is build-time with a validated string
148
+ trace_module
149
+ end
150
+
151
+ MODULE_TEMPLATE = <<~RUBY
152
+ # @param set_transaction_name [Boolean] If `true`, use the GraphQL operation name as the request name on the monitoring platform
153
+ # @param trace_scalars [Boolean] If `true`, leaf fields will be traced too (Scalars _and_ Enums)
154
+ # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
155
+ # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
156
+ def initialize(...)
157
+ setup_%{monitor}_monitor(...)
158
+ super
159
+ end
160
+
161
+ def setup_%{monitor}_monitor(trace_scalars: false, trace_authorized: true, trace_resolve_type: true, set_transaction_name: false, **kwargs)
162
+ @trace_scalars = trace_scalars
163
+ @trace_authorized = trace_authorized
164
+ @trace_resolve_type = trace_resolve_type
165
+ @set_transaction_name = set_transaction_name
166
+ @%{monitor} = %{monitor_class}.new(trace: self, set_transaction_name: @set_transaction_name, **kwargs)
167
+ end
168
+
169
+ def parse(query_string:)
170
+ @%{monitor}.instrument(:parse, query_string) do
171
+ super
172
+ end
173
+ end
174
+
175
+ def lex(query_string:)
176
+ @%{monitor}.instrument(:lex, query_string) do
177
+ super
178
+ end
179
+ end
180
+
181
+ def validate(query:, validate:)
182
+ @%{monitor}.instrument(:validate, query) do
183
+ super
184
+ end
185
+ end
186
+
187
+ def begin_analyze_multiplex(multiplex, analyzers)
188
+ begin_%{monitor}_event(:analyze, nil)
189
+ super
190
+ end
191
+
192
+ def end_analyze_multiplex(multiplex, analyzers)
193
+ finish_%{monitor}_event
194
+ super
195
+ end
196
+
197
+ def execute_multiplex(multiplex:)
198
+ @%{monitor}.instrument(:execute, multiplex) do
199
+ super
200
+ end
201
+ end
202
+
203
+ def begin_execute_field(field, object, arguments, query)
204
+ return_type = field.type.unwrap
205
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
206
+ (field.trace.nil? && @trace_scalars) || field.trace
207
+ else
208
+ true
209
+ end
210
+
211
+ if trace_field
212
+ begin_%{monitor}_event(:execute_field, field)
213
+ end
214
+ super
215
+ end
216
+
217
+ def end_execute_field(field, object, arguments, query, result)
218
+ finish_%{monitor}_event
219
+ super
220
+ end
221
+
222
+ def dataloader_fiber_yield(source)
223
+ Fiber[PREVIOUS_EV_KEY] = finish_%{monitor}_event
224
+ super
225
+ end
226
+
227
+ def dataloader_fiber_resume(source)
228
+ prev_ev = Fiber[PREVIOUS_EV_KEY]
229
+ if prev_ev
230
+ begin_%{monitor}_event(prev_ev.keyword, prev_ev.object)
231
+ end
232
+ super
233
+ end
234
+
235
+ def begin_authorized(type, object, context)
236
+ @trace_authorized && begin_%{monitor}_event(:authorized, type)
237
+ super
238
+ end
239
+
240
+ def end_authorized(type, object, context, result)
241
+ finish_%{monitor}_event
242
+ super
243
+ end
244
+
245
+ def begin_resolve_type(type, value, context)
246
+ @trace_resolve_type && begin_%{monitor}_event(:resolve_type, type)
247
+ super
248
+ end
249
+
250
+ def end_resolve_type(type, value, context, resolved_type)
251
+ finish_%{monitor}_event
252
+ super
253
+ end
254
+
255
+ def begin_dataloader_source(source)
256
+ begin_%{monitor}_event(:dataloader_source, source)
257
+ super
258
+ end
259
+
260
+ def end_dataloader_source(source)
261
+ finish_%{monitor}_event
262
+ super
263
+ end
264
+
265
+ CURRENT_EV_KEY = :__graphql_%{monitor}_trace_event
266
+ PREVIOUS_EV_KEY = :__graphql_%{monitor}_trace_previous_event
267
+
268
+ private
269
+
270
+ def begin_%{monitor}_event(keyword, object)
271
+ Fiber[CURRENT_EV_KEY] = @%{monitor}.start_event(keyword, object)
272
+ end
273
+
274
+ def finish_%{monitor}_event
275
+ if ev = Fiber[CURRENT_EV_KEY]
276
+ ev.finish
277
+ # Use `false` to prevent grabbing an event from a parent fiber
278
+ Fiber[CURRENT_EV_KEY] = false
279
+ ev
280
+ end
281
+ end
282
+ RUBY
283
+ end
284
+ end
285
+ end
@@ -1,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