Skip to content

Commit

Permalink
Migrate lex and parse, implement DataDogTrace
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Feb 16, 2023
1 parent 04474fb commit 1705a38
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 68 deletions.
8 changes: 4 additions & 4 deletions lib/graphql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def default_parser
# Turn a query string or schema definition into an AST
# @param graphql_string [String] a GraphQL query string or schema definition
# @return [GraphQL::Language::Nodes::Document]
def self.parse(graphql_string, tracer: GraphQL::Tracing::NullTracer)
parse_with_racc(graphql_string, tracer: tracer)
def self.parse(graphql_string, trace: GraphQL::Tracing::NullTrace)
parse_with_racc(graphql_string, trace: trace)
end

# Read the contents of `filename` and parse them as GraphQL
Expand All @@ -54,8 +54,8 @@ def self.parse_file(filename)
parse_with_racc(content, filename: filename)
end

def self.parse_with_racc(string, filename: nil, tracer: GraphQL::Tracing::NullTracer)
GraphQL::Language::Parser.parse(string, filename: filename, tracer: tracer)
def self.parse_with_racc(string, filename: nil, trace: GraphQL::Tracing::NullTrace)
GraphQL::Language::Parser.parse(string, filename: filename, trace: trace)
end

# @return [Array<GraphQL::Language::Token>]
Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/execution/multiplex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def initialize(schema:, queries:, context:, max_complexity:)
@queries = queries
@queries.each { |q| q.multiplex = self }
@context = context
@current_trace = @context[:trace] || schema.new_trace
@current_trace = @context[:trace] || schema.new_trace(multiplex: self)
@dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
@tracers = schema.tracers + (context[:tracers] || [])
# Support `context: {backtrace: true}`
Expand Down
18 changes: 9 additions & 9 deletions lib/graphql/language/parser.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions lib/graphql/language/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -448,22 +448,22 @@ end

EMPTY_ARRAY = [].freeze

def initialize(query_string, filename:, tracer: Tracing::NullTracer)
def initialize(query_string, filename:, trace: Tracing::NullTrace)
raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) if query_string.nil?
@query_string = query_string
@filename = filename
@tracer = tracer
@trace = trace
@reused_next_token = [nil, nil]
end

def parse_document
@document ||= begin
# Break the string into tokens
@tracer.trace("lex", {query_string: @query_string}) do
@trace.lex(query_string: @query_string) do
@tokens ||= GraphQL.scan(@query_string)
end
# From the tokens, build an AST
@tracer.trace("parse", {query_string: @query_string}) do
@trace.parse(query_string: @query_string) do
if @tokens.empty?
raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string)
else
Expand All @@ -476,17 +476,17 @@ end
class << self
attr_accessor :cache

def parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer)
new(query_string, filename: filename, tracer: tracer).parse_document
def parse(query_string, filename: nil, trace: GraphQL::Tracing::NullTrace)
new(query_string, filename: filename, trace: trace).parse_document
end

def parse_file(filename, tracer: GraphQL::Tracing::NullTracer)
def parse_file(filename, trace: GraphQL::Tracing::NullTrace)
if cache
cache.fetch(filename) do
parse(File.read(filename), filename: filename, tracer: tracer)
parse(File.read(filename), filename: filename, trace: trace)
end
else
parse(File.read(filename), filename: filename, tracer: tracer)
parse(File.read(filename), filename: filename, trace: trace)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/graphql/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def interpreter?

# @return [GraphQL::Tracing::Trace]
def current_trace
@current_trace ||= multiplex ? multiplex.current_trace : schema.new_trace
@current_trace ||= multiplex ? multiplex.current_trace : schema.new_trace(multiplex: multiplex, query: self)
end

def subscription_update?
Expand Down Expand Up @@ -375,7 +375,7 @@ def prepare_ast
parse_error = nil
@document ||= begin
if query_string
GraphQL.parse(query_string, tracer: self)
GraphQL.parse(query_string, trace: self.current_trace)
end
rescue GraphQL::ParseError => err
parse_error = err
Expand Down
8 changes: 5 additions & 3 deletions lib/graphql/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -954,9 +954,11 @@ def trace_with(trace_mod, **options)
trace_class.include(trace_mod)
end

def new_trace
@trace_options ||= {}
trace_class.new(**@trace_options)
def new_trace(**options)
if defined?(@trace_options)
options = @trace_options.merge(options)
end
trace_class.new(**options)
end

def query_analyzer(new_analyzer)
Expand Down
37 changes: 21 additions & 16 deletions lib/graphql/tracing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

# New Tracing:
require "graphql/tracing/platform_trace"
require "graphql/tracing/data_dog_trace"
require "graphql/tracing/new_relic_trace"

if defined?(PrometheusExporter::Server)
Expand All @@ -21,17 +22,20 @@
module GraphQL
module Tracing
class Trace
def initialize(**_options)
# @param multiplex [GraphQL::Execution::Multiplex, nil]
# @param query [GraphQL::Query, nil]
def initialize(multiplex: nil, query: nil, **_options)
@multiplex = multiplex
@query = query
end

# TODO
# def lex(query_string:)
# yield
# end
def lex(query_string:)
yield
end

# def parse(query_string:)
# yield
# end
def parse(query_string:)
yield
end

