graphql 2.0.32 → 2.5.22

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 (308) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +95 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  83. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  84. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  85. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  86. data/lib/graphql/execution/interpreter/runtime.rb +363 -434
  87. data/lib/graphql/execution/interpreter.rb +91 -164
  88. data/lib/graphql/execution/lookahead.rb +105 -31
  89. data/lib/graphql/execution/multiplex.rb +7 -6
  90. data/lib/graphql/execution/next/field_resolve_step.rb +711 -0
  91. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  92. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  93. data/lib/graphql/execution/next/runner.rb +389 -0
  94. data/lib/graphql/execution/next/selections_step.rb +37 -0
  95. data/lib/graphql/execution/next.rb +70 -0
  96. data/lib/graphql/execution.rb +1 -0
  97. data/lib/graphql/execution_error.rb +13 -10
  98. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  99. data/lib/graphql/introspection/directive_type.rb +7 -3
  100. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  101. data/lib/graphql/introspection/entry_points.rb +20 -6
  102. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  103. data/lib/graphql/introspection/field_type.rb +13 -5
  104. data/lib/graphql/introspection/input_value_type.rb +21 -13
  105. data/lib/graphql/introspection/schema_type.rb +8 -11
  106. data/lib/graphql/introspection/type_type.rb +64 -28
  107. data/lib/graphql/invalid_name_error.rb +1 -1
  108. data/lib/graphql/invalid_null_error.rb +26 -17
  109. data/lib/graphql/language/block_string.rb +34 -18
  110. data/lib/graphql/language/cache.rb +13 -0
  111. data/lib/graphql/language/comment.rb +18 -0
  112. data/lib/graphql/language/definition_slice.rb +1 -1
  113. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  114. data/lib/graphql/language/lexer.rb +319 -193
  115. data/lib/graphql/language/nodes.rb +136 -77
  116. data/lib/graphql/language/parser.rb +807 -1985
  117. data/lib/graphql/language/printer.rb +324 -151
  118. data/lib/graphql/language/sanitized_printer.rb +21 -23
  119. data/lib/graphql/language/static_visitor.rb +171 -0
  120. data/lib/graphql/language/visitor.rb +23 -83
  121. data/lib/graphql/language.rb +71 -1
  122. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  123. data/lib/graphql/pagination/array_connection.rb +6 -6
  124. data/lib/graphql/pagination/connection.rb +30 -1
  125. data/lib/graphql/pagination/connections.rb +32 -0
  126. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  127. data/lib/graphql/query/context/scoped_context.rb +101 -0
  128. data/lib/graphql/query/context.rb +82 -144
  129. data/lib/graphql/query/null_context.rb +15 -18
  130. data/lib/graphql/query/partial.rb +179 -0
  131. data/lib/graphql/query/validation_pipeline.rb +4 -4
  132. data/lib/graphql/query/variable_validation_error.rb +1 -1
  133. data/lib/graphql/query/variables.rb +3 -3
  134. data/lib/graphql/query.rb +126 -81
  135. data/lib/graphql/railtie.rb +16 -6
  136. data/lib/graphql/rake_task.rb +3 -12
  137. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  138. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  139. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  140. data/lib/graphql/rubocop.rb +2 -0
  141. data/lib/graphql/schema/addition.rb +26 -13
  142. data/lib/graphql/schema/always_visible.rb +7 -2
  143. data/lib/graphql/schema/argument.rb +75 -9
  144. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  145. data/lib/graphql/schema/build_from_definition.rb +123 -60
  146. data/lib/graphql/schema/directive/flagged.rb +4 -2
  147. data/lib/graphql/schema/directive/one_of.rb +12 -0
  148. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  149. data/lib/graphql/schema/directive.rb +54 -2
  150. data/lib/graphql/schema/enum.rb +110 -27
  151. data/lib/graphql/schema/enum_value.rb +10 -2
  152. data/lib/graphql/schema/field/connection_extension.rb +15 -49
  153. data/lib/graphql/schema/field/scope_extension.rb +23 -7
  154. data/lib/graphql/schema/field.rb +245 -118
  155. data/lib/graphql/schema/field_extension.rb +34 -1
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +116 -60
  158. data/lib/graphql/schema/interface.rb +34 -16
  159. data/lib/graphql/schema/introspection_system.rb +8 -17
  160. data/lib/graphql/schema/late_bound_type.rb +4 -0
  161. data/lib/graphql/schema/list.rb +3 -3
  162. data/lib/graphql/schema/loader.rb +3 -4
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -2
  164. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  165. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  166. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  167. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  168. data/lib/graphql/schema/member/has_directives.rb +4 -4
  169. data/lib/graphql/schema/member/has_fields.rb +115 -15
  170. data/lib/graphql/schema/member/has_interfaces.rb +26 -12
  171. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  172. data/lib/graphql/schema/member/has_validators.rb +1 -1
  173. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  174. data/lib/graphql/schema/member/scoped.rb +19 -0
  175. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  176. data/lib/graphql/schema/member/validates_input.rb +3 -3
  177. data/lib/graphql/schema/member.rb +6 -0
  178. data/lib/graphql/schema/mutation.rb +7 -0
  179. data/lib/graphql/schema/object.rb +34 -8
  180. data/lib/graphql/schema/printer.rb +9 -7
  181. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  182. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  183. data/lib/graphql/schema/resolver.rb +90 -32
  184. data/lib/graphql/schema/scalar.rb +4 -9
  185. data/lib/graphql/schema/subscription.rb +63 -10
  186. data/lib/graphql/schema/timeout.rb +19 -2
  187. data/lib/graphql/schema/type_expression.rb +2 -2
  188. data/lib/graphql/schema/union.rb +2 -2
  189. data/lib/graphql/schema/unique_within_type.rb +1 -1
  190. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  191. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  192. data/lib/graphql/schema/validator.rb +3 -1
  193. data/lib/graphql/schema/visibility/migration.rb +188 -0
  194. data/lib/graphql/schema/visibility/profile.rb +445 -0
  195. data/lib/graphql/schema/visibility/visit.rb +190 -0
  196. data/lib/graphql/schema/visibility.rb +311 -0
  197. data/lib/graphql/schema/warden.rb +275 -103
  198. data/lib/graphql/schema.rb +950 -210
  199. data/lib/graphql/static_validation/all_rules.rb +3 -3
  200. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  201. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  202. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  203. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  204. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  205. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  206. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  207. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  208. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  209. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  210. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  211. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  212. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  213. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  214. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  215. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  216. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  217. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  218. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  219. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  220. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  221. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  222. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  223. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  224. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  225. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  226. data/lib/graphql/static_validation/validation_context.rb +21 -5
  227. data/lib/graphql/static_validation/validator.rb +9 -1
  228. data/lib/graphql/static_validation.rb +0 -1
  229. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  230. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  231. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  232. data/lib/graphql/subscriptions/event.rb +21 -4
  233. data/lib/graphql/subscriptions/serialize.rb +3 -1
  234. data/lib/graphql/subscriptions.rb +21 -17
  235. data/lib/graphql/testing/helpers.rb +161 -0
  236. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  237. data/lib/graphql/testing.rb +3 -0
  238. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  239. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  240. data/lib/graphql/tracing/appoptics_trace.rb +7 -3
  241. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  242. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  243. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  244. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  245. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  246. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  247. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  248. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  249. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  250. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  251. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  252. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  253. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  254. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  255. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  256. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  257. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  258. data/lib/graphql/tracing/null_trace.rb +9 -0
  259. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  260. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  261. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  262. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  263. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  264. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  265. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  266. data/lib/graphql/tracing/scout_trace.rb +32 -58
  267. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  268. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  269. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  270. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  271. data/lib/graphql/tracing/trace.rb +112 -1
  272. data/lib/graphql/tracing.rb +31 -28
  273. data/lib/graphql/type_kinds.rb +2 -1
  274. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  275. data/lib/graphql/types/relay/connection_behaviors.rb +44 -2
  276. data/lib/graphql/types/relay/edge_behaviors.rb +18 -0
  277. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  278. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  279. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  280. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  281. data/lib/graphql/types.rb +18 -10
  282. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  283. data/lib/graphql/unauthorized_error.rb +5 -1
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +71 -54
  286. data/readme.md +12 -2
  287. metadata +233 -37
  288. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  289. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  290. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  291. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  292. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  293. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  294. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  295. data/lib/graphql/analysis/ast.rb +0 -81
  296. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  297. data/lib/graphql/backtrace/trace.rb +0 -96
  298. data/lib/graphql/backtrace/tracer.rb +0 -80
  299. data/lib/graphql/deprecation.rb +0 -9
  300. data/lib/graphql/filter.rb +0 -59
  301. data/lib/graphql/language/parser.y +0 -560
  302. data/lib/graphql/language/token.rb +0 -34
  303. data/lib/graphql/schema/base_64_bp.rb +0 -26
  304. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  305. data/lib/graphql/schema/null_mask.rb +0 -11
  306. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  307. data/lib/graphql/static_validation/type_stack.rb +0 -216
  308. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -1,187 +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
- # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
24
-
25
- {
26
- 'lex' => 'lex.graphql',
27
- 'parse' => 'parse.graphql',
28
- 'validate' => 'validate.graphql',
29
- 'analyze_query' => 'analyze.graphql',
30
- 'analyze_multiplex' => 'analyze.graphql',
31
- 'execute_multiplex' => 'execute.graphql',
32
- 'execute_query' => 'execute.graphql',
33
- 'execute_query_lazy' => 'execute.graphql',
34
- }.each do |trace_method, trace_key|
35
- module_eval <<-RUBY, __FILE__, __LINE__
36
- def #{trace_method}(**data)
37
- @tracer.trace("#{trace_key}", service: @service_name, type: 'custom') do |span|
38
- span.set_tag('component', 'graphql')
39
- span.set_tag('operation', '#{trace_method}')
40
25
 
41
- #{
42
- if trace_method == 'execute_multiplex'
43
- <<-RUBY
44
- operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
26
+ attr_reader :tracer, :service_name
45
27
 
46
- resource = if operations.empty?
47
- first_query = data[:multiplex].queries.first
48
- fallback_transaction_name(first_query && first_query.context)
49
- else
50
- operations
51
- end
52
- span.resource = resource if resource
53
-
54
- # [Deprecated] will be removed in the future
55
- span.set_metric('_dd1.sr.eausr', @analytics_sample_rate) if @analytics_enabled
56
- RUBY
57
- elsif trace_method == 'execute_query'
58
- <<-RUBY
59
- span.set_tag(:selected_operation_name, data[:query].selected_operation_name)
60
- span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type)
61
- span.set_tag(:query_string, data[:query].query_string)
62
- RUBY
63
- end
64
- }
65
- if @has_prepare_span
66
- 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
67
42
  end
