From db051d9cfb64c95e5a8e3fad8c232d9967f1dfc9 Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Tue, 21 May 2024 17:59:14 +0200 Subject: [PATCH 01/11] Added unified naming convention tracer (following span attributes spec) --- .../contrib/graphql/configuration/settings.rb | 5 + .../tracing/contrib/graphql/patcher.rb | 10 +- .../tracing/contrib/graphql/unified_trace.rb | 175 ++++++++++++++++++ .../contrib/graphql/unified_trace_patcher.rb | 26 +++ .../graphql/configuration/settings_spec.rb | 26 +++ .../tracing/contrib/graphql/patcher_spec.rb | 89 +++++++++ .../contrib/graphql/test_schema_examples.rb | 46 ++++- .../contrib/graphql/trace_patcher_spec.rb | 4 +- .../contrib/graphql/tracing_patcher_spec.rb | 4 +- .../contrib/graphql/unified_trace_patcher.rb | 26 +++ 10 files changed, 404 insertions(+), 7 deletions(-) create mode 100644 lib/datadog/tracing/contrib/graphql/unified_trace.rb create mode 100644 lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb create mode 100644 spec/datadog/tracing/contrib/graphql/unified_trace_patcher.rb diff --git a/lib/datadog/tracing/contrib/graphql/configuration/settings.rb b/lib/datadog/tracing/contrib/graphql/configuration/settings.rb index 9f14ec5ee57..f6f5973e191 100644 --- a/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +++ b/lib/datadog/tracing/contrib/graphql/configuration/settings.rb @@ -42,6 +42,11 @@ class Settings < Contrib::Configuration::Settings o.type :bool o.default false end + + option :with_unified_tracer do |o| + o.type :bool + o.default false + end end end end diff --git a/lib/datadog/tracing/contrib/graphql/patcher.rb b/lib/datadog/tracing/contrib/graphql/patcher.rb index 26ab0cbd281..b473b999ff5 100644 --- a/lib/datadog/tracing/contrib/graphql/patcher.rb +++ b/lib/datadog/tracing/contrib/graphql/patcher.rb @@ -4,6 +4,7 @@ require_relative '../patcher' require_relative 'tracing_patcher' require_relative 'trace_patcher' +require_relative 'unified_trace_patcher' module Datadog module Tracing @@ -23,10 +24,15 @@ def patch if configuration[:with_deprecated_tracer] TracingPatcher.patch!(schemas, trace_options) elsif Integration.trace_supported? - TracePatcher.patch!(schemas, trace_options) + if configuration[:with_unified_tracer] + UnifiedTracePatcher.patch!(schemas, trace_options) + else + TracePatcher.patch!(schemas, trace_options) + end else Datadog.logger.warn( - "GraphQL version (#{target_version}) does not support GraphQL::Tracing::DataDogTrace. "\ + "GraphQL version (#{target_version}) does not support GraphQL::Tracing::DataDogTrace"\ + 'or Datadog::Tracing::Contrib::GraphQL::UnifiedTrace.'\ 'Falling back to GraphQL::Tracing::DataDogTracing.' ) TracingPatcher.patch!(schemas, trace_options) diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb new file mode 100644 index 00000000000..66b4f4afa04 --- /dev/null +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +require 'graphql/tracing' + +module Datadog + module Tracing + module Contrib + module GraphQL + # These methods will be called by the GraphQL runtime to trace the execution of queries + module UnifiedTrace + # @param tracer [#trace] Deprecated + # @param analytics_enabled [Boolean] Deprecated + # @param analytics_sample_rate [Float] Deprecated + def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest) + @analytics_enabled = analytics_enabled + @analytics_sample_rate = analytics_sample_rate + + @service_name = service + @has_prepare_span = respond_to?(:prepare_span) + super + end + + def lex(query_string:) + trace(proc { super }, 'lex', query_string, query_string: query_string) + end + + def parse(query_string:) + trace(proc { super }, 'parse', query_string, query_string: query_string) do |span| + span.set_tag('graphql.source', query_string) + end + end + + def validate(query:, validate:) + trace(proc { super }, 'validate', query.selected_operation_name, query: query, validate: validate) do |span| + span.set_tag('graphql.source', query.query_string) + end + end + + def analyze_multiplex(multiplex:) + trace(proc { super }, 'analyze_multiplex', multiplex_resource(multiplex), multiplex: multiplex) + end + + def analyze_query(query:) + trace(proc { super }, 'analyze', query.query_string, query: query) + end + + def execute_multiplex(multiplex:) + trace(proc { super }, 'execute_multiplex', multiplex_resource(multiplex), multiplex: multiplex) do |span| + span.set_tag('graphql.source', "Multiplex[#{multiplex.queries.map(&:query_string).join(', ')}]") + end + end + + def execute_query(query:) + trace(proc { super }, 'execute', query.selected_operation_name, query: query) do |span| + span.set_tag('graphql.source', query.query_string) + span.set_tag('graphql.operation.type', query.selected_operation.operation_type) + span.set_tag('graphql.operation.name', query.selected_operation_name) if query.selected_operation_name + query.provided_variables.each do |key, value| + span.set_tag("graphql.variables.#{key}", value) + end + end + end + + def execute_query_lazy(query:, multiplex:) + resource = if query + query.selected_operation_name || fallback_transaction_name(query.context) + else + multiplex_resource(multiplex) + end + trace(proc { super }, 'execute_lazy', resource, query: query, multiplex: multiplex) + end + + def execute_field_span(callable, span_key, **kwargs) + return_type = kwargs[:field].type.unwrap + trace_field = if return_type.kind.scalar? || return_type.kind.enum? + (kwargs[:field].trace.nil? && @trace_scalars) || kwargs[:field].trace + else + true + end + platform_key = @platform_key_cache[UnifiedTrace].platform_field_key_cache[kwargs[:field]] if trace_field + + if platform_key && trace_field + trace(callable, span_key, platform_key, **kwargs) do |span| + kwargs[:query].provided_variables.each do |key, value| + span.set_tag("graphql.variables.#{key}", value) + end + end + else + callable.call + end + end + + def execute_field(**kwargs) + execute_field_span(proc { super(**kwargs) }, 'resolve', **kwargs) + end + + def execute_field_lazy(**kwargs) + execute_field_span(proc { super(**kwargs) }, 'resolve_lazy', **kwargs) + end + + def authorized_span(callable, span_key, **kwargs) + platform_key = @platform_key_cache[UnifiedTrace].platform_authorized_key_cache[kwargs[:type]] + trace(callable, span_key, platform_key, **kwargs) + end + + def authorized(**kwargs) + authorized_span(proc { super(**kwargs) }, 'authorized', **kwargs) + end + + def authorized_lazy(**kwargs) + authorized_span(proc { super(**kwargs) }, 'authorized_lazy', **kwargs) + end + + def resolve_type_span(callable, span_key, **kwargs) + platform_key = @platform_key_cache[UnifiedTrace].platform_resolve_type_key_cache[kwargs[:type]] + trace(callable, span_key, platform_key, **kwargs) + end + + def resolve_type(**kwargs) + resolve_type_span(proc { super(**kwargs) }, 'resolve_type', **kwargs) + end + + def resolve_type_lazy(**kwargs) + resolve_type_span(proc { super(**kwargs) }, 'resolve_type_lazy', **kwargs) + end + + include ::GraphQL::Tracing::PlatformTrace if defined?(::GraphQL::Tracing::PlatformTrace) + + # 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(field) + field.path + end + + def platform_authorized_key(type) + "#{type.graphql_name}.authorized" + end + + def platform_resolve_type_key(type) + "#{type.graphql_name}.resolve_type" + end + + private + + def trace(callable, trace_key, resource, **kwargs) + Tracing.trace("graphql.#{trace_key}", resource: resource, service: @service_name, type: 'graphql') do |span| + yield(span) if block_given? + + prepare_span(trace_key, kwargs, span) if @has_prepare_span + + callable.call + end + end + + def multiplex_resource(multiplex) + return nil unless multiplex + + operations = multiplex.queries.map(&:selected_operation_name).compact.join(', ') + if operations.empty? + first_query = multiplex.queries.first + fallback_transaction_name(first_query && first_query.context) + else + operations + end + end + end + end + end + end +end diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb b/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb new file mode 100644 index 00000000000..b75a37c1070 --- /dev/null +++ b/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative 'unified_trace' + +module Datadog + module Tracing + module Contrib + module GraphQL + # Provides instrumentation for `graphql` through the GraphQL's tracing with methods defined in UnifiedTrace + module UnifiedTracePatcher + module_function + + def patch!(schemas, options) + if schemas.empty? + ::GraphQL::Schema.trace_with(UnifiedTrace, **options) + else + schemas.each do |schema| + schema.trace_with(UnifiedTrace, **options) + end + end + end + end + end + end + end +end diff --git a/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb b/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb index 1487c1d14a0..9591c4b3c50 100644 --- a/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb @@ -54,4 +54,30 @@ end end end + + describe 'with_unified_tracer' do + context 'when default' do + it do + settings = described_class.new + + expect(settings.with_unified_tracer).to eq(false) + end + end + + context 'when given `true`' do + it do + settings = described_class.new(with_unified_tracer: true) + + expect(settings.with_unified_tracer).to eq(true) + end + end + + context 'when given `false`' do + it do + settings = described_class.new(with_unified_tracer: false) + + expect(settings.with_unified_tracer).to eq(false) + end + end + end end diff --git a/spec/datadog/tracing/contrib/graphql/patcher_spec.rb b/spec/datadog/tracing/contrib/graphql/patcher_spec.rb index f02618d0984..d828918fd1f 100644 --- a/spec/datadog/tracing/contrib/graphql/patcher_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/patcher_spec.rb @@ -2,6 +2,7 @@ require 'datadog/tracing/contrib/graphql/test_schema_examples' require 'datadog/tracing/contrib/graphql/tracing_patcher' require 'datadog/tracing/contrib/graphql/trace_patcher' +require 'datadog/tracing/contrib/graphql/unified_trace_patcher' require 'datadog' @@ -64,6 +65,48 @@ end end + context 'with with_unified_tracer enabled' do + it do + allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(true) + expect(Datadog::Tracing::Contrib::GraphQL::UnifiedTracePatcher).to receive(:patch!).with( + [], + hash_including(:analytics_enabled, :analytics_sample_rate, :service) + ) + + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end + end + end + + context 'with with_unified_tracer disabled' do + it do + allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(true) + expect(Datadog::Tracing::Contrib::GraphQL::TracePatcher).to receive(:patch!).with( + [], + hash_including(:analytics_enabled, :analytics_sample_rate, :service) + ) + + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: false + end + end + end + + context 'with with_unified_tracer enabled and with_deprecated_tracer enabled' do + it do + allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(true) + expect(Datadog::Tracing::Contrib::GraphQL::TracingPatcher).to receive(:patch!).with( + [], + hash_including(:analytics_enabled, :analytics_sample_rate, :service) + ) + + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true, with_deprecated_tracer: true + end + end + end + context 'with given schema' do it do allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(true) @@ -127,6 +170,52 @@ end end + context 'with with_unified_tracer enabled' do + it do + allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(false) + expect(Datadog::Tracing::Contrib::GraphQL::TracingPatcher).to receive(:patch!).with( + [], + hash_including(:analytics_enabled, :analytics_sample_rate, :service) + ) + expect_any_instance_of(Datadog::Core::Logger).to receive(:warn) + .with(/Falling back to GraphQL::Tracing::DataDogTracing/) + + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true + end + end + end + + context 'with with_unified_tracer disabled' do + it do + allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(false) + expect(Datadog::Tracing::Contrib::GraphQL::TracingPatcher).to receive(:patch!).with( + [], + hash_including(:analytics_enabled, :analytics_sample_rate, :service) + ) + expect_any_instance_of(Datadog::Core::Logger).to receive(:warn) + .with(/Falling back to GraphQL::Tracing::DataDogTracing/) + + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: false + end + end + end + + context 'with with_unified_tracer enabled and with_deprecated_tracer enabled' do + it do + allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(false) + expect(Datadog::Tracing::Contrib::GraphQL::TracingPatcher).to receive(:patch!).with( + [], + hash_including(:analytics_enabled, :analytics_sample_rate, :service) + ) + + Datadog.configure do |c| + c.tracing.instrument :graphql, with_unified_tracer: true, with_deprecated_tracer: true + end + end + end + context 'with given schema' do it do allow(Datadog::Tracing::Contrib::GraphQL::Integration).to receive(:trace_supported?).and_return(false) diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index 15e7e9f83d5..0068e67a6ff 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -23,7 +23,7 @@ class TestGraphQLSchema < ::GraphQL::Schema query(TestGraphQLQuery) end -RSpec.shared_examples 'graphql instrumentation' do +RSpec.shared_examples 'graphql default instrumentation' do around do |example| Datadog::GraphQLTestHelpers.reset_schema_cache!(::GraphQL::Schema) Datadog::GraphQLTestHelpers.reset_schema_cache!(TestGraphQLSchema) @@ -69,3 +69,47 @@ class TestGraphQLSchema < ::GraphQL::Schema end end end + +RSpec.shared_examples 'graphql instrumentation with unified naming convention trace' do + around do |example| + Datadog::GraphQLTestHelpers.reset_schema_cache!(::GraphQL::Schema) + Datadog::GraphQLTestHelpers.reset_schema_cache!(TestGraphQLSchema) + + example.run + + Datadog::GraphQLTestHelpers.reset_schema_cache!(::GraphQL::Schema) + Datadog::GraphQLTestHelpers.reset_schema_cache!(TestGraphQLSchema) + end + + describe 'query trace' do + subject(:result) { TestGraphQLSchema.execute('{ user(id: 1) { name } }') } + + matrix = [ + ['graphql.analyze', 'graphql.analyze_query'], + ['graphql.analyze_multiplex', 'graphql.analyze_multiplex'], + ['graphql.authorized', 'graphql.authorized'], + ['graphql.authorized', 'graphql.authorized'], + ['graphql.execute', 'graphql.execute_query'], + ['graphql.execute_lazy', 'graphql.execute_query_lazy'], + ['graphql.execute_multiplex', 'graphql.execute_multiplex'], + (['graphql.lex', 'graphql.lex'] if Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('2.2')), + ['graphql.parse', 'graphql.parse'], + ['graphql.resolve', 'graphql.execute_field'], + # New Ruby-based parser doesn't emit a "lex" event. (graphql/c_parser still does.) + ['graphql.validate', 'graphql.validate'] + ].compact + + matrix.each_with_index do |(name, resource), index| + it "creates #{name} span with #{resource} resource" do + expect(result.to_h['errors']).to be nil + expect(spans).to have(matrix.length).items + span = spans[index] + + expect(span.name).to eq(name) + expect(span.resource).to eq(resource) + expect(span.service).to eq(tracer.default_service) + expect(span.type).to eq('graphql') + end + end + end +end diff --git a/spec/datadog/tracing/contrib/graphql/trace_patcher_spec.rb b/spec/datadog/tracing/contrib/graphql/trace_patcher_spec.rb index 6e225cf8aaf..a3898903d8e 100644 --- a/spec/datadog/tracing/contrib/graphql/trace_patcher_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/trace_patcher_spec.rb @@ -8,7 +8,7 @@ skip: Gem::Version.new(::GraphQL::VERSION) < Gem::Version.new('2.0.19') do describe '#patch!' do context 'with empty schema configuration' do - it_behaves_like 'graphql instrumentation' do + it_behaves_like 'graphql default instrumentation' do before do described_class.patch!([], {}) end @@ -16,7 +16,7 @@ end context 'with specified schemas configuration' do - it_behaves_like 'graphql instrumentation' do + it_behaves_like 'graphql default instrumentation' do before do described_class.patch!([TestGraphQLSchema], {}) end diff --git a/spec/datadog/tracing/contrib/graphql/tracing_patcher_spec.rb b/spec/datadog/tracing/contrib/graphql/tracing_patcher_spec.rb index 0943e474b5f..db036f6e4ac 100644 --- a/spec/datadog/tracing/contrib/graphql/tracing_patcher_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/tracing_patcher_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Datadog::Tracing::Contrib::GraphQL::TracingPatcher do describe '#patch!' do context 'with empty schema configuration' do - it_behaves_like 'graphql instrumentation' do + it_behaves_like 'graphql default instrumentation' do before do described_class.patch!([], {}) end @@ -15,7 +15,7 @@ end context 'with specified schemas configuration' do - it_behaves_like 'graphql instrumentation' do + it_behaves_like 'graphql default instrumentation' do before do described_class.patch!([TestGraphQLSchema], {}) end diff --git a/spec/datadog/tracing/contrib/graphql/unified_trace_patcher.rb b/spec/datadog/tracing/contrib/graphql/unified_trace_patcher.rb new file mode 100644 index 00000000000..a3c828d6fcb --- /dev/null +++ b/spec/datadog/tracing/contrib/graphql/unified_trace_patcher.rb @@ -0,0 +1,26 @@ +require 'datadog/tracing/contrib/support/spec_helper' +require 'datadog/tracing/contrib/graphql/test_schema_examples' +require 'datadog/tracing/contrib/graphql/unified_trace_patcher' + +require 'datadog' + +RSpec.describe Datadog::Tracing::Contrib::GraphQL::UnifiedTracePatcher, + skip: Gem::Version.new(::GraphQL::VERSION) < Gem::Version.new('2.0.19') do + describe '#patch!' do + context 'with empty schema configuration' do + it_behaves_like 'graphql instrumentation with unified naming convention trace' do + before do + described_class.patch!([], {}) + end + end + end + + context 'with specified schemas configuration' do + it_behaves_like 'graphql default instrumentation with unified naming convention trace' do + before do + described_class.patch!([TestGraphQLSchema], {}) + end + end + end + end + end From e4a23d77a8ed7089cca037a1fe217a2c9060a51f Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Mon, 27 May 2024 11:42:41 +0200 Subject: [PATCH 02/11] Fix require graphql --- lib/datadog/tracing/contrib/graphql/unified_trace.rb | 2 +- lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index 66b4f4afa04..d17de0f5249 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -124,7 +124,7 @@ def resolve_type_lazy(**kwargs) resolve_type_span(proc { super(**kwargs) }, 'resolve_type_lazy', **kwargs) end - include ::GraphQL::Tracing::PlatformTrace if defined?(::GraphQL::Tracing::PlatformTrace) + include ::GraphQL::Tracing::PlatformTrace # Implement this method in a subclass to apply custom tags to datadog spans # @param key [String] The event being traced diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb b/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb index b75a37c1070..0d3b1d33d79 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative 'unified_trace' - module Datadog module Tracing module Contrib @@ -11,6 +9,7 @@ module UnifiedTracePatcher module_function def patch!(schemas, options) + require_relative 'unified_trace' if schemas.empty? ::GraphQL::Schema.trace_with(UnifiedTrace, **options) else From c5dd2e5aaecdac001fb085405b5deb6630253d3d Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Mon, 27 May 2024 13:15:03 +0200 Subject: [PATCH 03/11] Added static typing for unified_trace --- .../tracing/contrib/graphql/unified_trace.rbs | 64 +++++++++++++++++++ .../contrib/graphql/unified_trace_patcher.rbs | 11 ++++ 2 files changed, 75 insertions(+) create mode 100644 sig/datadog/tracing/contrib/graphql/unified_trace.rbs create mode 100644 sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs diff --git a/sig/datadog/tracing/contrib/graphql/unified_trace.rbs b/sig/datadog/tracing/contrib/graphql/unified_trace.rbs new file mode 100644 index 00000000000..c3afdf38509 --- /dev/null +++ b/sig/datadog/tracing/contrib/graphql/unified_trace.rbs @@ -0,0 +1,64 @@ +module Datadog + module Tracing + module Contrib + module GraphQL + module UnifiedTrace + @analytics_enabled: untyped + + @analytics_sample_rate: untyped + + @service_name: untyped + + @has_prepare_span: untyped + def initialize: (?tracer: untyped?, ?analytics_enabled: bool, ?analytics_sample_rate: ::Float, ?service: untyped?, **untyped rest) -> void + + def lex: (query_string: untyped) -> untyped + + def parse: (query_string: untyped) -> untyped + + def validate: (query: untyped, validate: untyped) -> untyped + + def analyze_multiplex: (multiplex: untyped) -> untyped + + def analyze_query: (query: untyped) -> untyped + + def execute_multiplex: (multiplex: untyped) -> untyped + + def execute_query: (query: untyped) -> untyped + + def execute_query_lazy: (query: untyped, multiplex: untyped) -> untyped + + def execute_field_span: (untyped callable, untyped span_key, **untyped kwargs) -> untyped + + def execute_field: (**untyped kwargs) -> untyped + + def execute_field_lazy: (**untyped kwargs) -> untyped + + def authorized_span: (untyped callable, untyped span_key, **untyped kwargs) -> untyped + + def authorized: (**untyped kwargs) -> untyped + + def authorized_lazy: (**untyped kwargs) -> untyped + + def resolve_type_span: (untyped callable, untyped span_key, **untyped kwargs) -> untyped + + def resolve_type: (**untyped kwargs) -> untyped + + def resolve_type_lazy: (**untyped kwargs) -> untyped + + def platform_field_key: (untyped field) -> untyped + + def platform_authorized_key: (untyped type) -> ::String + + def platform_resolve_type_key: (untyped type) -> ::String + + private + + def trace: (untyped callable, untyped trace_key, untyped resource, **untyped kwargs) ?{ (untyped) -> untyped } -> untyped + + def multiplex_resource: (untyped multiplex) -> (nil | untyped) + end + end + end + end +end diff --git a/sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs b/sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs new file mode 100644 index 00000000000..6a85989398f --- /dev/null +++ b/sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs @@ -0,0 +1,11 @@ +module Datadog + module Tracing + module Contrib + module GraphQL + module UnifiedTracePatcher + def self?.patch!: (::Array[untyped] schemas, ::Hash[Symbol, untyped] options) -> untyped + end + end + end + end +end From ff62656517064275a8b9c4cc80149f65be2936a5 Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Tue, 28 May 2024 14:34:58 +0200 Subject: [PATCH 04/11] Fix unified trace tests --- .../contrib/graphql/test_schema_examples.rb | 39 +++++++++++++------ ...tcher.rb => unified_trace_patcher_spec.rb} | 2 +- 2 files changed, 28 insertions(+), 13 deletions(-) rename spec/datadog/tracing/contrib/graphql/{unified_trace_patcher.rb => unified_trace_patcher_spec.rb} (89%) diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index 0068e67a6ff..b78e31261a5 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -82,23 +82,26 @@ class TestGraphQLSchema < ::GraphQL::Schema end describe 'query trace' do - subject(:result) { TestGraphQLSchema.execute('{ user(id: 1) { name } }') } + subject(:result) { TestGraphQLSchema.execute('query Users{ user(id: 1) { name } }') } matrix = [ - ['graphql.analyze', 'graphql.analyze_query'], - ['graphql.analyze_multiplex', 'graphql.analyze_multiplex'], - ['graphql.authorized', 'graphql.authorized'], - ['graphql.authorized', 'graphql.authorized'], - ['graphql.execute', 'graphql.execute_query'], - ['graphql.execute_lazy', 'graphql.execute_query_lazy'], - ['graphql.execute_multiplex', 'graphql.execute_multiplex'], - (['graphql.lex', 'graphql.lex'] if Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('2.2')), - ['graphql.parse', 'graphql.parse'], - ['graphql.resolve', 'graphql.execute_field'], + ['graphql.analyze', 'query Users{ user(id: 1) { name } }'], + ['graphql.analyze_multiplex', 'Users'], + ['graphql.authorized', 'TestGraphQLQuery.authorized'], + ['graphql.authorized', 'TestUser.authorized'], + ['graphql.execute', 'Users'], + ['graphql.execute_lazy', 'Users'], + ['graphql.execute_multiplex', 'Users'], + (['graphql.lex', 'query Users{ user(id: 1) { name } }'] if Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('2.2')), + ['graphql.parse', 'query Users{ user(id: 1) { name } }'], + ['graphql.resolve', 'TestGraphQLQuery.user'], # New Ruby-based parser doesn't emit a "lex" event. (graphql/c_parser still does.) - ['graphql.validate', 'graphql.validate'] + ['graphql.validate', 'Users'] ].compact + spans_with_source = ['graphql.parse', 'graphql.validate', 'graphql.execute'] + spans_with_variables = ['graphql.execute', 'graphql.resolve'] + matrix.each_with_index do |(name, resource), index| it "creates #{name} span with #{resource} resource" do expect(result.to_h['errors']).to be nil @@ -109,6 +112,18 @@ class TestGraphQLSchema < ::GraphQL::Schema expect(span.resource).to eq(resource) expect(span.service).to eq(tracer.default_service) expect(span.type).to eq('graphql') + + # graphql.source for execute_multiplex is not required in the span attributes specification + if spans_with_source.include?(name) + expect(span.get_tag('graphql.source')).to eq('query Users{ user(id: 1) { name } }') + end + + if name == 'graphql.execute' + expect(span.get_tag('graphql.operation.type')).to eq('query') + expect(span.get_tag('graphql.operation.name')).to eq('Users') + end + + expect(span.get_tag('graphql.variables.id')).to eq('1') if spans_with_variables.include?(name) end end end diff --git a/spec/datadog/tracing/contrib/graphql/unified_trace_patcher.rb b/spec/datadog/tracing/contrib/graphql/unified_trace_patcher_spec.rb similarity index 89% rename from spec/datadog/tracing/contrib/graphql/unified_trace_patcher.rb rename to spec/datadog/tracing/contrib/graphql/unified_trace_patcher_spec.rb index a3c828d6fcb..ff94f1fbc8e 100644 --- a/spec/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +++ b/spec/datadog/tracing/contrib/graphql/unified_trace_patcher_spec.rb @@ -16,7 +16,7 @@ end context 'with specified schemas configuration' do - it_behaves_like 'graphql default instrumentation with unified naming convention trace' do + it_behaves_like 'graphql instrumentation with unified naming convention trace' do before do described_class.patch!([TestGraphQLSchema], {}) end From 3be948390fdd731b58a7d0f5145ca8ba883a908f Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Tue, 28 May 2024 15:36:57 +0200 Subject: [PATCH 05/11] Fix graphql.variables.* for graphql.resolve --- .../tracing/contrib/graphql/unified_trace.rb | 15 ++++------ .../contrib/graphql/test_schema_examples.rb | 28 +++++++++++++------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index d17de0f5249..35fc6763f67 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -71,17 +71,11 @@ def execute_query_lazy(query:, multiplex:) end def execute_field_span(callable, span_key, **kwargs) - return_type = kwargs[:field].type.unwrap - trace_field = if return_type.kind.scalar? || return_type.kind.enum? - (kwargs[:field].trace.nil? && @trace_scalars) || kwargs[:field].trace - else - true - end - platform_key = @platform_key_cache[UnifiedTrace].platform_field_key_cache[kwargs[:field]] if trace_field - - if platform_key && trace_field + platform_key = @platform_key_cache[UnifiedTrace].platform_field_key_cache[kwargs[:field]] + + if platform_key trace(callable, span_key, platform_key, **kwargs) do |span| - kwargs[:query].provided_variables.each do |key, value| + kwargs[:arguments].each do |key, value| span.set_tag("graphql.variables.#{key}", value) end end @@ -91,6 +85,7 @@ def execute_field_span(callable, span_key, **kwargs) end def execute_field(**kwargs) + # kwargs[:arguments] is { id => 1 } for 'user(id: 1) { name }'. This is what we want to send to the WAF. execute_field_span(proc { super(**kwargs) }, 'resolve', **kwargs) end diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index b78e31261a5..1e0e3c2e625 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -82,29 +82,36 @@ class TestGraphQLSchema < ::GraphQL::Schema end describe 'query trace' do - subject(:result) { TestGraphQLSchema.execute('query Users{ user(id: 1) { name } }') } + subject(:result) do + TestGraphQLSchema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) + end matrix = [ - ['graphql.analyze', 'query Users{ user(id: 1) { name } }'], + ['graphql.analyze', 'query Users($var: ID!){ user(id: $var) { name } }'], ['graphql.analyze_multiplex', 'Users'], ['graphql.authorized', 'TestGraphQLQuery.authorized'], ['graphql.authorized', 'TestUser.authorized'], ['graphql.execute', 'Users'], ['graphql.execute_lazy', 'Users'], ['graphql.execute_multiplex', 'Users'], - (['graphql.lex', 'query Users{ user(id: 1) { name } }'] if Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('2.2')), - ['graphql.parse', 'query Users{ user(id: 1) { name } }'], + if Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('2.2') + ['graphql.lex', 'query Users($var: ID!){ user(id: $var) { name } }'] + end, + ['graphql.parse', 'query Users($var: ID!){ user(id: $var) { name } }'], ['graphql.resolve', 'TestGraphQLQuery.user'], + ['graphql.resolve', 'TestUser.name'], # New Ruby-based parser doesn't emit a "lex" event. (graphql/c_parser still does.) ['graphql.validate', 'Users'] ].compact + # graphql.source for execute_multiplex is not required in the span attributes specification spans_with_source = ['graphql.parse', 'graphql.validate', 'graphql.execute'] - spans_with_variables = ['graphql.execute', 'graphql.resolve'] matrix.each_with_index do |(name, resource), index| it "creates #{name} span with #{resource} resource" do expect(result.to_h['errors']).to be nil + expect(result.to_h['data']).to eq({ 'user' => { 'name' => 'Bits' } }) + expect(spans).to have(matrix.length).items span = spans[index] @@ -113,17 +120,22 @@ class TestGraphQLSchema < ::GraphQL::Schema expect(span.service).to eq(tracer.default_service) expect(span.type).to eq('graphql') - # graphql.source for execute_multiplex is not required in the span attributes specification if spans_with_source.include?(name) - expect(span.get_tag('graphql.source')).to eq('query Users{ user(id: 1) { name } }') + expect(span.get_tag('graphql.source')).to eq('query Users($var: ID!){ user(id: $var) { name } }') end if name == 'graphql.execute' expect(span.get_tag('graphql.operation.type')).to eq('query') expect(span.get_tag('graphql.operation.name')).to eq('Users') + # graphql.variables.* in graphql.execute span are the ones defined outside the query + # (variables part in JSON for example) + expect(span.get_tag('graphql.variables.var')).to eq(1) end - expect(span.get_tag('graphql.variables.id')).to eq('1') if spans_with_variables.include?(name) + if name == 'graphql.resolve' && resource == 'TestGraphQLQuery.user' + # During graphql.resolve, it converts it to string (as it builds an SQL query for example) + expect(span.get_tag('graphql.variables.id')).to eq('1') + end end end end From a564e4b661f6c20e24d9ec3cfc88a7c541bafbd2 Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Tue, 28 May 2024 16:53:19 +0200 Subject: [PATCH 06/11] Fix trailing whitespace --- spec/datadog/tracing/contrib/graphql/test_schema_examples.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index 1e0e3c2e625..beb8f84e584 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -82,7 +82,7 @@ class TestGraphQLSchema < ::GraphQL::Schema end describe 'query trace' do - subject(:result) do + subject(:result) do TestGraphQLSchema.execute(query: 'query Users($var: ID!){ user(id: $var) { name } }', variables: { var: 1 }) end From e22728b5ea67dfbb3f45b6c665781fddc4b0549b Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Thu, 20 Jun 2024 13:00:05 +0200 Subject: [PATCH 07/11] Completed typing for new tracer --- Steepfile | 1 + .../tracing/contrib/graphql/unified_trace.rb | 4 +- .../tracing/contrib/graphql/unified_trace.rbs | 66 +++++++++++-------- .../contrib/graphql/unified_trace_patcher.rbs | 2 +- vendor/rbs/graphql/0/graphql.rbs | 37 +++++++++++ 5 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 vendor/rbs/graphql/0/graphql.rbs diff --git a/Steepfile b/Steepfile index 23076c9885e..83d86357de3 100644 --- a/Steepfile +++ b/Steepfile @@ -595,6 +595,7 @@ target :datadog do library 'opentelemetry-api' library 'passenger' library 'webmock' + library 'graphql' # TODO: gem 'libddwaf' library 'libddwaf' diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index 35fc6763f67..afcc261b657 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -8,10 +8,10 @@ module Contrib module GraphQL # These methods will be called by the GraphQL runtime to trace the execution of queries module UnifiedTrace - # @param tracer [#trace] Deprecated # @param analytics_enabled [Boolean] Deprecated # @param analytics_sample_rate [Float] Deprecated - def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest) + # @param service [String|nil] The service name to be set on the spans + def initialize(analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest) @analytics_enabled = analytics_enabled @analytics_sample_rate = analytics_sample_rate diff --git a/sig/datadog/tracing/contrib/graphql/unified_trace.rbs b/sig/datadog/tracing/contrib/graphql/unified_trace.rbs index c3afdf38509..ee5515fae68 100644 --- a/sig/datadog/tracing/contrib/graphql/unified_trace.rbs +++ b/sig/datadog/tracing/contrib/graphql/unified_trace.rbs @@ -3,60 +3,72 @@ module Datadog module Contrib module GraphQL module UnifiedTrace - @analytics_enabled: untyped + @analytics_enabled: bool - @analytics_sample_rate: untyped + @analytics_sample_rate: Float - @service_name: untyped + @service_name: String? - @has_prepare_span: untyped - def initialize: (?tracer: untyped?, ?analytics_enabled: bool, ?analytics_sample_rate: ::Float, ?service: untyped?, **untyped rest) -> void + @has_prepare_span: bool + def initialize: (?analytics_enabled: bool, ?analytics_sample_rate: Float, ?service: String?, **Hash[Symbol, Object] rest) -> self + + type lexerArray = Array[Integer | Symbol | String | nil | lexerArray] - def lex: (query_string: untyped) -> untyped + def lex: (query_string: String) -> lexerArray - def parse: (query_string: untyped) -> untyped + def parse: (query_string: String) -> GraphQL::Language::Nodes::Document - def validate: (query: untyped, validate: untyped) -> untyped + def validate: (query: GraphQL::Query, validate: bool) -> { remaining_timeout: Float?, error: Array[StandardError] } - def analyze_multiplex: (multiplex: untyped) -> untyped + def analyze_multiplex: (multiplex: GraphQL::Execution::Multiplex) -> Array[Object] - def analyze_query: (query: untyped) -> untyped + def analyze_query: (query: GraphQL::Query) -> Array[Object] - def execute_multiplex: (multiplex: untyped) -> untyped + def execute_multiplex: (multiplex: GraphQL::Execution::Multiplex) -> Array[GraphQL::Query::Result] - def execute_query: (query: untyped) -> untyped + def execute_query: (query: GraphQL::Query) -> GraphQL::Query::Result - def execute_query_lazy: (query: untyped, multiplex: untyped) -> untyped + def execute_query_lazy: (query: GraphQL::Query, multiplex: GraphQL::Execution::Multiplex) -> GraphQL::Query::Result + + type executeFieldKwargs = {query: GraphQL::Query, field: GraphQL::Schema::Field, ast_node: GraphQL::Language::Nodes::Field, arguments: Hash[Symbol, String], object: GraphQL::Schema::Object?} - def execute_field_span: (untyped callable, untyped span_key, **untyped kwargs) -> untyped + def execute_field_span: (Proc callable, String span_key, **executeFieldKwargs kwargs) -> Array[Object] - def execute_field: (**untyped kwargs) -> untyped + def execute_field: (**executeFieldKwargs kwargs) -> Array[Object] - def execute_field_lazy: (**untyped kwargs) -> untyped + def execute_field_lazy: (**executeFieldKwargs kwargs) -> Array[Object] + + type authorizedKwargs = {query: GraphQL::Query, type: GraphQL::Schema::Object, object: GraphQL::Schema::Object?} - def authorized_span: (untyped callable, untyped span_key, **untyped kwargs) -> untyped + def authorized_span: (Proc callable, String span_key, **authorizedKwargs kwargs) -> GraphQL::Schema::Object? - def authorized: (**untyped kwargs) -> untyped + def authorized: (**authorizedKwargs kwargs) -> GraphQL::Schema::Object? - def authorized_lazy: (**untyped kwargs) -> untyped + def authorized_lazy: (**authorizedKwargs kwargs) -> GraphQL::Schema::Object? + + type resolveTypeKwargs = {query: GraphQL::Query, type: GraphQL::Schema::Union, object: GraphQL::Schema::Object?} - def resolve_type_span: (untyped callable, untyped span_key, **untyped kwargs) -> untyped + def resolve_type_span: (Proc callable, String span_key, **resolveTypeKwargs kwargs) -> [GraphQL::Schema::Object, nil] - def resolve_type: (**untyped kwargs) -> untyped + def resolve_type: (**resolveTypeKwargs kwargs) -> [GraphQL::Schema::Object, nil] - def resolve_type_lazy: (**untyped kwargs) -> untyped + def resolve_type_lazy: (**resolveTypeKwargs kwargs) -> [GraphQL::Schema::Object, nil] - def platform_field_key: (untyped field) -> untyped + def platform_field_key: (GraphQL::Schema::Field field) -> String - def platform_authorized_key: (untyped type) -> ::String + def platform_authorized_key: (GraphQL::Schema::Object type) -> String - def platform_resolve_type_key: (untyped type) -> ::String + def platform_resolve_type_key: (GraphQL::Schema::Union type) -> String private - def trace: (untyped callable, untyped trace_key, untyped resource, **untyped kwargs) ?{ (untyped) -> untyped } -> untyped + type traceKwargsValues = GraphQL::Query | GraphQL::Schema::Union | GraphQL::Schema::Object | GraphQL::Schema::Field | GraphQL::Execution::Multiplex | GraphQL::Language::Nodes::Field | Hash[Symbol, String] | String | bool | nil - def multiplex_resource: (untyped multiplex) -> (nil | untyped) + type traceResult = lexerArray | GraphQL::Language::Nodes::Document | { remaining_timeout: Float?, error: Array[StandardError] } | Array[Object] | GraphQL::Schema::Object? | [GraphQL::Schema::Object, nil] + + def trace: (Proc callable, String trace_key, String resource, **Hash[Symbol, traceKwargsValues ] kwargs) ?{ (Datadog::Tracing::SpanOperation) -> void } -> traceResult + + def multiplex_resource: (GraphQL::Execution::Multiplex multiplex) -> String? end end end diff --git a/sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs b/sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs index 6a85989398f..3a79304cadd 100644 --- a/sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs +++ b/sig/datadog/tracing/contrib/graphql/unified_trace_patcher.rbs @@ -3,7 +3,7 @@ module Datadog module Contrib module GraphQL module UnifiedTracePatcher - def self?.patch!: (::Array[untyped] schemas, ::Hash[Symbol, untyped] options) -> untyped + def self?.patch!: (Array[GraphQL::Schema] schemas, Hash[Symbol, bool | Float | String | nil] options) -> void end end end diff --git a/vendor/rbs/graphql/0/graphql.rbs b/vendor/rbs/graphql/0/graphql.rbs new file mode 100644 index 00000000000..81f931ae6c7 --- /dev/null +++ b/vendor/rbs/graphql/0/graphql.rbs @@ -0,0 +1,37 @@ +module GraphQL + class Schema + class Field + end + + class Object + end + + class Union + end + end + + module Tracing + class Trace + end + end + + module Language + module Nodes + class Document + end + + class Field + end + end + end + + class Query + class Result + end + end + + module Execution + class Multiplex + end + end +end \ No newline at end of file From d05e034cebb9cf8b12b93ef2bb8376340cded904 Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Fri, 21 Jun 2024 16:50:20 +0200 Subject: [PATCH 08/11] Update docs --- docs/GettingStarted.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 3169e33f0b7..4301be2b0f8 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -814,11 +814,12 @@ YourSchema.execute(query, variables: {}, context: {}, operation_name: nil) The `instrument :graphql` method accepts the following parameters. Additional options can be substituted in for `options`: -| Key | Type | Description | Default | -| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | -| `schemas` | `Array` | Array of `GraphQL::Schema` objects (that support class-based schema only) to trace. If you do not provide any, then tracing will applied to all the schemas. | `[]` | -| `with_deprecated_tracer` | `Bool` | Enable to instrument with deprecated `GraphQL::Tracing::DataDogTracing`. Default is `false`, using `GraphQL::Tracing::DataDogTrace` | `false` | -| `service_name` | `String` | Service name used for graphql instrumentation | `'ruby-graphql'` | +| Key | Type | Description | Default | +| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `schemas` | `Array` | Array of `GraphQL::Schema` objects (that support class-based schema only) to trace. If you do not provide any, then tracing will applied to all the schemas. | `[]` | +| `with_unified_tracer` | `Bool` | Enable to instrument with `UnifiedTrace` tracer, enabling support for API Catalog. `with_deprecated_tracer` has priority over this. Default is `false`, using `GraphQL::Tracing::DataDogTrace` (Added in v2.2) | `false` | +| `with_deprecated_tracer` | `Bool` | Enable to instrument with deprecated `GraphQL::Tracing::DataDogTracing`. This has priority over `with_unified_tracer`. Default is `false`, using `GraphQL::Tracing::DataDogTrace` | `false` | +| `service_name` | `String` | Service name used for graphql instrumentation | `'ruby-graphql'` | **Manually configuring GraphQL schemas** @@ -832,6 +833,14 @@ class YourSchema < GraphQL::Schema end ``` +With `UnifiedTracer` (Added in v2.2) + +```ruby +class YourSchema < GraphQL::Schema + trace_with Datadog::Tracing::Contrib::GraphQL::UnifiedTrace +end +``` + or with `GraphQL::Tracing::DataDogTracing` (deprecated) ```ruby From 6dba7af341abd7cc6baf22076fc5971a8e578dcf Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Fri, 21 Jun 2024 17:37:51 +0200 Subject: [PATCH 09/11] Add catch-all args and kwargs in prevision of upstream additional arguments --- .../tracing/contrib/graphql/unified_trace.rb | 48 +++++++++---------- .../tracing/contrib/graphql/unified_trace.rbs | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index afcc261b657..a6b7e225e8c 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -11,7 +11,7 @@ module UnifiedTrace # @param analytics_enabled [Boolean] Deprecated # @param analytics_sample_rate [Float] Deprecated # @param service [String|nil] The service name to be set on the spans - def initialize(analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest) + def initialize(*args, analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **kwargs) @analytics_enabled = analytics_enabled @analytics_sample_rate = analytics_sample_rate @@ -20,37 +20,37 @@ def initialize(analytics_enabled: false, analytics_sample_rate: 1.0, service: ni super end - def lex(query_string:) + def lex(*args, query_string:, **kwargs) trace(proc { super }, 'lex', query_string, query_string: query_string) end - def parse(query_string:) + def parse(*args, query_string:, **kwargs) trace(proc { super }, 'parse', query_string, query_string: query_string) do |span| span.set_tag('graphql.source', query_string) end end - def validate(query:, validate:) + def validate(*args, query:, validate:, **kwargs) trace(proc { super }, 'validate', query.selected_operation_name, query: query, validate: validate) do |span| span.set_tag('graphql.source', query.query_string) end end - def analyze_multiplex(multiplex:) + def analyze_multiplex(*args, multiplex:, **kwargs) trace(proc { super }, 'analyze_multiplex', multiplex_resource(multiplex), multiplex: multiplex) end - def analyze_query(query:) + def analyze_query(*args, query:, **kwargs) trace(proc { super }, 'analyze', query.query_string, query: query) end - def execute_multiplex(multiplex:) + def execute_multiplex(*args, multiplex:, **kwargs) trace(proc { super }, 'execute_multiplex', multiplex_resource(multiplex), multiplex: multiplex) do |span| span.set_tag('graphql.source', "Multiplex[#{multiplex.queries.map(&:query_string).join(', ')}]") end end - def execute_query(query:) + def execute_query(*args, query:, **kwargs) trace(proc { super }, 'execute', query.selected_operation_name, query: query) do |span| span.set_tag('graphql.source', query.query_string) span.set_tag('graphql.operation.type', query.selected_operation.operation_type) @@ -61,7 +61,7 @@ def execute_query(query:) end end - def execute_query_lazy(query:, multiplex:) + def execute_query_lazy(*args, query:, multiplex:, **kwargs) resource = if query query.selected_operation_name || fallback_transaction_name(query.context) else @@ -84,13 +84,13 @@ def execute_field_span(callable, span_key, **kwargs) end end - def execute_field(**kwargs) + def execute_field(*args, **kwargs) # kwargs[:arguments] is { id => 1 } for 'user(id: 1) { name }'. This is what we want to send to the WAF. - execute_field_span(proc { super(**kwargs) }, 'resolve', **kwargs) + execute_field_span(proc { super }, 'resolve', **kwargs) end - def execute_field_lazy(**kwargs) - execute_field_span(proc { super(**kwargs) }, 'resolve_lazy', **kwargs) + def execute_field_lazy(*args, **kwargs) + execute_field_span(proc { super }, 'resolve_lazy', **kwargs) end def authorized_span(callable, span_key, **kwargs) @@ -98,12 +98,12 @@ def authorized_span(callable, span_key, **kwargs) trace(callable, span_key, platform_key, **kwargs) end - def authorized(**kwargs) - authorized_span(proc { super(**kwargs) }, 'authorized', **kwargs) + def authorized(*args, **kwargs) + authorized_span(proc { super }, 'authorized', **kwargs) end - def authorized_lazy(**kwargs) - authorized_span(proc { super(**kwargs) }, 'authorized_lazy', **kwargs) + def authorized_lazy(*args, **kwargs) + authorized_span(proc { super }, 'authorized_lazy', **kwargs) end def resolve_type_span(callable, span_key, **kwargs) @@ -111,12 +111,12 @@ def resolve_type_span(callable, span_key, **kwargs) trace(callable, span_key, platform_key, **kwargs) end - def resolve_type(**kwargs) - resolve_type_span(proc { super(**kwargs) }, 'resolve_type', **kwargs) + def resolve_type(*args, **kwargs) + resolve_type_span(proc { super }, 'resolve_type', **kwargs) end - def resolve_type_lazy(**kwargs) - resolve_type_span(proc { super(**kwargs) }, 'resolve_type_lazy', **kwargs) + def resolve_type_lazy(*args, **kwargs) + resolve_type_span(proc { super }, 'resolve_type_lazy', **kwargs) end include ::GraphQL::Tracing::PlatformTrace @@ -128,15 +128,15 @@ def resolve_type_lazy(**kwargs) # def prepare_span(key, data, span) # end - def platform_field_key(field) + def platform_field_key(field, *args, **kwargs) field.path end - def platform_authorized_key(type) + def platform_authorized_key(type, *args, **kwargs) "#{type.graphql_name}.authorized" end - def platform_resolve_type_key(type) + def platform_resolve_type_key(type, *args, **kwargs) "#{type.graphql_name}.resolve_type" end diff --git a/sig/datadog/tracing/contrib/graphql/unified_trace.rbs b/sig/datadog/tracing/contrib/graphql/unified_trace.rbs index ee5515fae68..6e4aae46582 100644 --- a/sig/datadog/tracing/contrib/graphql/unified_trace.rbs +++ b/sig/datadog/tracing/contrib/graphql/unified_trace.rbs @@ -10,7 +10,7 @@ module Datadog @service_name: String? @has_prepare_span: bool - def initialize: (?analytics_enabled: bool, ?analytics_sample_rate: Float, ?service: String?, **Hash[Symbol, Object] rest) -> self + def initialize: (?analytics_enabled: bool, ?analytics_sample_rate: Float, ?service: String?, **Hash[Symbol, Object] kwargs) -> self type lexerArray = Array[Integer | Symbol | String | nil | lexerArray] From ffc51f2e3b2a06cb6547041375fa1adcf4985327 Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Mon, 24 Jun 2024 13:55:42 +0200 Subject: [PATCH 10/11] Added comments for clarifications, removed redundent ones, and added documentation for prepare_span --- docs/GettingStarted.md | 17 +++++++++++++++++ .../tracing/contrib/graphql/unified_trace.rb | 14 +++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 4301be2b0f8..3e3f2f9177f 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -853,6 +853,23 @@ end Do _NOT_ `instrument :graphql` in `Datadog.configure` if you choose to configure manually, as to avoid double tracing. These two means of configuring GraphQL tracing are considered mutually exclusive. +**Adding custom tags to Datadog spans** + +You can add custom tags to Datadog spans by implementing the `prepare_span` method in a subclass, then manually configuring your schema. + +```ruby +class YourSchema < GraphQL::Schema + module CustomTracing + include Datadog::Tracing::Contrib::GraphQL::UnifiedTrace + def prepare_span(trace_key, data, span) + span.set_tag("custom:#{trace_key}", data.keys.sort.join(",")) + end + end + + trace_with CustomTracing +end +``` + ### gRPC The `grpc` integration adds both client and server interceptors, which run as middleware before executing the service's remote procedure call. As gRPC applications are often distributed, the integration shares trace information between client and server. diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index a6b7e225e8c..b03c0c575ae 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -6,7 +6,10 @@ module Datadog module Tracing module Contrib module GraphQL - # These methods will be called by the GraphQL runtime to trace the execution of queries + # These methods will be called by the GraphQL runtime to trace the execution of queries. + # This tracer differs from the upstream one as it follows the unified naming convention specification, + # which is required to use features such as API Catalog. + # DEV-3.0: This tracer should be the default one in the next major version. module UnifiedTrace # @param analytics_enabled [Boolean] Deprecated # @param analytics_sample_rate [Float] Deprecated @@ -71,6 +74,7 @@ def execute_query_lazy(*args, query:, multiplex:, **kwargs) end def execute_field_span(callable, span_key, **kwargs) + # @platform_key_cache is initialized upstream, in ::GraphQL::Tracing::PlatformTrace platform_key = @platform_key_cache[UnifiedTrace].platform_field_key_cache[kwargs[:field]] if platform_key @@ -85,7 +89,6 @@ def execute_field_span(callable, span_key, **kwargs) end def execute_field(*args, **kwargs) - # kwargs[:arguments] is { id => 1 } for 'user(id: 1) { name }'. This is what we want to send to the WAF. execute_field_span(proc { super }, 'resolve', **kwargs) end @@ -121,13 +124,6 @@ def resolve_type_lazy(*args, **kwargs) include ::GraphQL::Tracing::PlatformTrace - # 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(field, *args, **kwargs) field.path end From 841371d7c9b2764be3d8c937209848a29649a8f4 Mon Sep 17 00:00:00 2001 From: Victor Pellan Date: Mon, 24 Jun 2024 14:50:55 +0200 Subject: [PATCH 11/11] Replaced provided_variables by variables.storage --- lib/datadog/tracing/contrib/graphql/unified_trace.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/datadog/tracing/contrib/graphql/unified_trace.rb b/lib/datadog/tracing/contrib/graphql/unified_trace.rb index b03c0c575ae..1f4ba8f73e8 100644 --- a/lib/datadog/tracing/contrib/graphql/unified_trace.rb +++ b/lib/datadog/tracing/contrib/graphql/unified_trace.rb @@ -58,7 +58,7 @@ def execute_query(*args, query:, **kwargs) span.set_tag('graphql.source', query.query_string) span.set_tag('graphql.operation.type', query.selected_operation.operation_type) span.set_tag('graphql.operation.name', query.selected_operation_name) if query.selected_operation_name - query.provided_variables.each do |key, value| + query.variables.instance_variable_get(:@storage).each do |key, value| span.set_tag("graphql.variables.#{key}", value) end end