From b6b0e13144535995481df3e4b7664bc80db4f696 Mon Sep 17 00:00:00 2001 From: Rob Van Gennip Date: Tue, 24 Jan 2023 14:08:22 -0500 Subject: [PATCH] Normalize span names for easier aggregation analysis --- instrumentation/graphql/README.md | 5 + .../graphql/instrumentation.rb | 6 ++ .../graphql/tracers/graphql_tracer.rb | 28 +++++- .../graphql/tracers/graphql_tracer_test.rb | 96 ++++++++++++++++++- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/instrumentation/graphql/README.md b/instrumentation/graphql/README.md index c10448ae3a..3bd20d6e68 100644 --- a/instrumentation/graphql/README.md +++ b/instrumentation/graphql/README.md @@ -41,6 +41,11 @@ OpenTelemetry::SDK.configure do |c| enable_platform_authorized: false, # enable_platform_resolve_type maps to the resolve_type and resolve_type_lazy keys enable_platform_resolve_type: false + + # Controls if platform tracing (field/authorized/resolve_type) + # should use the legacy span names (e.g. "MyType.myField") or the + # new normalized span names (e.g. "graphql.execute_field"). + legacy_platform_span_names: false } end ``` diff --git a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/instrumentation.rb b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/instrumentation.rb index 73f2c49607..dc2329690f 100644 --- a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/instrumentation.rb +++ b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/instrumentation.rb @@ -35,6 +35,11 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base # The enable_platform_resolve_type key expects a boolean value, # and enables the tracing of "resolve_type" and "resolve_type_lazy". # + # The legacy_platform_span_names key expects a boolean value, + # and controls if platform tracing (field/authorized/resolve_type) + # should use the legacy span names (e.g. "MyType.myField") or the + # new normalized span names (e.g. "graphql.execute_field"). + # # The schemas key expects an array of Schemas, and is used to specify # which schemas are to be instrumented. If this value is not supplied # the default behaviour is to instrument all schemas. @@ -42,6 +47,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base option :enable_platform_field, default: false, validate: :boolean option :enable_platform_authorized, default: false, validate: :boolean option :enable_platform_resolve_type, default: false, validate: :boolean + option :legacy_platform_span_names, default: true, validate: :boolean private diff --git a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb index 58fe5a4de0..d1afc3814f 100644 --- a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb +++ b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb @@ -45,19 +45,31 @@ def platform_trace(platform_key, key, data) # rubocop:disable Metrics/Cyclomatic def platform_field_key(type, field) return unless config[:enable_platform_field] - "#{type.graphql_name}.#{field.graphql_name}" + if config[:legacy_platform_span_names] + "#{type.graphql_name}.#{field.graphql_name}" + else + 'graphql.execute_field' + end end def platform_authorized_key(type) return unless config[:enable_platform_authorized] - "#{type.graphql_name}.authorized" + if config[:legacy_platform_span_names] + "#{type.graphql_name}.authorized" + else + 'graphql.authorized' + end end def platform_resolve_type_key(type) return unless config[:enable_platform_resolve_type] - "#{type.graphql_name}.resolve_type" + if config[:legacy_platform_span_names] + "#{type.graphql_name}.resolve_type" + else + 'graphql.resolve_type' + end end private @@ -73,6 +85,16 @@ def config def attributes_for(key, data) attributes = {} case key + when 'execute_field', 'execute_field_lazy' + attributes['graphql.field.parent'] = data[:owner].graphql_name # owner is the concrete type, not interface + attributes['graphql.field.name'] = data[:field].graphql_name + attributes['graphql.lazy'] = key == 'execute_field_lazy' + when 'authorized', 'authorized_lazy' + attributes['graphql.type.name'] = data.fetch(:type).graphql_name + attributes['graphql.lazy'] = key == 'authorized_lazy' + when 'resolve_type', 'resolve_type_lazy' + attributes['graphql.type.name'] = data.fetch(:type).graphql_name + attributes['graphql.lazy'] = key == 'resolve_type_lazy' when 'execute_query' attributes['graphql.operation.name'] = data[:query].selected_operation_name if data[:query].selected_operation_name attributes['graphql.operation.type'] = data[:query].selected_operation.operation_type diff --git a/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb b/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb index f24c3ce064..bfb1d025f5 100644 --- a/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb +++ b/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb @@ -128,6 +128,35 @@ end end + describe 'when platform_field is enabled without legacy naming' do + let(:config) { { enable_platform_field: true, legacy_platform_span_names: false } } + + it 'traces execute_field' do + SomeGraphQLAppSchema.execute(query_string, variables: { id: 1 }) + + span = spans.find do |s| + s.name == 'graphql.execute_field' && + s.attributes['graphql.field.parent'] == 'Query' && + s.attributes['graphql.field.name'] == 'resolvedField' + end + _(span).wont_be_nil + end + + it 'includes attributes' do + expected_attributes = { + 'graphql.field.parent' => 'Car', # type name, not interface + 'graphql.field.name' => 'model', + 'graphql.lazy' => false + } + + SomeGraphQLAppSchema.execute('{ vehicle { model } }') + + span = spans.find { |s| s.name == 'graphql.execute_field' && s.attributes['graphql.field.name'] == 'model' } + _(span).wont_be_nil + _(span.attributes.to_h).must_equal(expected_attributes) + end + end + describe 'when platform_authorized is enabled' do let(:config) { { enable_platform_authorized: true } } @@ -143,6 +172,41 @@ end end + describe 'when platform_authorized is enabled without legacy naming' do + let(:config) { { enable_platform_authorized: true, legacy_platform_span_names: false } } + + it 'traces .authorized' do + skip unless supports_authorized_and_resolved_types? + SomeGraphQLAppSchema.execute(query_string, variables: { id: 1 }) + + span = spans.find do |s| + s.name == 'graphql.authorized' && + s.attributes['graphql.type.name'] == 'Query' + end + _(span).wont_be_nil + + span = spans.find do |s| + s.name == 'graphql.authorized' && + s.attributes['graphql.type.name'] == 'SlightlyComplex' + end + _(span).wont_be_nil + end + + it 'includes attributes' do + skip unless supports_authorized_and_resolved_types? + expected_attributes = { + 'graphql.type.name' => 'OtherQuery', + 'graphql.lazy' => false + } + + SomeOtherGraphQLAppSchema.execute('{ simpleField }') + + span = spans.find { |s| s.name == 'graphql.authorized' } + _(span).wont_be_nil + _(span.attributes.to_h).must_equal(expected_attributes) + end + end + describe 'when platform_resolve_type is enabled' do let(:config) { { enable_platform_resolve_type: true } } @@ -155,6 +219,32 @@ end end + describe 'when platform_resolve_type is enabled without legacy naming' do + let(:config) { { enable_platform_resolve_type: true, legacy_platform_span_names: false } } + + it 'traces .resolve_type' do + skip unless supports_authorized_and_resolved_types? + SomeGraphQLAppSchema.execute('{ vehicle { __typename } }') + + span = spans.find { |s| s.name == 'graphql.resolve_type' && s.attributes['graphql.type.name'] == 'Vehicle' } + _(span).wont_be_nil + end + + it 'includes attributes' do + skip unless supports_authorized_and_resolved_types? + expected_attributes = { + 'graphql.type.name' => 'Vehicle', + 'graphql.lazy' => false + } + + SomeGraphQLAppSchema.execute('{ vehicle { __typename } }') + + span = spans.find { |s| s.name == 'graphql.resolve_type' } + _(span).wont_be_nil + _(span.attributes.to_h).must_equal(expected_attributes) + end + end + it 'traces validate with events' do SomeGraphQLAppSchema.execute( <<-GRAPHQL @@ -182,11 +272,13 @@ def supports_authorized_and_resolved_types? end module Old - Truck = Struct.new(:price) + Truck = Struct.new(:price, :model) end module Vehicle include GraphQL::Schema::Interface + + field :model, String, null: true, trace: true # Allow for this scalar to be traced end class Car < GraphQL::Schema::Object @@ -222,7 +314,7 @@ class QueryType < GraphQL::Schema::Object field :vehicle, Vehicle, null: true def vehicle - Old::Truck.new(50) + Old::Truck.new(50, 'Model T') end def simple_field