Skip to content

Commit 9c869b8

Browse files
committed
Migrate NewRelic
1 parent b30b0ca commit 9c869b8

File tree

5 files changed

+78
-200
lines changed

5 files changed

+78
-200
lines changed

lib/graphql/tracing/new_relic_trace.rb

Lines changed: 36 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -17,163 +17,56 @@ module Tracing
1717
# @example Installing without trace events for `authorized?` or `resolve_type` calls
1818
# trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false
1919
module NewRelicTrace
20-
# @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
21-
# This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
22-
# It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
23-
# @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
24-
# @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
25-
def initialize(set_transaction_name: false, trace_authorized: true, trace_resolve_type: true, **_rest)
26-
@set_transaction_name = set_transaction_name
27-
@trace_authorized = trace_authorized
28-
@trace_resolve_type = trace_resolve_type
29-
@nr_field_names = Hash.new do |h, field|
30-
h[field] = "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
31-
end.compare_by_identity
32-
33-
@nr_authorized_names = Hash.new do |h, type|
34-
h[type] = "GraphQL/Authorized/#{type.graphql_name}"
35-
end.compare_by_identity
36-
37-
@nr_resolve_type_names = Hash.new do |h, type|
38-
h[type] = "GraphQL/ResolveType/#{type.graphql_name}"
39-
end.compare_by_identity
40-
41-
@nr_source_names = Hash.new do |h, source_inst|
42-
h[source_inst] = "GraphQL/Source/#{source_inst.class.name}"
43-
end.compare_by_identity
44-
45-
@nr_parse = @nr_validate = @nr_analyze = @nr_execute = nil
46-
super
47-
end
48-
49-
def parse(query_string:)
50-
@nr_parse = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/parse", category: :web)
51-
super
52-
ensure
53-
@nr_parse.finish
54-
end
55-
56-
def begin_validate(query, validate)
57-
@nr_validate = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/validate", category: :web)
58-
super
59-
end
60-
61-
def end_validate(query, validate, validation_errors)
62-
@nr_validate.finish
63-
super
64-
end
65-
66-
def begin_analyze_multiplex(multiplex, analyzers)
67-
@nr_analyze = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/analyze", category: :web)
68-
super
69-
end
70-
71-
def end_analyze_multiplex(multiplex, analyzers)
72-
@nr_analyze.finish
73-
super
74-
end
75-
76-
def execute_multiplex(multiplex:)
77-
query = multiplex.queries.first
78-
set_this_txn_name = query.context[:set_new_relic_transaction_name]
79-
if set_this_txn_name || (set_this_txn_name.nil? && @set_transaction_name)
80-
NewRelic::Agent.set_transaction_name(transaction_name(query))
20+
include NotificationsTrace
21+
class NewRelicEngine < NotificationsTrace::Engine
22+
PARSE_NAME = "GraphQL/parse"
23+
LEX_NAME = "GraphQL/lex"
24+
VALIDATE_NAME = "GraphQL/validate"
25+
EXECUTE_NAME = "GraphQL/execute"
26+
ANALYZE_NAME = "GraphQL/analyze"
27+
28+
def instrument(keyword, payload, &block)
29+
if keyword == :execute && payload.queries.size == 1
30+
query = payload.queries.first
31+
set_this_txn_name = query.context[:set_new_relic_transaction_name]
32+
if set_this_txn_name || (set_this_txn_name.nil? && @set_transaction_name)
33+
NewRelic::Agent.set_transaction_name(transaction_name(query))
34+
end
35+
end
36+
::NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(name_for(keyword, payload), &block)
8137
end
82-
@nr_execute = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/execute", category: :web)
83-
super
84-
ensure
85-
@nr_execute.finish
86-
end
87-
88-
def begin_execute_field(field, object, arguments, query)
89-
nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_field_names[field], category: :web)
90-
super
91-
end
92-
93-
def end_execute_field(field, objects, arguments, query, result)
94-
nr_segment_stack.pop.finish
95-
super
96-
end
9738