68
- super
69
- end
70
- end
71
- RUBY
72
- end
73
-
74
- # rubocop:enable Development/NoEvalCop
43
+ span.resource = resource if resource
75
44
 
76
- def execute_field_span(span_key, query, field, ast_node, arguments, object)
77
- return_type = field.type.unwrap
78
- trace_field = if return_type.kind.scalar? || return_type.kind.enum?
79
- (field.trace.nil? && @trace_scalars) || field.trace
80
- else
81
- true
82
- end
83
- platform_key = if trace_field
84
- @platform_key_cache[DataDogTrace].platform_field_key_cache[field]
85
- else
86
- nil
87
- end
88
- if platform_key && trace_field
89
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
90
- span.set_tag('component', 'graphql')
91
- 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
92
49
 
93
50
  if @has_prepare_span
94
- prepare_span_data = { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }
95
- prepare_span(span_key, prepare_span_data, span)
51
+ @trace.prepare_span(keyword, object, span)
96
52
  end
97
53
  yield
98
54
  end
99
- else
100
- yield
101
- end
102
- end
103
- def execute_field(query:, field:, ast_node:, arguments:, object:)
104
- execute_field_span("execute_field", query, field, ast_node, arguments, object) do
105
- super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
106
- end
107
- end
108
-
109
- def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
110
- execute_field_span("execute_field_lazy", query, field, ast_node, arguments, object) do
111
- super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
112
- end
113
- end
114
-
115
- def authorized(query:, type:, object:)
116
- authorized_span("authorized", object, type, query) do
117
- super(query: query, type: type, object: object)
118
55
  end
119
- end
120
-
121
- def authorized_span(span_key, object, type, query)
122
- platform_key = @platform_key_cache[DataDogTrace].platform_authorized_key_cache[type]
123
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
124
- span.set_tag('component', 'graphql')
125
- span.set_tag('operation', span_key)
126
56
 
127
- if @has_prepare_span
128
- 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')
129
62
  end
