graphql 2.4.10 → 2.4.11
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.
- checksums.yaml +4 -4
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
- data/lib/graphql/dashboard/statics/dashboard.css +3 -0
- data/lib/graphql/dashboard/statics/dashboard.js +78 -0
- data/lib/graphql/dashboard/statics/header-icon.png +0 -0
- data/lib/graphql/dashboard/statics/icon.png +0 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +63 -0
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +60 -0
- data/lib/graphql/dashboard.rb +142 -0
- data/lib/graphql/execution/interpreter/runtime.rb +1 -1
- data/lib/graphql/invalid_name_error.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +6 -12
- data/lib/graphql/schema/build_from_definition.rb +0 -1
- data/lib/graphql/schema/enum.rb +16 -1
- data/lib/graphql/schema/input_object.rb +1 -1
- data/lib/graphql/schema/loader.rb +0 -1
- data/lib/graphql/schema/member/has_dataloader.rb +4 -0
- data/lib/graphql/schema.rb +48 -6
- data/lib/graphql/tracing/active_support_notifications_trace.rb +6 -2
- data/lib/graphql/tracing/appoptics_trace.rb +2 -0
- data/lib/graphql/tracing/appsignal_trace.rb +6 -0
- data/lib/graphql/tracing/data_dog_trace.rb +5 -0
- data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
- data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
- data/lib/graphql/tracing/detailed_trace.rb +93 -0
- data/lib/graphql/tracing/new_relic_trace.rb +9 -0
- data/lib/graphql/tracing/perfetto_trace.rb +13 -2
- data/lib/graphql/tracing/prometheus_trace.rb +22 -0
- data/lib/graphql/tracing/scout_trace.rb +6 -0
- data/lib/graphql/tracing/sentry_trace.rb +5 -0
- data/lib/graphql/tracing/statsd_trace.rb +9 -0
- data/lib/graphql/tracing.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -0
- metadata +15 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
# Raised automatically when a field's resolve function returns `nil`
|
|
4
4
|
# for a non-null field.
|
|
5
|
-
class InvalidNullError < GraphQL::
|
|
5
|
+
class InvalidNullError < GraphQL::Error
|
|
6
6
|
# @return [GraphQL::BaseType] The owner of {#field}
|
|
7
7
|
attr_reader :parent_type
|
|
8
8
|
|
|
@@ -12,23 +12,17 @@ module GraphQL
|
|
|
12
12
|
# @return [nil, GraphQL::ExecutionError] The invalid value for this field
|
|
13
13
|
attr_reader :value
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
# @return [GraphQL::Language::Nodes::Field] the field where the error occurred
|
|
16
|
+
attr_reader :ast_node
|
|
17
|
+
|
|
18
|
+
def initialize(parent_type, field, value, ast_node)
|
|
16
19
|
@parent_type = parent_type
|
|
17
20
|
@field = field
|
|
18
21
|
@value = value
|
|
22
|
+
@ast_node = ast_node
|
|
19
23
|
super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}")
|
|
20
24
|
end
|
|
21
25
|
|
|
22
|
-
# @return [Hash] An entry for the response's "errors" key
|
|
23
|
-
def to_h
|
|
24
|
-
{ "message" => message }
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# @deprecated always false
|
|
28
|
-
def parent_error?
|
|
29
|
-
false
|
|
30
|
-
end
|
|
31
|
-
|
|
32
26
|
class << self
|
|
33
27
|
attr_accessor :parent_class
|
|
34
28
|
|
|
@@ -298,7 +298,6 @@ module GraphQL
|
|
|
298
298
|
description: enum_value_definition.description,
|
|
299
299
|
directives: builder.prepare_directives(enum_value_definition, type_resolver),
|
|
300
300
|
ast_node: enum_value_definition,
|
|
301
|
-
value_method: GraphQL::Schema::Enum.respond_to?(enum_value_definition.name.downcase) ? false : nil,
|
|
302
301
|
)
|
|
303
302
|
end
|
|
304
303
|
end
|
data/lib/graphql/schema/enum.rb
CHANGED
|
@@ -70,7 +70,9 @@ module GraphQL
|
|
|
70
70
|
kwargs[:owner] = self
|
|
71
71
|
value = enum_value_class.new(*args, **kwargs, &block)
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
if value_method || (value_methods && value_method != false)
|
|
74
|
+
generate_value_method(value, value_method)
|
|
75
|
+
end
|
|
74
76
|
|
|
75
77
|
key = value.graphql_name
|
|
76
78
|
prev_value = own_values[key]
|
|
@@ -159,6 +161,18 @@ module GraphQL
|
|
|
159
161
|
end
|
|
160
162
|
end
|
|
161
163
|
|
|
164
|
+
def value_methods(new_value = NOT_CONFIGURED)
|
|
165
|
+
if NOT_CONFIGURED.equal?(new_value)
|
|
166
|
+
if @value_methods != nil
|
|
167
|
+
@value_methods
|
|
168
|
+
else
|
|
169
|
+
find_inherited_value(:value_methods, false)
|
|
170
|
+
end
|
|
171
|
+
else
|
|
172
|
+
@value_methods = new_value
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
162
176
|
def kind
|
|
163
177
|
GraphQL::TypeKinds::ENUM
|
|
164
178
|
end
|
|
@@ -220,6 +234,7 @@ module GraphQL
|
|
|
220
234
|
# because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
|
|
221
235
|
child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
|
|
222
236
|
end
|
|
237
|
+
child_class.class_eval { @value_methods = nil }
|
|
223
238
|
super
|
|
224
239
|
end
|
|
225
240
|
|
|
@@ -23,6 +23,10 @@ module GraphQL
|
|
|
23
23
|
# @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
|
|
24
24
|
# @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
|
|
25
25
|
# @return [ActiveRecord::Base, nil]
|
|
26
|
+
# @example Finding a record by ID
|
|
27
|
+
# dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded
|
|
28
|
+
# @example Finding a record by another attribute
|
|
29
|
+
# dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded
|
|
26
30
|
def dataload_record(model, find_by_value, find_by: nil)
|
|
27
31
|
source = if find_by
|
|
28
32
|
dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -1298,7 +1298,10 @@ module GraphQL
|
|
|
1298
1298
|
def type_error(type_error, ctx)
|
|
1299
1299
|
case type_error
|
|
1300
1300
|
when GraphQL::InvalidNullError
|
|
1301
|
-
|
|
1301
|
+
execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
|
|
1302
|
+
execution_error.path = ctx[:current_path]
|
|
1303
|
+
|
|
1304
|
+
ctx.errors << execution_error
|
|
1302
1305
|
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
|
1303
1306
|
raise type_error
|
|
1304
1307
|
when GraphQL::IntegerDecodingError
|
|
@@ -1366,6 +1369,16 @@ module GraphQL
|
|
|
1366
1369
|
}.freeze
|
|
1367
1370
|
end
|
|
1368
1371
|
|
|
1372
|
+
# @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
|
|
1373
|
+
attr_accessor :detailed_trace
|
|
1374
|
+
|
|
1375
|
+
# @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
|
|
1376
|
+
# @return [Boolean] When `true`, save a detailed trace for this query.
|
|
1377
|
+
# @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
|
|
1378
|
+
def detailed_trace?(query)
|
|
1379
|
+
raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
|
|
1380
|
+
end
|
|
1381
|
+
|
|
1369
1382
|
def tracer(new_tracer, silence_deprecation_warning: false)
|
|
1370
1383
|
if !silence_deprecation_warning
|
|
1371
1384
|
warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
|
|
@@ -1383,14 +1396,22 @@ module GraphQL
|
|
|
1383
1396
|
find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
|
|
1384
1397
|
end
|
|
1385
1398
|
|
|
1386
|
-
# Mix `trace_mod` into this schema's `Trace` class so that its methods
|
|
1387
|
-
#
|
|
1399
|
+
# Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
|
|
1400
|
+
#
|
|
1401
|
+
# You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
|
|
1402
|
+
# it will only run for queries with a matching `context[:trace_mode]`.
|
|
1403
|
+
#
|
|
1404
|
+
# Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
|
|
1405
|
+
#
|
|
1406
|
+
# @example Adding a trace in a special mode
|
|
1407
|
+
# # only runs when `query.context[:trace_mode]` is `:special`
|
|
1408
|
+
# trace_with SpecialTrace, mode: :special
|
|
1388
1409
|
#
|
|
1389
1410
|
# @param trace_mod [Module] A module that implements tracing methods
|
|
1390
1411
|
# @param mode [Symbol] Trace module will only be used for this trade mode
|
|
1391
1412
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1392
1413
|
# @return [void]
|
|
1393
|
-
# @see GraphQL::Tracing::Trace for available tracing methods
|
|
1414
|
+
# @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
|
|
1394
1415
|
def trace_with(trace_mod, mode: :default, **options)
|
|
1395
1416
|
if mode.is_a?(Array)
|
|
1396
1417
|
mode.each { |m| trace_with(trace_mod, mode: m, **options) }
|
|
@@ -1440,12 +1461,33 @@ module GraphQL
|
|
|
1440
1461
|
#
|
|
1441
1462
|
# If no `mode:` is given, then {default_trace_mode} will be used.
|
|
1442
1463
|
#
|
|
1464
|
+
# If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
|
|
1465
|
+
# DetailedTrace's mode will override the passed-in `mode`.
|
|
1466
|
+
#
|
|
1443
1467
|
# @param mode [Symbol] Trace modules for this trade mode will be included
|
|
1444
1468
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1445
1469
|
# @return [Tracing::Trace]
|
|
1446
1470
|
def new_trace(mode: nil, **options)
|
|
1447
|
-
|
|
1448
|
-
|
|
1471
|
+
should_sample = if detailed_trace
|
|
1472
|
+
if (query = options[:query])
|
|
1473
|
+
detailed_trace?(query)
|
|
1474
|
+
elsif (multiplex = options[:multiplex])
|
|
1475
|
+
if multiplex.queries.length == 1
|
|
1476
|
+
detailed_trace?(multiplex.queries.first)
|
|
1477
|
+
else
|
|
1478
|
+
detailed_trace?(multiplex)
|
|
1479
|
+
end
|
|
1480
|
+
end
|
|
1481
|
+
else
|
|
1482
|
+
false
|
|
1483
|
+
end
|
|
1484
|
+
|
|
1485
|
+
if should_sample
|
|
1486
|
+
mode = detailed_trace.trace_mode
|
|
1487
|
+
else
|
|
1488
|
+
target = options[:query] || options[:multiplex]
|
|
1489
|
+
mode ||= target && target.context[:trace_mode]
|
|
1490
|
+
end
|
|
1449
1491
|
|
|
1450
1492
|
trace_mode = mode || default_trace_mode
|
|
1451
1493
|
base_trace_options = trace_options_for(trace_mode)
|
|
@@ -4,8 +4,12 @@ require "graphql/tracing/notifications_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
-
# This implementation forwards events to ActiveSupport::Notifications
|
|
8
|
-
#
|
|
7
|
+
# This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix.
|
|
8
|
+
#
|
|
9
|
+
# @example Sending execution events to ActiveSupport::Notifications
|
|
10
|
+
# class MySchema < GraphQL::Schema
|
|
11
|
+
# trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
|
|
12
|
+
# end
|
|
9
13
|
module ActiveSupportNotificationsTrace
|
|
10
14
|
include NotificationsTrace
|
|
11
15
|
def initialize(engine: ActiveSupport::Notifications, **rest)
|
|
@@ -22,10 +22,12 @@ module GraphQL
|
|
|
22
22
|
# These GraphQL events will show up as 'graphql.execute' spans
|
|
23
23
|
EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
|
|
24
24
|
|
|
25
|
+
|
|
25
26
|
# During auto-instrumentation this version of AppOpticsTracing is compared
|
|
26
27
|
# with the version provided in the appoptics_apm gem, so that the newer
|
|
27
28
|
# version of the class can be used
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
def self.version
|
|
30
32
|
Gem::Version.new('1.0.0')
|
|
31
33
|
end
|
|
@@ -4,6 +4,12 @@ require "graphql/tracing/platform_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
+
# Instrumentation for reporting GraphQL-Ruby times to Appsignal.
|
|
8
|
+
#
|
|
9
|
+
# @example Installing the tracer
|
|
10
|
+
# class MySchema < GraphQL::Schema
|
|
11
|
+
# trace_with GraphQL::Tracing::AppsignalTrace
|
|
12
|
+
# end
|
|
7
13
|
module AppsignalTrace
|
|
8
14
|
include PlatformTrace
|
|
9
15
|
|
|
@@ -4,6 +4,11 @@ require "graphql/tracing/platform_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
+
# A tracer for reporting to DataDog
|
|
8
|
+
# @example Adding this tracer to your schema
|
|
9
|
+
# class MySchema < GraphQL::Schema
|
|
10
|
+
# trace_with GraphQL::Tracing::DataDogTrace
|
|
11
|
+
# end
|
|
7
12
|
module DataDogTrace
|
|
8
13
|
# @param tracer [#trace] Deprecated
|
|
9
14
|
# @param analytics_enabled [Boolean] Deprecated
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Tracing
|
|
5
|
+
class DetailedTrace
|
|
6
|
+
# An in-memory trace storage backend. Suitable for testing and development only.
|
|
7
|
+
# It won't work for multi-process deployments and everything is erased when the app is restarted.
|
|
8
|
+
class MemoryBackend
|
|
9
|
+
def initialize(limit: nil)
|
|
10
|
+
@limit = limit
|
|
11
|
+
@traces = {}
|
|
12
|
+
@next_id = 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def traces(last:, before:)
|
|
16
|
+
page = []
|
|
17
|
+
@traces.values.reverse_each do |trace|
|
|
18
|
+
if page.size == last
|
|
19
|
+
break
|
|
20
|
+
elsif before.nil? || trace.begin_ms < before
|
|
21
|
+
page << trace
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
page
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_trace(id)
|
|
28
|
+
@traces[id]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delete_trace(id)
|
|
32
|
+
@traces.delete(id.to_i)
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def delete_all_traces
|
|
37
|
+
@traces.clear
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def save_trace(operation_name, duration, begin_ms, trace_data)
|
|
42
|
+
id = @next_id
|
|
43
|
+
@next_id += 1
|
|
44
|
+
@traces[id] = DetailedTrace::StoredTrace.new(
|
|
45
|
+
id: id,
|
|
46
|
+
operation_name: operation_name,
|
|
47
|
+
duration_ms: duration,
|
|
48
|
+
begin_ms: begin_ms,
|
|
49
|
+
trace_data: trace_data
|
|
50
|
+
)
|
|
51
|
+
if @limit && @traces.size > @limit
|
|
52
|
+
del_keys = @traces.keys[0...-@limit]
|
|
53
|
+
del_keys.each { |k| @traces.delete(k) }
|
|
54
|
+
end
|
|
55
|
+
id
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Tracing
|
|
5
|
+
class DetailedTrace
|
|
6
|
+
class RedisBackend
|
|
7
|
+
KEY_PREFIX = "gql:trace:"
|
|
8
|
+
def initialize(redis:, limit: nil)
|
|
9
|
+
@redis = redis
|
|
10
|
+
@key = KEY_PREFIX + "traces"
|
|
11
|
+
@remrangebyrank_limit = limit ? -limit - 1 : nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def traces(last:, before:)
|
|
15
|
+
before = case before
|
|
16
|
+
when Numeric
|
|
17
|
+
"(#{before}"
|
|
18
|
+
when nil
|
|
19
|
+
"+inf"
|
|
20
|
+
end
|
|
21
|
+
str_pairs = @redis.zrange(@key, before, 0, byscore: true, rev: true, limit: [0, last || 100], withscores: true)
|
|
22
|
+
str_pairs.map do |(str_data, score)|
|
|
23
|
+
entry_to_trace(score, str_data)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def delete_trace(id)
|
|
28
|
+
@redis.zremrangebyscore(@key, id, id)
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def delete_all_traces
|
|
33
|
+
@redis.del(@key)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find_trace(id)
|
|
37
|
+
str_data = @redis.zrange(@key, id, id, byscore: true).first
|
|
38
|
+
if str_data.nil?
|
|
39
|
+
nil
|
|
40
|
+
else
|
|
41
|
+
entry_to_trace(id, str_data)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def save_trace(operation_name, duration_ms, begin_ms, trace_data)
|
|
46
|
+
id = begin_ms
|
|
47
|
+
data = JSON.dump({ "o" => operation_name, "d" => duration_ms, "b" => begin_ms, "t" => Base64.encode64(trace_data) })
|
|
48
|
+
@redis.pipelined do |pipeline|
|
|
49
|
+
pipeline.zadd(@key, id, data)
|
|
50
|
+
if @remrangebyrank_limit
|
|
51
|
+
pipeline.zremrangebyrank(@key, 0, @remrangebyrank_limit)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
id
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def entry_to_trace(id, json_str)
|
|
60
|
+
data = JSON.parse(json_str)
|
|
61
|
+
StoredTrace.new(
|
|
62
|
+
id: id,
|
|
63
|
+
operation_name: data["o"],
|
|
64
|
+
duration_ms: data["d"].to_f,
|
|
65
|
+
begin_ms: data["b"].to_i,
|
|
66
|
+
trace_data: Base64.decode64(data["t"]),
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "graphql/tracing/detailed_trace/memory_backend"
|
|
3
|
+
require "graphql/tracing/detailed_trace/redis_backend"
|
|
4
|
+
|
|
5
|
+
module GraphQL
|
|
6
|
+
module Tracing
|
|
7
|
+
# `DetailedTrace` can make detailed profiles for a subset of production traffic.
|
|
8
|
+
#
|
|
9
|
+
# When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
|
|
10
|
+
# overriding the one in `context[:trace_mode]`.
|
|
11
|
+
#
|
|
12
|
+
# __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
|
|
13
|
+
# You can configure this database to retail all data (persistent) or to expire data according to your rules.
|
|
14
|
+
# If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
|
|
15
|
+
#
|
|
16
|
+
# @example Adding the sampler to your schema
|
|
17
|
+
# class MySchema < GraphQL::Schema
|
|
18
|
+
# # Add the sampler:
|
|
19
|
+
# use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100
|
|
20
|
+
#
|
|
21
|
+
# # And implement this hook to tell it when to take a sample:
|
|
22
|
+
# def self.detailed_trace?(query)
|
|
23
|
+
# # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
|
|
24
|
+
# # Could call out to Flipper, etc
|
|
25
|
+
# rand <= 0.000_1 # one in ten thousand
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
|
|
30
|
+
class DetailedTrace
|
|
31
|
+
# @param redis [Redis] If provided, profiles will be stored in Redis for later review
|
|
32
|
+
# @param limit [Integer] A maximum number of profiles to store
|
|
33
|
+
def self.use(schema, trace_mode: :profile_sample, memory: false, redis: nil, limit: nil)
|
|
34
|
+
storage = if redis
|
|
35
|
+
RedisBackend.new(redis: redis, limit: limit)
|
|
36
|
+
elsif memory
|
|
37
|
+
MemoryBackend.new(limit: limit)
|
|
38
|
+
else
|
|
39
|
+
raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
|
|
40
|
+
end
|
|
41
|
+
schema.detailed_trace = self.new(storage: storage, trace_mode: trace_mode)
|
|
42
|
+
schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize(storage:, trace_mode:)
|
|
46
|
+
@storage = storage
|
|
47
|
+
@trace_mode = trace_mode
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
|
|
51
|
+
attr_reader :trace_mode
|
|
52
|
+
|
|
53
|
+
# @return [String] ID of saved trace
|
|
54
|
+
def save_trace(operation_name, duration_ms, begin_ms, trace_data)
|
|
55
|
+
@storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param last [Integer]
|
|
59
|
+
# @param before [Integer] Timestamp in milliseconds since epoch
|
|
60
|
+
# @return [Enumerable<StoredTrace>]
|
|
61
|
+
def traces(last: nil, before: nil)
|
|
62
|
+
@storage.traces(last: last, before: before)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [StoredTrace, nil]
|
|
66
|
+
def find_trace(id)
|
|
67
|
+
@storage.find_trace(id)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [void]
|
|
71
|
+
def delete_trace(id)
|
|
72
|
+
@storage.delete_trace(id)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [void]
|
|
76
|
+
def delete_all_traces
|
|
77
|
+
@storage.delete_all_traces
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class StoredTrace
|
|
81
|
+
def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
|
|
82
|
+
@id = id
|
|
83
|
+
@operation_name = operation_name
|
|
84
|
+
@duration_ms = duration_ms
|
|
85
|
+
@begin_ms = begin_ms
|
|
86
|
+
@trace_data = trace_data
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
attr_reader :id, :operation_name, :duration_ms, :begin_ms, :trace_data
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -4,6 +4,15 @@ require "graphql/tracing/platform_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
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
|
|
7
16
|
module NewRelicTrace
|
|
8
17
|
# @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
|
|
9
18
|
# This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
|
|
@@ -61,8 +61,10 @@ module GraphQL
|
|
|
61
61
|
DA_STR_VAL_NIL_IID = 14
|
|
62
62
|
|
|
63
63
|
# @param active_support_notifications_pattern [String, RegExp, false] A filter for `ActiveSupport::Notifications`, if it's present. Or `false` to skip subscribing.
|
|
64
|
-
def initialize(active_support_notifications_pattern: nil, **_rest)
|
|
64
|
+
def initialize(active_support_notifications_pattern: nil, save_profile: false, **_rest)
|
|
65
65
|
super
|
|
66
|
+
@save_profile = save_profile
|
|
67
|
+
Fiber[:graphql_flow_stack] = nil
|
|
66
68
|
@sequence_id = object_id
|
|
67
69
|
@pid = Process.pid
|
|
68
70
|
@flow_ids = Hash.new { |h, source_inst| h[source_inst] = [] }.compare_by_identity
|
|
@@ -108,6 +110,7 @@ module GraphQL
|
|
|
108
110
|
@fibers_counter_id = :fibers_counter.object_id
|
|
109
111
|
@fields_counter_id = :fields_counter.object_id
|
|
110
112
|
@begin_validate = nil
|
|
113
|
+
@begin_time = nil
|
|
111
114
|
@packets = []
|
|
112
115
|
@packets << TracePacket.new(
|
|
113
116
|
track_descriptor: TrackDescriptor.new(
|
|
@@ -172,6 +175,8 @@ module GraphQL
|
|
|
172
175
|
end
|
|
173
176
|
|
|
174
177
|
def begin_execute_multiplex(m)
|
|
178
|
+
@operation_name = m.queries.map { |q| q.selected_operation_name || "anonymous" }.join(",")
|
|
179
|
+
@begin_time = Time.now
|
|
175
180
|
@packets << trace_packet(
|
|
176
181
|
type: TrackEvent::Type::TYPE_SLICE_BEGIN,
|
|
177
182
|
track_uuid: fid,
|
|
@@ -189,6 +194,12 @@ module GraphQL
|
|
|
189
194
|
track_uuid: fid,
|
|
190
195
|
)
|
|
191
196
|
unsubscribe_from_active_support_notifications
|
|
197
|
+
if @save_profile
|
|
198
|
+
begin_ts = (@begin_time.to_f * 1000).round
|
|
199
|
+
end_ts = (Time.now.to_f * 1000).round
|
|
200
|
+
duration_ms = end_ts - begin_ts
|
|
201
|
+
m.schema.detailed_trace.save_trace(@operation_name, duration_ms, begin_ts, Trace.encode(Trace.new(packet: @packets)))
|
|
202
|
+
end
|
|
192
203
|
super
|
|
193
204
|
end
|
|
194
205
|
|
|
@@ -297,7 +308,7 @@ module GraphQL
|
|
|
297
308
|
{
|
|
298
309
|
debug_annotations: [
|
|
299
310
|
@begin_validate.track_event.debug_annotations.first,
|
|
300
|
-
payload_to_debug("valid?",
|
|
311
|
+
payload_to_debug("valid?", validation_errors.empty?)
|
|
301
312
|
]
|
|
302
313
|
}
|
|
303
314
|
)
|
|
@@ -4,6 +4,28 @@ require "graphql/tracing/platform_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
+
# A tracer for reporting GraphQL-Ruby times to Prometheus.
|
|
8
|
+
#
|
|
9
|
+
# The PrometheusExporter server must be run with a custom type collector that extends `GraphQL::Tracing::PrometheusTracing::GraphQLCollector`.
|
|
10
|
+
#
|
|
11
|
+
# @example Adding this trace to your schema
|
|
12
|
+
# require 'prometheus_exporter/client'
|
|
13
|
+
#
|
|
14
|
+
# class MySchema < GraphQL::Schema
|
|
15
|
+
# trace_with GraphQL::Tracing::PrometheusTrace
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @example Running a custom type collector
|
|
19
|
+
# # lib/graphql_collector.rb
|
|
20
|
+
# if defined?(PrometheusExporter::Server)
|
|
21
|
+
# require 'graphql/tracing'
|
|
22
|
+
#
|
|
23
|
+
# class GraphQLCollector < GraphQL::Tracing::PrometheusTrace::GraphQLCollector
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# # Then run:
|
|
28
|
+
# # bundle exec prometheus_exporter -a lib/graphql_collector.rb
|
|
7
29
|
module PrometheusTrace
|
|
8
30
|
if defined?(PrometheusExporter::Server)
|
|
9
31
|
autoload :GraphQLCollector, "graphql/tracing/prometheus_trace/graphql_collector"
|
|
@@ -4,6 +4,12 @@ require "graphql/tracing/platform_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
+
# A tracer for sending GraphQL-Ruby times to Scout
|
|
8
|
+
#
|
|
9
|
+
# @example Adding this tracer to your schema
|
|
10
|
+
# class MySchema < GraphQL::Schema
|
|
11
|
+
# trace_with GraphQL::Tracing::ScoutTrace
|
|
12
|
+
# end
|
|
7
13
|
module ScoutTrace
|
|
8
14
|
include PlatformTrace
|
|
9
15
|
|
|
@@ -4,6 +4,11 @@ require "graphql/tracing/platform_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
+
# A tracer for reporting GraphQL-Ruby times to Sentry.
|
|
8
|
+
# @example Installing the tracer
|
|
9
|
+
# class MySchema < GraphQL::Schema
|
|
10
|
+
# trace_with GraphQL::Tracing::SentryTrace
|
|
11
|
+
# end
|
|
7
12
|
module SentryTrace
|
|
8
13
|
include PlatformTrace
|
|
9
14
|
|
|
@@ -4,6 +4,15 @@ require "graphql/tracing/platform_trace"
|
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
+
# A tracer for reporting GraphQL-Ruby times to Statsd.
|
|
8
|
+
# Passing any Statsd client that implements `.time(name) { ... }` will work.
|
|
9
|
+
#
|
|
10
|
+
# @example Installing this tracer
|
|
11
|
+
# # eg:
|
|
12
|
+
# # $statsd = Statsd.new 'localhost', 9125
|
|
13
|
+
# class MySchema < GraphQL::Schema
|
|
14
|
+
# use GraphQL::Tracing::StatsdTrace, statsd: $statsd
|
|
15
|
+
# end
|
|
7
16
|
module StatsdTrace
|
|
8
17
|
include PlatformTrace
|
|
9
18
|
|
data/lib/graphql/tracing.rb
CHANGED
|
@@ -32,6 +32,7 @@ module GraphQL
|
|
|
32
32
|
autoload :StatsdTrace, "graphql/tracing/statsd_trace"
|
|
33
33
|
autoload :PrometheusTrace, "graphql/tracing/prometheus_trace"
|
|
34
34
|
autoload :PerfettoTrace, "graphql/tracing/perfetto_trace"
|
|
35
|
+
autoload :DetailedTrace, "graphql/tracing/detailed_trace"
|
|
35
36
|
|
|
36
37
|
# Objects may include traceable to gain a `.trace(...)` method.
|
|
37
38
|
# The object must have a `@tracers` ivar of type `Array<<#trace(k, d, &b)>>`.
|
data/lib/graphql/version.rb
CHANGED
data/lib/graphql.rb
CHANGED
|
@@ -125,6 +125,9 @@ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: http
|
|
|
125
125
|
autoload :LoadApplicationObjectFailedError, "graphql/load_application_object_failed_error"
|
|
126
126
|
autoload :Testing, "graphql/testing"
|
|
127
127
|
autoload :Current, "graphql/current"
|
|
128
|
+
if defined?(::Rails::Engine)
|
|
129
|
+
autoload :Dashboard, 'graphql/dashboard'
|
|
130
|
+
end
|
|
128
131
|
end
|
|
129
132
|
|
|
130
133
|
require "graphql/version"
|