98-
def begin_authorized(type, obj, ctx)
99-
if @trace_authorized
100-
nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_authorized_names[type], category: :web)
39+
def platform_source_class_key(source_class)
40+
"GraphQL/Source/#{source_class.name}"
10141
end
102-
super
103-
end
10442

105-
def end_authorized(type, obj, ctx, is_authed)
106-
if @trace_authorized
107-
nr_segment_stack.pop.finish
43+
def platform_field_key(field)
44+
"GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
10845
end
109-
super
110-
end
11146

112-
def begin_resolve_type(type, value, context)
113-
if @trace_resolve_type
114-
nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_resolve_type_names[type], category: :web)
47+
def platform_authorized_key(type)
48+
"GraphQL/Authorized/#{type.graphql_name}"
11549
end
116-
super
117-
end
11850

119-
def end_resolve_type(type, value, context, resolved_type)
120-
if @trace_resolve_type
121-
nr_segment_stack.pop.finish
51+
def platform_resolve_type_key(type)
52+
"GraphQL/ResolveType/#{type.graphql_name}"
12253
end
123-
super
124-
end
12554

126-
def begin_dataloader(dl)
127-
super
128-
end
55+
class Event < NotificationsTrace::Engine::Event
56+
def start
57+
name = @engine.name_for(keyword, payload)
58+
@nr_ev = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: name, category: :web)
59+
end
12960

130-
def end_dataloader(dl)
131-
super
132-
end
133-
134-
def begin_dataloader_source(source)
135-
nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_source_names[source], category: :web)
136-
super
137-
end
138-
139-
def end_dataloader_source(source)
140-
nr_segment_stack.pop.finish
141-
super
142-
end
143-
144-
def dataloader_fiber_yield(source)
145-
current_segment = nr_segment_stack.last
146-
current_segment.finish
147-
super
148-
end
149-
150-
def dataloader_fiber_resume(source)
151-
prev_segment = nr_segment_stack.pop
152-
seg_partial_name = prev_segment.name.sub(/^.*(GraphQL.*)$/, '\1')
153-
nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: seg_partial_name, category: :web)
154-
super
155-
end
156-
157-
private
158-
159-
def nr_segment_stack
160-
Fiber[:graphql_nr_segment_stack] ||= []
161-
end
162-
163-
def transaction_name(query)
164-
selected_op = query.selected_operation
165-
txn_name = if selected_op
166-
op_type = selected_op.operation_type
167-
op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
168-
"#{op_type}.#{op_name}"
169-
else
170-
"query.anonymous"
61+
def finish
62+
@nr_ev.finish
63+
end
17164
end
172-
"GraphQL/#{txn_name}"
17365
end
17466

175-
def fallback_transaction_name(context)
176-
context[:tracing_fallback_transaction_name]
67+
# @see NotificationsTrace Parent module documents configuration options
68+
def initialize(engine: NewRelicEngine, **_rest)
69+
super
17770
end
17871
end
17972
end

lib/graphql/tracing/notifications_trace.rb

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def initialize(set_transaction_name:)
1414
@platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }.compare_by_identity
1515
@platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }.compare_by_identity
1616
@platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }.compare_by_identity
17-
@platform_source_key_cache = Hash.new { |h, source_cls| h[k] = platform_source_class_key(source_cls) }.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
1818
end
1919

2020
def instrument(keyword, payload, &block)
@@ -45,6 +45,26 @@ def fallback_transaction_name(context)
4545
context[:tracing_fallback_transaction_name]
4646
end
4747

