graphql 2.5.18 → 2.5.19
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/generators/graphql/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/graphql/dashboard.rb +1 -1
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +20 -5
- data/lib/graphql/tracing/perfetto_trace.rb +47 -1
- data/lib/graphql/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a94d75c592000805bf9fd18507343c0b3428f1cf1d9a1828edc921828fd2735
|
|
4
|
+
data.tar.gz: 2984583eb702965d43e45f58f1ac29b6a782b8ecf02b4ee1953b5c5bbb6ec872
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 743bd603f22bb38bdff984c75b37eea32d460aeafd50617fc52dc9c4b93fe1d3265df196b728432a7b2d6494a6ba20c47b658ee84b387377037ea173844c654b
|
|
7
|
+
data.tar.gz: 12354210f6208443a5ec0dc931606440de29bfe0bdb7c6468e7faf9bc013a67671eab62fcc3bd88405d1ffb840a5c0e04be337d9a6707d511a2046d315722f63
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rails/generators/active_record'
|
|
3
|
+
|
|
4
|
+
module Graphql
|
|
5
|
+
module Generators
|
|
6
|
+
class DetailedTraceGenerator < ::Rails::Generators::Base
|
|
7
|
+
include ::Rails::Generators::Migration
|
|
8
|
+
desc "Install GraphQL::Tracing::DetailedTrace for your schema"
|
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
10
|
+
|
|
11
|
+
class_option :redis,
|
|
12
|
+
type: :boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
desc: "Use Redis for persistence instead of ActiveRecord"
|
|
15
|
+
|
|
16
|
+
def self.next_migration_number(dirname)
|
|
17
|
+
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def install_detailed_traces
|
|
21
|
+
|
|
22
|
+
schema_glob = File.expand_path("app/graphql/*_schema.rb", destination_root)
|
|
23
|
+
schema_file = Dir.glob(schema_glob).first
|
|
24
|
+
if !schema_file
|
|
25
|
+
raise ArgumentError, "Failed to find schema definition file (checked: #{schema_glob.inspect})"
|
|
26
|
+
end
|
|
27
|
+
schema_file_match = /( *)class ([A-Za-z:]+) < GraphQL::Schema/.match(File.read(schema_file))
|
|
28
|
+
schema_name = schema_file_match[2]
|
|
29
|
+
indent = schema_file_match[1] + " "
|
|
30
|
+
|
|
31
|
+
if !options.redis?
|
|
32
|
+
migration_template 'create_graphql_detailed_traces.erb', 'db/migrate/create_graphql_detailed_traces.rb'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
log :add_detailed_traces_plugin
|
|
36
|
+
sentinel = /< GraphQL::Schema\s*\n/m
|
|
37
|
+
code = <<-RUBY
|
|
38
|
+
#{indent}use GraphQL::Tracing::DetailedTrace#{options.redis? ? ", redis: raise(\"TODO: pass a connection to a persistent redis database\")" : ""}, limit: 50
|
|
39
|
+
|
|
40
|
+
#{indent}# When this returns true, DetailedTrace will trace the query
|
|
41
|
+
#{indent}# Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
|
|
42
|
+
#{indent}# Could call out to Flipper, etc
|
|
43
|
+
#{indent}def self.detailed_trace?(query)
|
|
44
|
+
#{indent} rand <= 0.000_1 # one in ten thousand
|
|
45
|
+
#{indent}end
|
|
46
|
+
|
|
47
|
+
RUBY
|
|
48
|
+
|
|
49
|
+
in_root do
|
|
50
|
+
inject_into_file schema_file, code, after: sentinel, force: false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
routes_source = File.read(File.expand_path("config/routes.rb", destination_root))
|
|
54
|
+
already_has_dashboard = routes_source.include?("GraphQL::Dashboard") ||
|
|
55
|
+
routes_source.include?("Schema.dashboard") ||
|
|
56
|
+
routes_source.include?("GraphQL::Pro::Routes::Lazy")
|
|
57
|
+
|
|
58
|
+
if (!already_has_dashboard || behavior == :revoke)
|
|
59
|
+
log :route, "GraphQL::Dashboard"
|
|
60
|
+
shell.mute do
|
|
61
|
+
route <<~RUBY
|
|
62
|
+
# TODO: add authorization to this route and expose it in production
|
|
63
|
+
# See https://graphql-ruby.org/pro/dashboard.html#authorizing-the-dashboard
|
|
64
|
+
if Rails.env.development?
|
|
65
|
+
mount GraphQL::Dashboard, at: "/graphql/dashboard", schema: #{schema_name.inspect}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
RUBY
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
gem("google-protobuf")
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class CreateGraphqlDetailedTraces < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :graphql_detailed_traces, force: true do |t|
|
|
4
|
+
t.bigint :begin_ms, null: false
|
|
5
|
+
t.float :duration_ms, null: false
|
|
6
|
+
t.binary :trace_data, null: false
|
|
7
|
+
t.string :operation_name, null: false
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/graphql/dashboard.rb
CHANGED
|
@@ -120,7 +120,7 @@ module Graphql
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
class StaticsController < ApplicationController
|
|
123
|
-
|
|
123
|
+
skip_forgery_protection
|
|
124
124
|
# Use an explicit list of files to avoid any chance of reading other files from disk
|
|
125
125
|
STATICS = {}
|
|
126
126
|
|
|
@@ -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
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
if defined?(ActiveRecord)
|
|
3
|
+
require "graphql/tracing/detailed_trace/active_record_backend"
|
|
4
|
+
end
|
|
2
5
|
require "graphql/tracing/detailed_trace/memory_backend"
|
|
3
6
|
require "graphql/tracing/detailed_trace/redis_backend"
|
|
4
7
|
|
|
5
8
|
module GraphQL
|
|
6
9
|
module Tracing
|
|
7
|
-
# `DetailedTrace` can make detailed profiles for a subset of production traffic.
|
|
10
|
+
# `DetailedTrace` can make detailed profiles for a subset of production traffic. Install it in Rails with `rails generate graphql:detailed_trace`.
|
|
8
11
|
#
|
|
9
12
|
# When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
|
|
10
13
|
# overriding the one in `context[:trace_mode]`.
|
|
@@ -13,10 +16,19 @@ module GraphQL
|
|
|
13
16
|
# this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations
|
|
14
17
|
# entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`.
|
|
15
18
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
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
|
+
#
|
|
18
27
|
# If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
|
|
19
28
|
#
|
|
29
|
+
# @example Installing with Rails
|
|
30
|
+
# rails generate graphql:detailed_trace # optional: --redis
|
|
31
|
+
#
|
|
20
32
|
# @example Adding the sampler to your schema
|
|
21
33
|
# class MySchema < GraphQL::Schema
|
|
22
34
|
# # Add the sampler:
|
|
@@ -53,13 +65,16 @@ module GraphQL
|
|
|
53
65
|
# @param redis [Redis] If provided, profiles will be stored in Redis for later review
|
|
54
66
|
# @param limit [Integer] A maximum number of profiles to store
|
|
55
67
|
# @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead)
|
|
56
|
-
|
|
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)
|
|
57
70
|
storage = if redis
|
|
58
71
|
RedisBackend.new(redis: redis, limit: limit)
|
|
59
72
|
elsif memory
|
|
60
73
|
MemoryBackend.new(limit: limit)
|
|
74
|
+
elsif defined?(ActiveRecord)
|
|
75
|
+
ActiveRecordBackend.new(limit: limit, model_class: model_class)
|
|
61
76
|
else
|
|
62
|
-
raise ArgumentError, "
|
|
77
|
+
raise ArgumentError, "To store traces, install ActiveRecord or provide `redis: ...`"
|
|
63
78
|
end
|
|
64
79
|
detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug)
|
|
65
80
|
schema.detailed_trace = detailed_trace
|
|
@@ -91,6 +91,21 @@ module GraphQL
|
|
|
91
91
|
ctx_debug
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
+
@arguments_filter = if (ctx = query&.context) && (dtf = ctx[:detailed_trace_filter])
|
|
95
|
+
dtf
|
|
96
|
+
elsif defined?(ActiveSupport::ParameterFilter)
|
|
97
|
+
fp = if defined?(Rails) && Rails.application && (app_config = Rails.application.config.filter_parameters).present? && !app_config.empty?
|
|
98
|
+
app_config
|
|
99
|
+
elsif ActiveSupport.respond_to?(:filter_parameters)
|
|
100
|
+
ActiveSupport.filter_parameters
|
|
101
|
+
else
|
|
102
|
+
EmptyObjects::EMPTY_ARRAY
|
|
103
|
+
end
|
|
104
|
+
ActiveSupport::ParameterFilter.new(fp, mask: ArgumentsFilter::FILTERED)
|
|
105
|
+
else
|
|
106
|
+
ArgumentsFilter.new
|
|
107
|
+
end
|
|
108
|
+
|
|
94
109
|
Fiber[:graphql_flow_stack] = nil
|
|
95
110
|
@sequence_id = object_id
|
|
96
111
|
@pid = Process.pid
|
|
@@ -590,6 +605,22 @@ module GraphQL
|
|
|
590
605
|
Fiber.current.object_id
|
|
591
606
|
end
|
|
592
607
|
|
|
608
|
+
class ArgumentsFilter
|
|
609
|
+
# From Rails defaults
|
|
610
|
+
# https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt#L6-L8
|
|
611
|
+
SENSITIVE_KEY = /passw|token|crypt|email|_key|salt|certificate|secret|ssn|cvv|cvc|otp/i
|
|
612
|
+
FILTERED = "[FILTERED]"
|
|
613
|
+
|
|
614
|
+
def filter_param(key, value)
|
|
615
|
+
if (key.is_a?(String) && SENSITIVE_KEY.match?(key)) ||
|
|
616
|
+
(key.is_a?(Symbol) && SENSITIVE_KEY.match?(key.name))
|
|
617
|
+
FILTERED
|
|
618
|
+
else
|
|
619
|
+
value
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
593
624
|
def debug_annotation(iid, value_key, value)
|
|
594
625
|
if iid
|
|
595
626
|
DebugAnnotation.new(name_iid: iid, value_key => value)
|
|
@@ -634,7 +665,22 @@ module GraphQL
|
|
|
634
665
|
when Array
|
|
635
666
|
debug_annotation(iid, :array_values, v.each_with_index.map { |v2, idx| payload_to_debug((k ? "#{k}.#{idx}" : String(idx)), v2, intern_value: intern_value) }.compact)
|
|
636
667
|
when Hash
|
|
637
|
-
|
|
668
|
+
debug_v = v.map { |k2, v2|
|
|
669
|
+
debug_k = case k2
|
|
670
|
+
when String
|
|
671
|
+
k2
|
|
672
|
+
when Symbol
|
|
673
|
+
k2.name
|
|
674
|
+
else
|
|
675
|
+
String(k2)
|
|
676
|
+
end
|
|
677
|
+
filtered_v2 = @arguments_filter.filter_param(debug_k, v2)
|
|
678
|
+
payload_to_debug(debug_k, filtered_v2, intern_value: intern_value)
|
|
679
|
+
}
|
|
680
|
+
debug_v.compact!
|
|
681
|
+
debug_annotation(iid, :dict_entries, debug_v)
|
|
682
|
+
when GraphQL::Schema::InputObject
|
|
683
|
+
payload_to_debug(k, v.to_h, iid: iid, intern_value: intern_value)
|
|
638
684
|
else
|
|
639
685
|
class_name_iid = @interned_da_string_values[v.class.name]
|
|
640
686
|
da = [
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.5.
|
|
4
|
+
version: 2.5.19
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Mosolgo
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-02-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|
|
@@ -383,6 +383,7 @@ files:
|
|
|
383
383
|
- ".yardopts"
|
|
384
384
|
- MIT-LICENSE
|
|
385
385
|
- lib/generators/graphql/core.rb
|
|
386
|
+
- lib/generators/graphql/detailed_trace_generator.rb
|
|
386
387
|
- lib/generators/graphql/enum_generator.rb
|
|
387
388
|
- lib/generators/graphql/field_extractor.rb
|
|
388
389
|
- lib/generators/graphql/input_generator.rb
|
|
@@ -412,6 +413,7 @@ files:
|
|
|
412
413
|
- lib/generators/graphql/templates/base_resolver.erb
|
|
413
414
|
- lib/generators/graphql/templates/base_scalar.erb
|
|
414
415
|
- lib/generators/graphql/templates/base_union.erb
|
|
416
|
+
- lib/generators/graphql/templates/create_graphql_detailed_traces.erb
|
|
415
417
|
- lib/generators/graphql/templates/enum.erb
|
|
416
418
|
- lib/generators/graphql/templates/graphql_controller.erb
|
|
417
419
|
- lib/generators/graphql/templates/input.erb
|
|
@@ -741,6 +743,7 @@ files:
|
|
|
741
743
|
- lib/graphql/tracing/data_dog_trace.rb
|
|
742
744
|
- lib/graphql/tracing/data_dog_tracing.rb
|
|
743
745
|
- lib/graphql/tracing/detailed_trace.rb
|
|
746
|
+
- lib/graphql/tracing/detailed_trace/active_record_backend.rb
|
|
744
747
|
- lib/graphql/tracing/detailed_trace/memory_backend.rb
|
|
745
748
|
- lib/graphql/tracing/detailed_trace/redis_backend.rb
|
|
746
749
|
- lib/graphql/tracing/legacy_hooks_trace.rb
|