def validate(query:, validate:)
yield
Expand Down Expand Up @@ -82,15 +86,16 @@ def resolve_type_lazy(query:, type:, object:)
end
end

NullTrace = Trace.new

class LegacyTrace < Trace
# TODO: These are not migrated yet
# def lex(query_string:)
# yield
# end

# def parse(query_string:)
# yield
# end
def lex(query_string:, &block)
(@multiplex || @query).trace("lex", { query_string: query_string }, &block)
end

def parse(query_string:, &block)
(@multiplex || @query).trace("parse", { query_string: query_string }, &block)
end

def validate(query:, validate:, &block)
query.trace("validate", { validate: validate, query: query }, &block)
Expand Down
101 changes: 101 additions & 0 deletions lib/graphql/tracing/data_dog_trace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# frozen_string_literal: true

module GraphQL
module Tracing
module DataDogTrace
include PlatformTrace

# @param analytics_enabled [Boolean] Deprecated
# @param analytics_sample_rate [Float] Deprecated
def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: "ruby-graphql", **rest)
if tracer.nil?
tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
end
@tracer = tracer

analytics_available = defined?(Datadog::Contrib::Analytics) \
&& Datadog::Contrib::Analytics.respond_to?(:enabled?) \
&& Datadog::Contrib::Analytics.respond_to?(:set_sample_rate)

@analytics_enabled = analytics_available && Datadog::Contrib::Analytics.enabled?(analytics_enabled)
@analytics_sample_rate = analytics_sample_rate
@service_name = service
end

{
'lex' => 'lex.graphql',
'parse' => 'parse.graphql',
'validate' => 'validate.graphql',
'analyze_query' => 'analyze.graphql',
'analyze_multiplex' => 'analyze.graphql',
'execute_multiplex' => 'execute.graphql',
'execute_query' => 'execute.graphql',
'execute_query_lazy' => 'execute.graphql',
'authorized' => 'authorize.graphql',
'authorized_lazy' => 'authorize.graphql',
'resolve_type' => 'resolve_type.graphql',
'resolve_type_lazy' => 'resolve_type.graphql',
}.each do |trace_method, trace_key|
module_eval <<-RUBY, __FILE__, __LINE__
def #{trace_method}(**data)
@tracer.trace("#{trace_key}", service: @service_name) do |span|
span.span_type = 'custom'
if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, '#{trace_method}')
end
#{
if trace_method == 'execute_multiplex'
<<-RUBY
operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
resource = if operations.empty?
first_query = data[:multiplex].queries.first
fallback_transaction_name(first_query && first_query.context)
else
operations
end
span.resource = resource if resource
# For top span of query, set the analytics sample rate tag, if available.
if @analytics_enabled
Datadog::Contrib::Analytics.set_sample_rate(span, @analytics_sample_rate)
end
RUBY
elsif trace_method == 'execute_query'
<<-RUBY
span.set_tag(:selected_operation_name, data[:query].selected_operation_name)
span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type)
span.set_tag(:query_string, data[:query].query_string)
RUBY
end
}
prepare_span("#{trace_method}", data, span)
super
end
end
RUBY
end
# Implement this method in a subclass to apply custom tags to datadog spans
# @param key [String] The event being traced
# @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
# @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
def prepare_span(key, data, span)
end
def platform_field_key(type, field)
"#{type.graphql_name}.#{field.graphql_name}"
end
def platform_authorized_key(type)
"#{type.graphql_name}.authorized"
end
def platform_resolve_type_key(type)
"#{type.graphql_name}.resolve_type"
end
end
end
end
11 changes: 0 additions & 11 deletions lib/graphql/tracing/new_relic_trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@ module Tracing
module NewRelicTrace
include PlatformTrace

self.platform_keys = {
"lex" => "GraphQL/lex",
"parse" => "GraphQL/parse",
"validate" => "GraphQL/validate",
"analyze_query" => "GraphQL/analyze",
"analyze_multiplex" => "GraphQL/analyze",
"execute_multiplex" => "GraphQL/execute",
"execute_query" => "GraphQL/execute",
"execute_query_lazy" => "GraphQL/execute",
}

# @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
# This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
# It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
Expand Down
12 changes: 0 additions & 12 deletions lib/graphql/tracing/platform_trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,6 @@
module GraphQL
module Tracing
module PlatformTrace
module ClassMethods
attr_accessor :platform_keys

def inherited(child_class)
child_class.platform_keys = self.platform_keys
end
end

def self.included(child_mod)
child_mod.extend(ClassMethods)
end

def initialize(trace_scalars: false, **_options)
@trace_scalars = trace_scalars
super
Expand Down
6 changes: 5 additions & 1 deletion spec/graphql/language/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,11 @@

it "serves traces" do
TestTracing.clear
GraphQL.parse("{ t: __typename }", tracer: TestTracing)
schema = Class.new(GraphQL::Schema) do
tracer(TestTracing)
end
query = GraphQL::Query.new(schema, "{ t: __typename }")
GraphQL.parse("{ t: __typename }", trace: query.current_trace)
traces = TestTracing.traces
assert_equal 2, traces.length
lex_trace, parse_trace = traces
Expand Down
Loading

0 comments on commit 1705a38

Please sign in to comment.