graphql 2.2.17 → 2.5.16

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install_generator.rb +46 -0
  4. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  5. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  6. data/lib/generators/graphql/templates/schema.erb +3 -0
  7. data/lib/generators/graphql/type_generator.rb +1 -1
  8. data/lib/graphql/analysis/analyzer.rb +90 -0
  9. data/lib/graphql/analysis/field_usage.rb +82 -0
  10. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  11. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  12. data/lib/graphql/analysis/query_complexity.rb +263 -0
  13. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  14. data/lib/graphql/analysis/visitor.rb +280 -0
  15. data/lib/graphql/analysis.rb +95 -1
  16. data/lib/graphql/autoload.rb +38 -0
  17. data/lib/graphql/backtrace/table.rb +118 -55
  18. data/lib/graphql/backtrace.rb +1 -19
  19. data/lib/graphql/current.rb +57 -0
  20. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  21. data/lib/graphql/dashboard/installable.rb +22 -0
  22. data/lib/graphql/dashboard/limiters.rb +93 -0
  23. data/lib/graphql/dashboard/operation_store.rb +199 -0
  24. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  25. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  26. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  27. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  28. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  29. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  30. data/lib/graphql/dashboard/statics/icon.png +0 -0
  31. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  42. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  43. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  44. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  45. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  46. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  47. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  48. data/lib/graphql/dashboard.rb +158 -0
  49. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  50. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  51. data/lib/graphql/dataloader/async_dataloader.rb +46 -19
  52. data/lib/graphql/dataloader/null_dataloader.rb +51 -10
  53. data/lib/graphql/dataloader/source.rb +20 -9
  54. data/lib/graphql/dataloader.rb +153 -45
  55. data/lib/graphql/date_encoding_error.rb +1 -1
  56. data/lib/graphql/dig.rb +2 -1
  57. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  58. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  59. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  60. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +63 -5
  61. data/lib/graphql/execution/interpreter/runtime.rb +321 -222
  62. data/lib/graphql/execution/interpreter.rb +23 -30
  63. data/lib/graphql/execution/lookahead.rb +18 -11
  64. data/lib/graphql/execution/multiplex.rb +6 -5
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +1 -1
  67. data/lib/graphql/introspection/entry_points.rb +2 -2
  68. data/lib/graphql/introspection/field_type.rb +1 -1
  69. data/lib/graphql/introspection/schema_type.rb +6 -11
  70. data/lib/graphql/introspection/type_type.rb +5 -5
  71. data/lib/graphql/invalid_name_error.rb +1 -1
  72. data/lib/graphql/invalid_null_error.rb +20 -17
  73. data/lib/graphql/language/cache.rb +13 -0
  74. data/lib/graphql/language/comment.rb +18 -0
  75. data/lib/graphql/language/document_from_schema_definition.rb +64 -35
  76. data/lib/graphql/language/lexer.rb +72 -42
  77. data/lib/graphql/language/nodes.rb +93 -52
  78. data/lib/graphql/language/parser.rb +168 -61
  79. data/lib/graphql/language/printer.rb +31 -15
  80. data/lib/graphql/language/sanitized_printer.rb +1 -1
  81. data/lib/graphql/language.rb +61 -1
  82. data/lib/graphql/pagination/connection.rb +1 -1
  83. data/lib/graphql/query/context/scoped_context.rb +1 -1
  84. data/lib/graphql/query/context.rb +46 -47
  85. data/lib/graphql/query/null_context.rb +3 -5
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query/validation_pipeline.rb +2 -2
  88. data/lib/graphql/query/variable_validation_error.rb +1 -1
  89. data/lib/graphql/query.rb +123 -69
  90. data/lib/graphql/railtie.rb +7 -0
  91. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  92. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  93. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  94. data/lib/graphql/rubocop.rb +2 -0
  95. data/lib/graphql/schema/addition.rb +26 -13
  96. data/lib/graphql/schema/always_visible.rb +7 -2
  97. data/lib/graphql/schema/argument.rb +57 -8
  98. data/lib/graphql/schema/build_from_definition.rb +116 -49
  99. data/lib/graphql/schema/directive/flagged.rb +4 -2
  100. data/lib/graphql/schema/directive.rb +54 -2
  101. data/lib/graphql/schema/enum.rb +107 -24
  102. data/lib/graphql/schema/enum_value.rb +10 -2
  103. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  104. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  105. data/lib/graphql/schema/field.rb +134 -45
  106. data/lib/graphql/schema/field_extension.rb +1 -1
  107. data/lib/graphql/schema/has_single_input_argument.rb +6 -2
  108. data/lib/graphql/schema/input_object.rb +122 -64
  109. data/lib/graphql/schema/interface.rb +23 -5
  110. data/lib/graphql/schema/introspection_system.rb +6 -17
  111. data/lib/graphql/schema/late_bound_type.rb +4 -0
  112. data/lib/graphql/schema/list.rb +3 -3
  113. data/lib/graphql/schema/loader.rb +3 -2
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  115. data/lib/graphql/schema/member/has_arguments.rb +44 -58
  116. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  118. data/lib/graphql/schema/member/has_directives.rb +4 -4
  119. data/lib/graphql/schema/member/has_fields.rb +26 -6
  120. data/lib/graphql/schema/member/has_interfaces.rb +6 -6
  121. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  122. data/lib/graphql/schema/member/has_validators.rb +1 -1
  123. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  124. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  125. data/lib/graphql/schema/member.rb +1 -0
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +25 -8
  128. data/lib/graphql/schema/printer.rb +1 -0
  129. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  130. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  131. data/lib/graphql/schema/resolver.rb +29 -23
  132. data/lib/graphql/schema/scalar.rb +1 -6
  133. data/lib/graphql/schema/subscription.rb +52 -6
  134. data/lib/graphql/schema/timeout.rb +19 -2
  135. data/lib/graphql/schema/type_expression.rb +2 -2
  136. data/lib/graphql/schema/union.rb +1 -1
  137. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  139. data/lib/graphql/schema/validator.rb +3 -1
  140. data/lib/graphql/schema/visibility/migration.rb +188 -0
  141. data/lib/graphql/schema/visibility/profile.rb +445 -0
  142. data/lib/graphql/schema/visibility/visit.rb +190 -0
  143. data/lib/graphql/schema/visibility.rb +311 -0
  144. data/lib/graphql/schema/warden.rb +190 -20
  145. data/lib/graphql/schema.rb +695 -167
  146. data/lib/graphql/static_validation/all_rules.rb +2 -2
  147. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  148. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  149. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  150. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  151. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  152. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  153. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  154. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  155. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  156. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  157. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  158. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  159. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  160. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  161. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  162. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  163. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  164. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  165. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  166. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  167. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  168. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  169. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  170. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  171. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  172. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  173. data/lib/graphql/static_validation/validation_context.rb +18 -2
  174. data/lib/graphql/static_validation/validator.rb +6 -1
  175. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -3
  176. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  177. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  178. data/lib/graphql/subscriptions/event.rb +13 -2
  179. data/lib/graphql/subscriptions/serialize.rb +1 -1
  180. data/lib/graphql/subscriptions.rb +7 -5
  181. data/lib/graphql/testing/helpers.rb +48 -16
  182. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  183. data/lib/graphql/testing.rb +1 -0
  184. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  185. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  186. data/lib/graphql/tracing/appoptics_trace.rb +5 -1
  187. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  188. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  189. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  190. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  191. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  192. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  193. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  194. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  195. data/lib/graphql/tracing/detailed_trace.rb +141 -0
  196. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  197. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  198. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  199. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  200. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  201. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  202. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  203. data/lib/graphql/tracing/null_trace.rb +9 -0
  204. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  205. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  206. data/lib/graphql/tracing/perfetto_trace.rb +818 -0
  207. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  208. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  209. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  210. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  211. data/lib/graphql/tracing/scout_trace.rb +32 -58
  212. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  213. data/lib/graphql/tracing/sentry_trace.rb +64 -98
  214. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  215. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  216. data/lib/graphql/tracing/trace.rb +111 -1
  217. data/lib/graphql/tracing.rb +31 -30
  218. data/lib/graphql/type_kinds.rb +2 -1
  219. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  220. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  221. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  222. data/lib/graphql/types.rb +18 -11
  223. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  224. data/lib/graphql/version.rb +1 -1
  225. data/lib/graphql.rb +64 -54
  226. metadata +197 -22
  227. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  228. data/lib/graphql/analysis/ast/field_usage.rb +0 -82
  229. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  230. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  231. data/lib/graphql/analysis/ast/query_complexity.rb +0 -182
  232. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  233. data/lib/graphql/analysis/ast.rb +0 -94
  234. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  235. data/lib/graphql/backtrace/trace.rb +0 -93
  236. data/lib/graphql/backtrace/tracer.rb +0 -80
  237. data/lib/graphql/language/token.rb +0 -34
  238. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  239. data/lib/graphql/schema/null_mask.rb +0 -11
  240. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -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,141 @@
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
+ # By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize
13
+ # this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations
14
+ # entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`.
15
+ #
16
+ # __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
17
+ # You can configure this database to retain all data (persistent) or to expire data according to your rules.
18
+ # If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
19
+ #
20
+ # @example Adding the sampler to your schema
21
+ # class MySchema < GraphQL::Schema
22
+ # # Add the sampler:
23
+ # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100
24
+ #
25
+ # # And implement this hook to tell it when to take a sample:
26
+ # def self.detailed_trace?(query)
27
+ # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
28
+ # # Could call out to Flipper, etc
29
+ # rand <= 0.000_1 # one in ten thousand
30
+ # end
31
+ # end
32
+ #
33
+ # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
34
+ #
35
+ # @example Customizing debug output in traces
36
+ # class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace
37
+ # def inspect_object(object)
38
+ # if object.is_a?(SomeThing)
39
+ # # handle it specially ...
40
+ # else
41
+ # super
42
+ # end
43
+ # end
44
+ # end
45
+ #
46
+ # @example disabling debug annotations completely
47
+ # use DetailedTrace, debug: false, ...
48
+ #
49
+ # @example disabling debug annotations for one query
50
+ # MySchema.execute(query_str, context: { detailed_trace_debug: false })
51
+ #
52
+ class DetailedTrace
53
+ # @param redis [Redis] If provided, profiles will be stored in Redis for later review
54
+ # @param limit [Integer] A maximum number of profiles to store
55
+ # @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead)
56
+ def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil)
57
+ storage = if redis
58
+ RedisBackend.new(redis: redis, limit: limit)
59
+ elsif memory
60
+ MemoryBackend.new(limit: limit)
61
+ else
62
+ raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
63
+ end
64
+ detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug)
65
+ schema.detailed_trace = detailed_trace
66
+ schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
67
+ end
68
+
69
+ def initialize(storage:, trace_mode:, debug:)
70
+ @storage = storage
71
+ @trace_mode = trace_mode
72
+ @debug = debug
73
+ end
74
+
75
+ # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
76
+ attr_reader :trace_mode
77
+
78
+ # @return [String] ID of saved trace
79
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
80
+ @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
81
+ end
82
+
83
+ # @return [Boolean]
84
+ def debug?
85
+ @debug
86
+ end
87
+
88
+ # @param last [Integer]
89
+ # @param before [Integer] Timestamp in milliseconds since epoch
90
+ # @return [Enumerable<StoredTrace>]
91
+ def traces(last: nil, before: nil)
92
+ @storage.traces(last: last, before: before)
93
+ end
94
+
95
+ # @return [StoredTrace, nil]
96
+ def find_trace(id)
97
+ @storage.find_trace(id)
98
+ end
99
+
100
+ # @return [void]
101
+ def delete_trace(id)
102
+ @storage.delete_trace(id)
103
+ end
104
+
105
+ # @return [void]
106
+ def delete_all_traces
107
+ @storage.delete_all_traces
108
+ end
109
+
110
+ def inspect_object(object)
111
+ self.class.inspect_object(object)
112
+ end
113
+
114
+ def self.inspect_object(object)
115
+ if defined?(ActiveRecord::Relation) && object.is_a?(ActiveRecord::Relation)
116
+ "#{object.class}, .to_sql=#{object.to_sql.inspect}"
117
+ else
118
+ object.inspect
119
+ end
120
+ end
121
+
122
+ # Default debug setting
123
+ # @return [true]
124
+ def self.debug?
125
+ true
126
+ end
127
+
128
+ class StoredTrace
129
+ def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
130
+ @id = id
131
+ @operation_name = operation_name
132
+ @duration_ms = duration_ms
133
+ @begin_ms = begin_ms
134
+ @trace_data = trace_data
135
+ end
136
+
137
+ attr_reader :id, :operation_name, :duration_ms, :begin_ms, :trace_data
138
+ end
139
+ end
140
+ end
141
+ 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,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ # This module is the basis for Ruby-level integration with third-party monitoring platforms.
6
+ # Platform-specific traces include this module and implement an adapter.
7
+ #
8
+ # @see ActiveSupportNotificationsTrace Integration via ActiveSupport::Notifications, an alternative approach.
9
+ module MonitorTrace
10
+ class Monitor
11
+ def initialize(trace:, set_transaction_name:, **_rest)
12
+ @trace = trace
13
+ @set_transaction_name = set_transaction_name
14
+ @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }.compare_by_identity
15
+ @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }.compare_by_identity
16
+ @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }.compare_by_identity
17
+ @platform_source_class_key_cache = Hash.new { |h, source_cls| h[source_cls] = platform_source_class_key(source_cls) }.compare_by_identity
18
+ end
19
+
20
+ def instrument(keyword, object, &block)
21
+ raise "Implement #{self.class}#instrument to measure the block"
22
+ end
23
+
24
+ def start_event(keyword, object)
25
+ ev = self.class::Event.new(self, keyword, object)
26
+ ev.start
27
+ ev
28
+ end
29
+
30
+ # Get the transaction name based on the operation type and name if possible, or fall back to a user provided
31
+ # one. Useful for anonymous queries.
32
+ def transaction_name(query)
33
+ selected_op = query.selected_operation
34
+ txn_name = if selected_op
35
+ op_type = selected_op.operation_type
36
+ op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
37
+ "#{op_type}.#{op_name}"
38
+ else
39
+ "query.anonymous"
40
+ end
41
+ "GraphQL/#{txn_name}"
42
+ end
43
+
44
+ def fallback_transaction_name(context)
45
+ context[:tracing_fallback_transaction_name]
46
+ end
47
+
48
+ def name_for(keyword, object)
49
+ case keyword
50
+ when :execute_field
51
+ @platform_field_key_cache[object]
52
+ when :authorized
53
+ @platform_authorized_key_cache[object]
54
+ when :resolve_type
55
+ @platform_resolve_type_key_cache[object]
56
+ when :dataloader_source
57
+ @platform_source_class_key_cache[object.class]
58
+ when :parse then self.class::PARSE_NAME
59
+ when :lex then self.class::LEX_NAME
60
+ when :execute then self.class::EXECUTE_NAME
61
+ when :analyze then self.class::ANALYZE_NAME
62
+ when :validate then self.class::VALIDATE_NAME
63
+ else
64
+ raise "No name for #{keyword.inspect}"
65
+ end
66
+ end
67
+
68
+ class Event
69
+ def initialize(monitor, keyword, object)
70
+ @monitor = monitor
71
+ @keyword = keyword
72
+ @object = object
73
+ end
74
+
75
+ attr_reader :keyword, :object
76
+
77
+ def start
78
+ raise "Implement #{self.class}#start to begin a new event (#{inspect})"
79
+ end
80
+
81
+ def finish
82
+ raise "Implement #{self.class}#finish to end this event (#{inspect})"
83
+ end
84
+ end
85
+
86
+ module GraphQLSuffixNames
87
+ PARSE_NAME = "parse.graphql"
88
+ LEX_NAME = "lex.graphql"
89
+ VALIDATE_NAME = "validate.graphql"
90
+ EXECUTE_NAME = "execute.graphql"
91
+ ANALYZE_NAME = "analyze.graphql"
92
+
93
+ def platform_field_key(field)
94
+ "#{field.path}.graphql"
95
+ end
96
+
97
+ def platform_authorized_key(type)
98
+ "#{type.graphql_name}.authorized.graphql"
99
+ end
100
+
101
+ def platform_resolve_type_key(type)
102
+ "#{type.graphql_name}.resolve_type.graphql"
103
+ end
104
+
105
+ def platform_source_class_key(source_class)
106
+ "#{source_class.name.gsub("::", "_")}.fetch.graphql"
107
+ end
108
+ end
109
+
110
+ module GraphQLPrefixNames
111
+ PARSE_NAME = "graphql.parse"
112
+ LEX_NAME = "graphql.lex"
113
+ VALIDATE_NAME = "graphql.validate"
114
+ EXECUTE_NAME = "graphql.execute"
115
+ ANALYZE_NAME = "graphql.analyze"
116
+
117
+ def platform_field_key(field)
118
+ "graphql.#{field.path}"
119
+ end
120
+
121
+ def platform_authorized_key(type)
122
+ "graphql.authorized.#{type.graphql_name}"
123
+ end
124
+
125
+ def platform_resolve_type_key(type)
126
+ "graphql.resolve_type.#{type.graphql_name}"
127
+ end
128
+
129
+ def platform_source_class_key(source_class)
130
+ "graphql.fetch.#{source_class.name.gsub("::", "_")}"
131
+ end
132
+ end
133
+ end
134
+
135
+ def self.create_module(monitor_name)
136
+ if !monitor_name.match?(/[a-z]+/)
137
+ raise ArgumentError, "monitor name must be [a-z]+, not: #{monitor_name.inspect}"
138
+ end
139
+
140
+ trace_module = Module.new
141
+ code = MODULE_TEMPLATE % {
142
+ monitor: monitor_name,
143
+ monitor_class: monitor_name.capitalize + "Monitor",
144
+ }
145
+ trace_module.module_eval(code, __FILE__, __LINE__ + 5) # rubocop:disable Development/NoEvalCop This is build-time with a validated string
146
+ trace_module
147
+ end
148
+
149
+ MODULE_TEMPLATE = <<~RUBY
150
+ # @param set_transaction_name [Boolean] If `true`, use the GraphQL operation name as the request name on the monitoring platform
151
+ # @param trace_scalars [Boolean] If `true`, leaf fields will be traced too (Scalars _and_ Enums)
152
+ # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
153
+ # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
154
+ def initialize(...)
155
+ setup_%{monitor}_monitor(...)
156
+ super
157
+ end
158
+
159
+ def setup_%{monitor}_monitor(trace_scalars: false, trace_authorized: true, trace_resolve_type: true, set_transaction_name: false, **kwargs)
160
+ @trace_scalars = trace_scalars
161
+ @trace_authorized = trace_authorized
162
+ @trace_resolve_type = trace_resolve_type
163
+ @set_transaction_name = set_transaction_name
164
+ @%{monitor} = %{monitor_class}.new(trace: self, set_transaction_name: @set_transaction_name, **kwargs)
165
+ end
166
+
167
+ def parse(query_string:)
168
+ @%{monitor}.instrument(:parse, query_string) do
169
+ super
170
+ end
171
+ end
172
+
173
+ def lex(query_string:)
174
+ @%{monitor}.instrument(:lex, query_string) do
175
+ super
176
+ end
177
+ end
178
+
179
+ def validate(query:, validate:)
180
+ @%{monitor}.instrument(:validate, query) do
181
+ super
182
+ end
183
+ end
184
+
185
+ def begin_analyze_multiplex(multiplex, analyzers)
186
+ begin_%{monitor}_event(:analyze, nil)
187
+ super
188
+ end
189
+
190
+ def end_analyze_multiplex(multiplex, analyzers)
191
+ finish_%{monitor}_event
192
+ super
193
+ end
194
+
195
+ def execute_multiplex(multiplex:)
196
+ @%{monitor}.instrument(:execute, multiplex) do
197
+ super
198
+ end
199
+ end
200
+
201
+ def begin_execute_field(field, object, arguments, query)
202
+ return_type = field.type.unwrap
203
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
204
+ (field.trace.nil? && @trace_scalars) || field.trace
205
+ else
206
+ true
207
+ end
208
+
209
+ if trace_field
210
+ begin_%{monitor}_event(:execute_field, field)
211
+ end
212
+ super
213
+ end
214
+
215
+ def end_execute_field(field, object, arguments, query, result)
216
+ finish_%{monitor}_event
217
+ super
218
+ end
219
+
220
+ def dataloader_fiber_yield(source)
221
+ Fiber[PREVIOUS_EV_KEY] = finish_%{monitor}_event
222
+ super
223
+ end
224
+
225
+ def dataloader_fiber_resume(source)
226
+ prev_ev = Fiber[PREVIOUS_EV_KEY]
227
+ if prev_ev
228
+ begin_%{monitor}_event(prev_ev.keyword, prev_ev.object)
229
+ end
230
+ super
231
+ end
232
+
233
+ def begin_authorized(type, object, context)
234
+ @trace_authorized && begin_%{monitor}_event(:authorized, type)
235
+ super
236
+ end
237
+
238
+ def end_authorized(type, object, context, result)
239
+ finish_%{monitor}_event
240
+ super
241
+ end
242
+
243
+ def begin_resolve_type(type, value, context)
244
+ @trace_resolve_type && begin_%{monitor}_event(:resolve_type, type)
245
+ super
246
+ end
247
+
248
+ def end_resolve_type(type, value, context, resolved_type)
249
+ finish_%{monitor}_event
250
+ super
251
+ end
252
+
253
+ def begin_dataloader_source(source)
254
+ begin_%{monitor}_event(:dataloader_source, source)
255
+ super
256
+ end
257
+
258
+ def end_dataloader_source(source)
259
+ finish_%{monitor}_event
260
+ super
261
+ end
262
+
263
+ CURRENT_EV_KEY = :__graphql_%{monitor}_trace_event
264
+ PREVIOUS_EV_KEY = :__graphql_%{monitor}_trace_previous_event
265
+
266
+ private
267
+
268
+ def begin_%{monitor}_event(keyword, object)
269
+ Fiber[CURRENT_EV_KEY] = @%{monitor}.start_event(keyword, object)
270
+ end
271
+
272
+ def finish_%{monitor}_event
273
+ if ev = Fiber[CURRENT_EV_KEY]
274
+ ev.finish
275
+ # Use `false` to prevent grabbing an event from a parent fiber
276
+ Fiber[CURRENT_EV_KEY] = false
277
+ ev
278
+ end
279
+ end
280
+ RUBY
281
+ end
282
+ end
283
+ end