130
- yield
131
- end
132
- end
133
-
134
- def authorized_lazy(object:, type:, query:)
135
- authorized_span("authorized_lazy", object, type, query) do
136
- super(query: query, type: type, object: object)
137
- end
138
- end
139
-
140
- def resolve_type(object:, type:, query:)
141
- resolve_type_span("resolve_type", object, type, query) do
142
- super(object: object, query: query, type: type)
143
- end
144
- end
145
63
 
146
- def resolve_type_lazy(object:, type:, query:)
147
- resolve_type_span("resolve_type_lazy", object, type, query) do
148
- super(object: object, query: query, type: type)
149
- end
150
- end
151
-
152
- def resolve_type_span(span_key, object, type, query)
153
- platform_key = @platform_key_cache[DataDogTrace].platform_resolve_type_key_cache[type]
154
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
155
- span.set_tag('component', 'graphql')
156
- span.set_tag('operation', span_key)
157
-
158
- if @has_prepare_span
159
- prepare_span(span_key, {object: object, type: type, query: query}, span)
64
+ def finish
65
+ @dd_span.finish
160
66
  end
161
- yield
162
67
  end
163
68
  end
164
-
165
- include PlatformTrace
166
-
167
- # Implement this method in a subclass to apply custom tags to datadog spans
168
- # @param key [String] The event being traced
169
- # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
170
- # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
171
- # def prepare_span(key, data, span)
172
- # end
173
-
174
- def platform_field_key(field)
175
- field.path
176
- end
177
-
178
- def platform_authorized_key(type)
179
- "#{type.graphql_name}.authorized"
180
- end
181
-
182
- def platform_resolve_type_key(type)
183
- "#{type.graphql_name}.resolve_type"
184
- end
185
69
  end
186
70
  end