48+
def name_for(keyword, payload)
49+
case keyword
50+
when :execute_field
51+
@platform_field_key_cache[payload]
52+
when :authorized
53+
@platform_authorized_key_cache[payload]
54+
when :resolve_type
55+
@platform_resolve_type_key_cache[payload]
56+
when :dataloader_source
57+
@platform_source_class_key_cache[payload.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+
4868
class Event
4969
def initialize(engine, keyword, payload)
5070
@engine = engine
@@ -97,13 +117,17 @@ def finish
97117
# @param engine [Class<Engine>] The notifications engine to use -- other modules often provide a default here
98118
# @param set_transaction_name [Boolean] If `true`, use the GraphQL operation name as the request name on the monitoring platform
99119
# @param trace_scalars [Boolean] If `true`, leaf fields will be traced too (Scalars _and_ Enums)
100-
def initialize(engine:, set_transaction_name: false, trace_scalars: false, **rest)
120+
# @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
121+
# @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
122+
def initialize(engine:, set_transaction_name: false, trace_scalars: false, trace_authorized: true, trace_resolve_type: true, **rest)
101123
if defined?(Dry::Monitor) && engine == Dry::Monitor
102124
# Backwards compat
103125
engine = DryMonitoringEngine
104126
end
105127

106128
@trace_scalars = trace_scalars
129+
@trace_authorized = trace_authorized
130+
@trace_resolve_type = trace_resolve_type
107131
@notifications_engine = engine.new(set_transaction_name: set_transaction_name)
108132
super
109133
end
@@ -173,7 +197,7 @@ def dataloader_fiber_resume(source)
173197
end
174198

175199
def begin_authorized(type, object, context)
176-
begin_notifications_event(:authorized, type)
200+
@trace_authorized && begin_notifications_event(:authorized, type)
177201
super
178202
end
179203

@@ -183,7 +207,7 @@ def end_authorized(type, object, context, result)
183207
end
184208

185209
def begin_resolve_type(type, value, context)
186-
begin_notifications_event(:resolve_type, type)
210+
@trace_resolve_type && begin_notifications_event(:resolve_type, type)
187211
super
188212
end
189213

@@ -207,23 +231,17 @@ def end_dataloader_source(source)
207231

208232
private
209233

210-
def begin_notifications_event(keyword, payload = nil, set_current: true)
211-
ev = @notifications_engine.start_event(keyword, payload)
212-
if set_current
213-
Fiber[CURRENT_EV_KEY] = ev
214-
end
215-
ev
234+
def begin_notifications_event(keyword, payload = nil)
235+
Fiber[CURRENT_EV_KEY] = @notifications_engine.start_event(keyword, payload)
216236
end
217237

218-
def finish_notifications_event(ev = nil)
219-
finish_ev = ev || Fiber[CURRENT_EV_KEY]
220-
if finish_ev
221-
finish_ev.finish
222-
if ev.nil?
223-
Fiber.current.storage.delete(CURRENT_EV_KEY)
224-
end
238+
def finish_notifications_event
239+
if ev = Fiber[CURRENT_EV_KEY]
240+
ev.finish
241+
# Use `false` to prevent grabbing an event from a parent fiber
242+
Fiber[CURRENT_EV_KEY] = false
243+
ev
225244
end
226-
finish_ev
227245
end
228246
end
229247
end

lib/graphql/tracing/sentry_trace.rb

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,25 +50,12 @@ def instrument(keyword, payload)
5050
end
5151
end
5252

53-
def name_for(keyword, payload)
54-
case keyword
55-
when :execute_field
56-
@platform_field_key_cache[payload]
57-
when :authorized
58-
@platform_authorized_key_cache[payload]
59-
when :resolve_type
60-
@platform_resolve_type_key_cache[payload]
61-
when :dataloader_source
62-
@platform_source_class_key[payload.class]
63-
when :parse then "graphql.parse"
64-
when :lex then "graphql.lex"
65-
when :execute then "graphql.execute"
66-
when :analyze then "graphql.analyze"
67-
when :validate then "graphql.validate"
68-
else
69-
raise "No name for #{keyword.inspect}"
70-
end
71-
end
53+
PARSE_NAME = "graphql.parse"
54+
LEX_NAME = "graphql.lex"
55+
VALIDATE_NAME = "graphql.validate"
56+
EXECUTE_NAME = "graphql.execute"
57+
ANALYZE_NAME = "graphql.analyze"
58+
7259
private
7360

7461
def operation_name(query)

0 commit comments

Comments
 (0)