diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 0209cdd20a9..40f723de3ea 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -848,24 +848,26 @@ The `instrument :graphql` method accepts the following parameters. Additional op | Key | Description | Default | | --- | ----------- | ------- | | `schemas` | 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` | Enable to instrument with deprecated `GraphQL::Tracing::DataDogTracing`. Default is `false`, using `GraphQL::Tracing::DataDogTrace` | `false` | | `service_name` | Service name used for graphql instrumentation | `'ruby-graphql'` | **Manually configuring GraphQL schemas** -If you prefer to individually configure the tracer settings for a schema (e.g. you have multiple schemas with different service names), in the schema definition, you can add the following [using the GraphQL API](http://graphql-ruby.org/queries/tracing.html): +If you prefer to individually configure the tracer settings for a schema (e.g. you have multiple schemas), in the schema definition, you can add the following [using the GraphQL API](http://graphql-ruby.org/queries/tracing.html): + +With `GraphQL::Tracing::DataDogTrace` ```ruby -# Class-based schema class YourSchema < GraphQL::Schema - use(GraphQL::Tracing::DataDogTracing) + trace_with GraphQL::Tracing::DataDogTrace end ``` - -Or you can modify an already defined schema: +or with `GraphQL::Tracing::DataDogTracing` (deprecated) ```ruby -# Class-based schema -YourSchema.use(GraphQL::Tracing::DataDogTracing) +class YourSchema < GraphQL::Schema + use(GraphQL::Tracing::DataDogTracing) +end ``` **Note**: This integration does not support define-style schemas. Only class-based schemas are supported. diff --git a/lib/datadog/tracing/contrib/graphql/configuration/settings.rb b/lib/datadog/tracing/contrib/graphql/configuration/settings.rb index 0f7419ef356..bf87bac96ee 100644 --- a/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +++ b/lib/datadog/tracing/contrib/graphql/configuration/settings.rb @@ -34,6 +34,11 @@ class Settings < Contrib::Configuration::Settings end option :service_name + + option :with_deprecated_tracer do |o| + o.type :bool + o.default false + end end end end diff --git a/lib/datadog/tracing/contrib/graphql/integration.rb b/lib/datadog/tracing/contrib/graphql/integration.rb index 1ffc5b63fe6..5d0b051f82d 100644 --- a/lib/datadog/tracing/contrib/graphql/integration.rb +++ b/lib/datadog/tracing/contrib/graphql/integration.rb @@ -18,8 +18,7 @@ def self.version end def self.loaded? - !defined?(::GraphQL).nil? \ - && !defined?(::GraphQL::Tracing::DataDogTracing).nil? + !defined?(::GraphQL).nil? end # Breaking changes are introduced in `2.2.6` and have been backported to @@ -37,6 +36,10 @@ def self.compatible? ) end + def self.trace_supported? + version >= Gem::Version.new('2.0.19') + end + def new_configuration Configuration::Settings.new end diff --git a/lib/datadog/tracing/contrib/graphql/patcher.rb b/lib/datadog/tracing/contrib/graphql/patcher.rb index 92ef4f9481f..4a5a705e98f 100644 --- a/lib/datadog/tracing/contrib/graphql/patcher.rb +++ b/lib/datadog/tracing/contrib/graphql/patcher.rb @@ -1,5 +1,7 @@ require_relative '../analytics' require_relative '../patcher' +require_relative 'tracing_patcher' +require_relative 'trace_patcher' module Datadog module Tracing @@ -16,19 +18,18 @@ def target_version end def patch - schemas = configuration[:schemas] - - if schemas.empty? - ::GraphQL::Schema.tracer(::GraphQL::Tracing::DataDogTracing.new(**trace_options)) - else - schemas.each do |schema| - if schema.respond_to? :use - schema.use(::GraphQL::Tracing::DataDogTracing, **trace_options) - else - Datadog.logger.warn("Unable to patch #{schema}, please migrate to class-based schema.") - end + unless configuration[:with_deprecated_tracer] + if Integration.trace_supported? + TracePatcher.patch!(schemas, trace_options) + else + Datadog.logger.warn( + "GraphQL version (#{target_version}) does not support GraphQL::Tracing::DataDogTrace. "\ + 'Falling back to GraphQL::Tracing::DataDogTracing.' + ) end end + + TracingPatcher.patch!(schemas, trace_options) end def trace_options @@ -42,6 +43,10 @@ def trace_options def configuration Datadog.configuration.tracing[:graphql] end + + def schemas + configuration[:schemas] + end end end end diff --git a/lib/datadog/tracing/contrib/graphql/trace_patcher.rb b/lib/datadog/tracing/contrib/graphql/trace_patcher.rb new file mode 100644 index 00000000000..ea4d90797e7 --- /dev/null +++ b/lib/datadog/tracing/contrib/graphql/trace_patcher.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Datadog + module Tracing + module Contrib + module GraphQL + # Provides instrumentation for `graphql` through with GraphQL's trace + module TracePatcher + module_function + + def patch!(schemas, options) + if schemas.empty? + ::GraphQL::Schema.trace_with(::GraphQL::Tracing::DataDogTrace, **options) + else + schemas.each do |schema| + schema.trace_with(::GraphQL::Tracing::DataDogTrace, **options) + end + end + end + end + end + end + end +end diff --git a/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb b/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb new file mode 100644 index 00000000000..9a662c0c30c --- /dev/null +++ b/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Datadog + module Tracing + module Contrib + module GraphQL + # Provides instrumentation for `graphql` through the GraphQL's tracing + module TracingPatcher + module_function + + def patch!(schemas, options) + if schemas.empty? + ::GraphQL::Schema.tracer(::GraphQL::Tracing::DataDogTracing.new(**options)) + else + schemas.each do |schema| + if schema.respond_to? :use + schema.use(::GraphQL::Tracing::DataDogTracing, **options) + else + Datadog.logger.warn("Unable to patch #{schema}: Please migrate to class-based schema.") + end + 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 7be4e0decdd..1487c1d14a0 100644 --- a/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/configuration/settings_spec.rb @@ -28,4 +28,30 @@ end end end + + describe 'with_deprecated_tracer' do + context 'when default' do + it do + settings = described_class.new + + expect(settings.with_deprecated_tracer).to eq(false) + end + end + + context 'when given `true`' do + it do + settings = described_class.new(with_deprecated_tracer: true) + + expect(settings.with_deprecated_tracer).to eq(true) + end + end + + context 'when given `false`' do + it do + settings = described_class.new(with_deprecated_tracer: false) + + expect(settings.with_deprecated_tracer).to eq(false) + end + end + end end diff --git a/spec/datadog/tracing/contrib/graphql/integration_spec.rb b/spec/datadog/tracing/contrib/graphql/integration_spec.rb index c480d815328..038d0eb983e 100644 --- a/spec/datadog/tracing/contrib/graphql/integration_spec.rb +++ b/spec/datadog/tracing/contrib/graphql/integration_spec.rb @@ -22,27 +22,19 @@ describe '.loaded?' do subject(:loaded?) { described_class.loaded? } - context 'when neither GraphQL or GraphQL::Tracing::DataDogTracing are defined' do + context 'when GraphQL is not defined' do before do hide_const('GraphQL') - hide_const('GraphQL::Tracing::DataDogTracing') end it { is_expected.to be false } end - context 'when only GraphQL is defined' do + context 'when GraphQL is defined' do before do stub_const('GraphQL', Class.new) - hide_const('GraphQL::Tracing::DataDogTracing') end - it { is_expected.to be false } - end - - context 'when GraphQL::Tracing::DataDogTracing is defined' do - before { stub_const('GraphQL::Tracing::DataDogTracing', Class.new) } - it { is_expected.to be true } end end @@ -96,6 +88,20 @@ it { is_expected.to be(true) } end + describe '.trace_supported?' do + subject(:trace_supported) { described_class.trace_supported? } + + context 'with version `2.0.19`' do + include_context 'loaded gems', graphql: '2.0.19' + it { is_expected.to be true } + end + + context 'with version `2.0.18`' do + include_context 'loaded gems', graphql: decrement_gem_version('2.0.19') + it { is_expected.to be false } + end + end + describe '#default_configuration' do subject(:default_configuration) { integration.default_configuration } diff --git a/spec/datadog/tracing/contrib/graphql/patcher_spec.rb b/spec/datadog/tracing/contrib/graphql/patcher_spec.rb new file mode 100644 index 00000000000..8f008492b14 --- /dev/null +++ b/spec/datadog/tracing/contrib/graphql/patcher_spec.rb @@ -0,0 +1,147 @@ +require 'datadog/tracing/contrib/support/spec_helper' +require 'datadog/tracing/contrib/graphql/test_schema_examples' +require 'datadog/tracing/contrib/graphql/tracing_patcher' +require 'datadog/tracing/contrib/graphql/trace_patcher' + +require 'ddtrace' + +RSpec.describe Datadog::Tracing::Contrib::GraphQL::Patcher do + around do |example| + remove_patch!(:graphql) + Datadog.configuration.reset! + Datadog.configuration.tracing[:graphql].reset! + + without_warnings do + example.run + end + + remove_patch!(:graphql) + Datadog.configuration.reset! + Datadog.configuration.tracing[:graphql].reset! + end + + describe 'patch' do + context 'when graphql does not support trace' do + context 'with default configuration' do + it 'patches GraphQL' 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 + end + end + end + + context 'with 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_deprecated_tracer: true + end + end + end + + context 'with with_deprecated_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_deprecated_tracer: false + end + end + end + + context 'with given schema' 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( + [TestGraphQLSchema], + hash_including(:analytics_enabled, :analytics_sample_rate, :service) + ) + + Datadog.configure do |c| + c.tracing.instrument :graphql, schemas: [TestGraphQLSchema] + end + end + end + end + + context 'when graphql supports trace' do + context 'with default configuration' do + it 'patches GraphQL' 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 + end + end + end + + context 'with 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) + ) + expect_any_instance_of(Datadog::Core::Logger).not_to receive(:warn) + + Datadog.configure do |c| + c.tracing.instrument :graphql, with_deprecated_tracer: true + end + end + end + + context 'with with_deprecated_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_deprecated_tracer: false + end + end + end + + context 'with given schema' 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( + [TestGraphQLSchema], + 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, schemas: [TestGraphQLSchema] + end + end + end + end + end +end diff --git a/spec/datadog/tracing/contrib/graphql/test_helpers.rb b/spec/datadog/tracing/contrib/graphql/test_helpers.rb new file mode 100644 index 00000000000..328c37f9547 --- /dev/null +++ b/spec/datadog/tracing/contrib/graphql/test_helpers.rb @@ -0,0 +1,19 @@ +module Datadog + module GraphQLTestHelpers + module_function + + # Workaround to reset internal state + def reset_schema_cache!(s) + [ + '@own_tracers', + '@trace_modes', + '@trace_class', + '@tracers', + '@graphql_definition', + '@own_trace_modes', + ].each do |i_var| + s.remove_instance_variable(i_var) if s.instance_variable_defined?(i_var) + end + end + end +end diff --git a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb index 335b6e0900d..15e7e9f83d5 100644 --- a/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb +++ b/spec/datadog/tracing/contrib/graphql/test_schema_examples.rb @@ -1,6 +1,6 @@ -LogHelpers.without_warnings do - require 'graphql' -end +require 'graphql' + +require_relative 'test_helpers' class TestUserType < ::GraphQL::Schema::Object field :id, ::GraphQL::Types::ID, null: false @@ -24,6 +24,16 @@ class TestGraphQLSchema < ::GraphQL::Schema end RSpec.shared_examples 'graphql instrumentation' 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 } }') } diff --git a/spec/datadog/tracing/contrib/graphql/trace_patcher_spec.rb b/spec/datadog/tracing/contrib/graphql/trace_patcher_spec.rb new file mode 100644 index 00000000000..aea073160d0 --- /dev/null +++ b/spec/datadog/tracing/contrib/graphql/trace_patcher_spec.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/trace_patcher' + +require 'ddtrace' + +RSpec.describe Datadog::Tracing::Contrib::GraphQL::TracePatcher, + 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 + before do + described_class.patch!([], {}) + end + end + end + + context 'with specified schemas configuration' do + it_behaves_like 'graphql instrumentation' do + before do + described_class.patch!([TestGraphQLSchema], {}) + end + end + end + end + end diff --git a/spec/datadog/tracing/contrib/graphql/tracer_spec.rb b/spec/datadog/tracing/contrib/graphql/tracer_spec.rb deleted file mode 100644 index befaf1f4b0b..00000000000 --- a/spec/datadog/tracing/contrib/graphql/tracer_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'datadog/tracing/contrib/support/spec_helper' -require 'datadog/tracing/contrib/graphql/test_schema_examples' - -require 'ddtrace' -RSpec.describe 'GraphQL patcher' do - # GraphQL generates tons of warnings. - # This suppresses those warnings. - around do |example| - remove_patch!(:graphql) - Datadog.configuration.tracing[:graphql].reset! - reset_schema_cache!(::GraphQL::Schema) - reset_schema_cache!(TestGraphQLSchema) - - without_warnings do - example.run - end - - remove_patch!(:graphql) - Datadog.configuration.tracing[:graphql].reset! - reset_schema_cache!(::GraphQL::Schema) - reset_schema_cache!(TestGraphQLSchema) - end - - context 'with default configuration' do - it_behaves_like 'graphql instrumentation' do - before do - Datadog.configure do |c| - c.tracing.instrument :graphql - end - end - end - end - - context 'with empty schema configuration' do - it_behaves_like 'graphql instrumentation' do - before do - Datadog.configure do |c| - c.tracing.instrument :graphql, schemas: [] - end - end - end - end - - context 'with specified schemas configuration' do - it_behaves_like 'graphql instrumentation' do - before do - Datadog.configure do |c| - c.tracing.instrument :graphql, schemas: [TestGraphQLSchema] - end - end - end - end - - context 'when given something else' do - it do - expect_any_instance_of(Datadog::Core::Logger).to receive(:warn).with(/Unable to patch/) - - Datadog.configure do |c| - c.tracing.instrument :graphql, schemas: [OpenStruct.new] - end - end - end - - # Workaround to reset internal state - def reset_schema_cache!(s) - [ - '@own_tracers', - '@trace_modes', - '@trace_class', - '@tracers', - '@graphql_definition', - '@own_trace_modes', - ].each do |i_var| - s.remove_instance_variable(i_var) if s.instance_variable_defined?(i_var) - end - end -end diff --git a/spec/datadog/tracing/contrib/graphql/tracing_patcher_spec.rb b/spec/datadog/tracing/contrib/graphql/tracing_patcher_spec.rb new file mode 100644 index 00000000000..15129d8f510 --- /dev/null +++ b/spec/datadog/tracing/contrib/graphql/tracing_patcher_spec.rb @@ -0,0 +1,33 @@ +require 'datadog/tracing/contrib/support/spec_helper' +require 'datadog/tracing/contrib/graphql/test_schema_examples' +require 'datadog/tracing/contrib/graphql/tracing_patcher' + +require 'ddtrace' + +RSpec.describe Datadog::Tracing::Contrib::GraphQL::TracingPatcher do + describe '#patch!' do + context 'with empty schema configuration' do + it_behaves_like 'graphql instrumentation' do + before do + described_class.patch!([], {}) + end + end + end + + context 'with specified schemas configuration' do + it_behaves_like 'graphql instrumentation' do + before do + described_class.patch!([TestGraphQLSchema], {}) + end + end + end + + context 'when given something else' do + it do + expect_any_instance_of(Datadog::Core::Logger).to receive(:warn).with(/Unable to patch/) + + described_class.patch!([OpenStruct.new], {}) + end + end + end +end