187
71
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/platform_tracing"
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
  class DataDogTracing < PlatformTracing
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ class ActiveRecordBackend
7
+ class GraphqlDetailedTrace < ActiveRecord::Base
8
+ end
9
+
10
+ def initialize(limit: nil, model_class: nil)
11
+ @limit = limit
12
+ @model_class = model_class || GraphqlDetailedTrace
13
+ end
14
+
15
+ def traces(last:, before:)
16
+ gdts = @model_class.all.order("begin_ms DESC")
17
+ if before
18
+ gdts = gdts.where("begin_ms < ?", before)
19
+ end
20
+ if last
21
+ gdts = gdts.limit(last)
22
+ end
23
+ gdts.map { |gdt| record_to_stored_trace(gdt) }
24
+ end
25
+
26
+ def delete_trace(id)
27
+ @model_class.where(id: id).destroy_all
28
+ nil
29
+ end
30
+
31
+ def delete_all_traces
32
+ @model_class.all.destroy_all
33
+ end
34
+
35
+ def find_trace(id)
36
+ gdt = @model_class.find_by(id: id)
37
+ if gdt
38
+ record_to_stored_trace(gdt)
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
45
+ gdt = @model_class.create!(
46
+ begin_ms: begin_ms,
47
+ operation_name: operation_name,
48
+ duration_ms: duration_ms,
49
+ trace_data: trace_data,
50
+ )
51
+ if @limit
52
+ @model_class
53
+ .where("id NOT IN(SELECT id FROM graphql_detailed_traces ORDER BY begin_ms DESC LIMIT ?)", @limit)
54
+ .delete_all
55
+ end
56
+ gdt.id
57
+ end
58
+
59
+ private
60
+
61
+ def record_to_stored_trace(gdt)
62
+ StoredTrace.new(
63
+ id: gdt.id,
64
+ begin_ms: gdt.begin_ms,
65
+ operation_name: gdt.operation_name,
66
+ duration_ms: gdt.duration_ms,
67
+ trace_data: gdt.trace_data
68
+ )
69
+
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ # An in-memory trace storage backend. Suitable for testing and development only.
7
+ # It won't work for multi-process deployments and everything is erased when the app is restarted.
8
+ class MemoryBackend
9
+ def initialize(limit: nil)
10
+ @limit = limit
11
+ @traces = {}
12
+ @next_id = 0
13
+ end
14
+
15
+ def traces(last:, before:)
16
+ page = []
17
+ @traces.values.reverse_each do |trace|
18
+ if page.size == last
19
+ break
20
+ elsif before.nil? || trace.begin_ms < before
21
+ page << trace
22
+ end
23
+ end
24
+ page
25
+ end
26
+
27
+ def find_trace(id)
28
+ @traces[id]
29
+ end
30
+
31
+ def delete_trace(id)
32
+ @traces.delete(id.to_i)
33
+ nil
34
+ end
35
+
36
+ def delete_all_traces
37
+ @traces.clear
38
+ nil
39
+ end
40
+
41
+ def save_trace(operation_name, duration, begin_ms, trace_data)
42
+ id = @next_id
43
+ @next_id += 1
44
+ @traces[id] = DetailedTrace::StoredTrace.new(
45
+ id: id,
46
+ operation_name: operation_name,
47
+ duration_ms: duration,
48
+ begin_ms: begin_ms,
49
+ trace_data: trace_data
50
+ )
51
+ if @limit && @traces.size > @limit
52
+ del_keys = @traces.keys[0...-@limit]
53
+ del_keys.each { |k| @traces.delete(k) }
54
+ end
55
+ id
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -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,156 @@
1
+ # frozen_string_literal: true
2
+ if defined?(ActiveRecord)
3
+ require "graphql/tracing/detailed_trace/active_record_backend"
4
+ end
5
+ require "graphql/tracing/detailed_trace/memory_backend"
6
+ require "graphql/tracing/detailed_trace/redis_backend"
7
+
8
+ module GraphQL
9
+ module Tracing
10
+ # `DetailedTrace` can make detailed profiles for a subset of production traffic. Install it in Rails with `rails generate graphql:detailed_trace`.
11
+ #
12
+ # When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
13
+ # overriding the one in `context[:trace_mode]`.
14
+ #
15
+ # By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize
16
+ # this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations
17
+ # entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`.
18
+ #
19
+ # You can store saved traces in two ways:
20
+ #
21
+ # - __ActiveRecord__: With `rails generate graphql:detailed_trace`, a new migration will be added to your app.
22
+ # That table will be used to store trace data.
23
+ #
24
+ # - __Redis__: Pass `redis: ...` to save trace data to a Redis database. Depending on your needs,
25
+ # you can configure this database to retain all data (persistent) or to expire data according to your rules.
26
+ #
27
+ # If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
28
+ #
29
+ # @example Installing with Rails
30
+ # rails generate graphql:detailed_trace # optional: --redis
31
+ #
32
+ # @example Adding the sampler to your schema
33
+ # class MySchema < GraphQL::Schema
34
+ # # Add the sampler:
35
+ # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100
36
+ #
37
+ # # And implement this hook to tell it when to take a sample:
38
+ # def self.detailed_trace?(query)
39
+ # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
40
+ # # Could call out to Flipper, etc
41
+ # rand <= 0.000_1 # one in ten thousand
42
+ # end
43
+ # end
44
+ #
45
+ # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
46
+ #
47
+ # @example Customizing debug output in traces
48
+ # class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace
49
+ # def inspect_object(object)
50
+ # if object.is_a?(SomeThing)
51
+ # # handle it specially ...
52
+ # else
53
+ # super
54
+ # end
55
+ # end
56
+ # end
57
+ #
58
+ # @example disabling debug annotations completely
59
+ # use DetailedTrace, debug: false, ...
60
+ #
61
+ # @example disabling debug annotations for one query
62
+ # MySchema.execute(query_str, context: { detailed_trace_debug: false })
63
+ #
64
+ class DetailedTrace
65
+ # @param redis [Redis] If provided, profiles will be stored in Redis for later review
66
+ # @param limit [Integer] A maximum number of profiles to store
67
+ # @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead)
68
+ # @param model_class [Class<ActiveRecord::Base>] Overrides {ActiveRecordBackend::GraphqlDetailedTrace} if present
69
+ def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil, model_class: nil)
70
+ storage = if redis
71
+ RedisBackend.new(redis: redis, limit: limit)
72
+ elsif memory
73
+ MemoryBackend.new(limit: limit)
74
+ elsif defined?(ActiveRecord)
75
+ ActiveRecordBackend.new(limit: limit, model_class: model_class)
76
+ else
77
+ raise ArgumentError, "To store traces, install ActiveRecord or provide `redis: ...`"
78
+ end
79
+ detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug)
80
+ schema.detailed_trace = detailed_trace
81
+ schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
82
+ end
83
+
84
+ def initialize(storage:, trace_mode:, debug:)
85
+ @storage = storage
86
+ @trace_mode = trace_mode
87
+ @debug = debug
88
+ end
89
+
90
+ # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
91
+ attr_reader :trace_mode
92
+
93
+ # @return [String] ID of saved trace
94
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
95
+ @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
96
+ end
97
+
98
+ # @return [Boolean]
99
+ def debug?
100
+ @debug
101
+ end
102
+
103
+ # @param last [Integer]
104
+ # @param before [Integer] Timestamp in milliseconds since epoch
105
+ # @return [Enumerable<StoredTrace>]
106
+ def traces(last: nil, before: nil)
107
+ @storage.traces(last: last, before: before)
108
+ end
109
+
110
+ # @return [StoredTrace, nil]
111
+ def find_trace(id)
112
+ @storage.find_trace(id)
113
+ end
114
+
115
+ # @return [void]
116
+ def delete_trace(id)
117
+ @storage.delete_trace(id)
118
+ end
119
+
120
+ # @return [void]
121
+ def delete_all_traces
122
+ @storage.delete_all_traces
123
+ end
124
+
125
+ def inspect_object(object)
126
+ self.class.inspect_object(object)
127
+ end
128
+
129
+ def self.inspect_object(object)
130
+ if defined?(ActiveRecord::Relation) && object.is_a?(ActiveRecord::Relation)
131
+ "#{object.class}, .to_sql=#{object.to_sql.inspect}"
132
+ else
133
+ object.inspect
134
+ end
135
+ end
136
+
137
+ # Default debug setting
138
+ # @return [true]
139
+ def self.debug?
140
+ true
141
+ end
142
+
143
+ class StoredTrace
144
+ def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
145
+ @id = id
146
+ @operation_name = operation_name
147
+ @duration_ms = duration_ms
148
+ @begin_ms = begin_ms
149
+ @trace_data = trace_data
150
+ end
151
+
152
+ attr_reader :id, :operation_name, :duration_ms, :begin_ms, :trace_data
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ module LegacyHooksTrace
6
+ def execute_multiplex(multiplex:)
7
+ multiplex_instrumenters = multiplex.schema.instrumenters[:multiplex]
8
+ query_instrumenters = multiplex.schema.instrumenters[:query]
9
+ # First, run multiplex instrumentation, then query instrumentation for each query
10
+ RunHooks.call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
11
+ RunHooks.each_query_call_hooks(query_instrumenters, multiplex.queries) do
12
+ super
13
+ end
14
+ end
15
+ end
16
+
17
+ module RunHooks
18
+ module_function
19
+ # Call the before_ hooks of each query,
20
+ # Then yield if no errors.
21
+ # `call_hooks` takes care of appropriate cleanup.
22
+ def each_query_call_hooks(instrumenters, queries, i = 0)
23
+ if i >= queries.length
24
+ yield
25
+ else
26
+ query = queries[i]
27
+ call_hooks(instrumenters, query, :before_query, :after_query) {
28
+ each_query_call_hooks(instrumenters, queries, i + 1) {
29
+ yield
30
+ }
31
+ }
32
+ end
33
+ end
34
+
35
+ # Call each before hook, and if they all succeed, yield.
36
+ # If they don't all succeed, call after_ for each one that succeeded.
37
+ def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
38
+ begin
39
+ successful = []
40
+ instrumenters.each do |instrumenter|
41
+ instrumenter.public_send(before_hook_name, object)
42
+ successful << instrumenter
43
+ end
44
+
45
+ # if any before hooks raise an exception, quit calling before hooks,
46
+ # but call the after hooks on anything that succeeded but also
47
+ # raise the exception that came from the before hook.
48
+ rescue GraphQL::ExecutionError => err
49
+ object.context.errors << err
50
+ rescue => e
51
+ raise call_after_hooks(successful, object, after_hook_name, e)
52
+ end
53
+
54
+ begin
55
+ yield # Call the user code
56
+ ensure
57
+ ex = call_after_hooks(successful, object, after_hook_name, nil)
58
+ raise ex if ex
59
+ end
60
+ end
61
+
62
+ def call_after_hooks(instrumenters, object, after_hook_name, ex)
63
+ instrumenters.reverse_each do |instrumenter|
64
+ begin
65
+ instrumenter.public_send(after_hook_name, object)
66
+ rescue => e
67
+ ex = e
68
+ end
69
+ end
70
+ ex
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end