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 bd65f7d88..0d4c3b0ad 100644 --- a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb +++ b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb @@ -13,6 +13,8 @@ module Tracers # GraphQLTracer contains the OpenTelemetry tracer implementation compatible with # the GraphQL tracer API class GraphQLTracer < ::GraphQL::Tracing::PlatformTracing + DEFAULT_HASH = {}.freeze + self.platform_keys = { 'lex' => 'graphql.lex', 'parse' => 'graphql.parse', @@ -86,24 +88,60 @@ def config end 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[:type]&.graphql_name - attributes['graphql.lazy'] = key == 'authorized_lazy' - when 'resolve_type', 'resolve_type_lazy' - attributes['graphql.type.name'] = data[:type]&.graphql_name - attributes['graphql.lazy'] = key == 'resolve_type_lazy' + when 'execute_field' + field_attr_cache = data[:query].context.namespace(:otel_attrs)[:execute_field_attrs] ||= attr_cache do |field| + attrs = {} + attrs['graphql.field.parent'] = field.owner.graphql_name if field.owner.graphql_name + attrs['graphql.field.name'] = field.graphql_name if field.graphql_name + attrs['graphql.lazy'] = false + attrs.freeze + end + field_attr_cache[data[:field]] + when 'execute_field_lazy' + lazy_field_attr_cache = data[:query].context.namespace(:otel_attrs)[:execute_field_lazy_attrs] ||= attr_cache do |field| + attrs = {} + attrs['graphql.field.parent'] = field.owner.graphql_name if field.owner.graphql_name + attrs['graphql.field.name'] = field.graphql_name if field.graphql_name + attrs['graphql.lazy'] = true + attrs.freeze + end + lazy_field_attr_cache[data[:field]] + when 'authorized', 'resolve_type' + type_attrs_cache = data[:context].namespace(:otel_attrs)[:type_attrs] ||= attr_cache do |type| + attrs = {} + attrs['graphql.type.name'] = type.graphql_name if type.graphql_name + attrs['graphql.lazy'] = false + attrs.freeze + end + type_attrs_cache[data[:type]] + when 'authorized_lazy', 'resolve_type_lazy' + type_lazy_attrs_cache = data[:context].namespace(:otel_attrs)[:type_lazy_attrs] ||= attr_cache do |type| + attrs = {} + attrs['graphql.type.name'] = type.graphql_name if type.graphql_name + attrs['graphql.lazy'] = true + attrs.freeze + end + type_lazy_attrs_cache[data[:type]] 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 - attributes['graphql.document'] = data[:query].query_string + attrs = {} + attrs['graphql.document'] = data[:query].query_string if data[:query].query_string + # rubocop:disable Style/SafeNavigation - using safe navigation creates more objects, we want to avoid this + attrs['graphql.operation.type'] = data[:query].selected_operation.operation_type if data[:query].selected_operation && data[:query].selected_operation.operation_type + # rubocop:enable Style/SafeNavigation + attrs['graphql.operation.name'] = data[:query].selected_operation_name || 'anonymous' + attrs.freeze + else + DEFAULT_HASH + end + end + + def attr_cache + cache_h = Hash.new do |h, k| + h[k] = yield(k) end - attributes + cache_h.compare_by_identity + cache_h end end end 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 0813c22c0..61908a79d 100644 --- a/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb +++ b/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb @@ -83,7 +83,8 @@ it 'omits nil attributes for execute_query' do expected_attributes = { 'graphql.operation.type' => 'query', - 'graphql.document' => '{ simpleField }' + 'graphql.document' => '{ simpleField }', + 'graphql.operation.name' => 'anonymous' } SomeGraphQLAppSchema.execute('{ simpleField }') @@ -141,7 +142,7 @@ it 'includes attributes using platform types' do skip if uses_platform_interfaces? expected_attributes = { - 'graphql.field.parent' => 'Car', # type name, not interface + 'graphql.field.parent' => 'Vehicle', # interface name, not type 'graphql.field.name' => 'model', 'graphql.lazy' => false }