diff --git a/.rubocop.yml b/.rubocop.yml index cb74d3c07f2..220057d903c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -25,6 +25,7 @@ AllCops: - 'lib/datadog/profiling/pprof/pprof_pb.rb' - 'spec/**/**/interesting_backtrace_helper.rb' # This file needs quite a few bizarre code patterns by design - 'vendor/bundle/**/*' + - 'spec/datadog/tracing/contrib/grpc/support/gen/**/*.rb' # Skip protoc autogenerated code NewCops: disable # Don't allow new cops to be enabled implicitly. SuggestExtensions: false # Stop pushing suggestions constantly. diff --git a/docs/DevelopmentGuide.md b/docs/DevelopmentGuide.md index 1510bb80963..e744e422567 100644 --- a/docs/DevelopmentGuide.md +++ b/docs/DevelopmentGuide.md @@ -248,3 +248,17 @@ Datadog.configure do |c| } end ``` + +### Generating GRPC proto stubs for tests + +If you modify any of the `.proto` files under `./spec/datadog/tracing/contrib/grpc/support/proto` used for +testing the `grpc` integration, you'll need to regenerate the Ruby code by running: + +``` +$ docker run \ + --platform linux/amd64 \ + -v ${PWD}:/app \ + -w /app \ + ruby:latest \ + ./spec/datadog/tracing/contrib/grpc/support/gen_proto.sh +``` diff --git a/lib/datadog/tracing/contrib/ext.rb b/lib/datadog/tracing/contrib/ext.rb index 89a79f50bc0..02c66ac2747 100644 --- a/lib/datadog/tracing/contrib/ext.rb +++ b/lib/datadog/tracing/contrib/ext.rb @@ -18,6 +18,11 @@ module RPC TAG_SYSTEM = 'rpc.system' TAG_SERVICE = 'rpc.service' TAG_METHOD = 'rpc.method' + + module GRPC + TAG_STATUS_CODE = 'rpc.grpc.status_code' + TAG_FULL_METHOD = 'rpc.grpc.full_method' + end end module Messaging diff --git a/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb b/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb index a3680481d93..11c48e82a25 100644 --- a/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +++ b/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb @@ -4,6 +4,7 @@ require_relative '../../analytics' require_relative '../ext' require_relative '../../ext' +require_relative '../formatting' module Datadog module Tracing @@ -16,30 +17,39 @@ module DatadogInterceptor # sending the request to the server. class Client < Base def trace(keywords) - keywords[:metadata] ||= {} + formatter = GRPC::Formatting::FullMethodStringFormatter.new(keywords[:method]) options = { span_type: Tracing::Metadata::Ext::HTTP::TYPE_OUTBOUND, service: service_name, # Maintain client-side service name configuration - resource: format_resource(keywords[:method]) + resource: formatter.resource_name } Tracing.trace(Ext::SPAN_CLIENT, **options) do |span, trace| - annotate!(trace, span, keywords[:metadata], keywords[:call]) - - yield + annotate!(trace, span, keywords, formatter) + + begin + yield + rescue StandardError => e + code = e.is_a?(::GRPC::BadStatus) ? e.code : ::GRPC::Core::StatusCodes::UNKNOWN + span.set_tag(Contrib::Ext::RPC::GRPC::TAG_STATUS_CODE, code) + + raise + else + span.set_tag(Contrib::Ext::RPC::GRPC::TAG_STATUS_CODE, ::GRPC::Core::StatusCodes::OK) + end end end private - def annotate!(trace, span, metadata, call) - span.set_tags(metadata) + def annotate!(trace, span, keywords, formatter) + metadata = keywords[:metadata] || {} + call = keywords[:call] - span.set_tag(Contrib::Ext::RPC::TAG_SYSTEM, Ext::TAG_SYSTEM) + span.set_tags(metadata) span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT) - span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT) span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_CLIENT) @@ -48,6 +58,9 @@ def annotate!(trace, span, metadata, call) span.set_tag(Tracing::Metadata::Ext::TAG_PEER_SERVICE, span.service) end + span.set_tag(Contrib::Ext::RPC::TAG_SYSTEM, Ext::TAG_SYSTEM) + span.set_tag(Contrib::Ext::RPC::GRPC::TAG_FULL_METHOD, formatter.grpc_full_method) + host, _port = find_host_port(call) span.set_tag(Tracing::Metadata::Ext::TAG_PEER_HOSTNAME, host) if host @@ -62,14 +75,6 @@ def annotate!(trace, span, metadata, call) Datadog.logger.debug("GRPC client trace failed: #{e}") end - def format_resource(proto_method) - proto_method - .downcase - .split('/') - .reject(&:empty?) - .join('.') - end - def find_deadline(call) return unless call.respond_to?(:deadline) && call.deadline.is_a?(Time) diff --git a/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb b/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb index bec7a23a333..e91deeda327 100644 --- a/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +++ b/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb @@ -4,6 +4,7 @@ require_relative '../../analytics' require_relative '../ext' require_relative '../../ext' +require_relative '../formatting' module Datadog module Tracing @@ -17,11 +18,12 @@ module DatadogInterceptor # its tracing context with a parent client-side context class Server < Base def trace(keywords) - method = keywords[:method] + formatter = GRPC::Formatting::MethodObjectFormatter.new(keywords[:method]) + options = { span_type: Tracing::Metadata::Ext::HTTP::TYPE_INBOUND, service: service_name, # TODO: Remove server-side service name configuration - resource: format_resource(method), + resource: formatter.resource_name, on_error: error_handler } metadata = keywords[:call].metadata @@ -29,13 +31,18 @@ def trace(keywords) set_distributed_context!(metadata) Tracing.trace(Ext::SPAN_SERVICE, **options) do |span| - span.set_tag(Contrib::Ext::RPC::TAG_SYSTEM, Ext::TAG_SYSTEM) - span.set_tag(Contrib::Ext::RPC::TAG_SERVICE, method.owner.to_s) - span.set_tag(Contrib::Ext::RPC::TAG_METHOD, method.name) + annotate!(span, metadata, formatter) - annotate!(span, metadata) + begin + yield + rescue StandardError => e + code = e.is_a?(::GRPC::BadStatus) ? e.code : ::GRPC::Core::StatusCodes::UNKNOWN + span.set_tag(Contrib::Ext::RPC::GRPC::TAG_STATUS_CODE, code) - yield + raise + else + span.set_tag(Contrib::Ext::RPC::GRPC::TAG_STATUS_CODE, ::GRPC::Core::StatusCodes::OK) + end end end @@ -49,7 +56,7 @@ def set_distributed_context!(metadata) ) end - def annotate!(span, metadata) + def annotate!(span, metadata, formatter) metadata.each do |header, value| # Datadog propagation headers are considered internal implementation detail. next if header.to_s.start_with?(Tracing::Distributed::Datadog::TAGS_PREFIX) @@ -58,27 +65,21 @@ def annotate!(span, metadata) end span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_SERVER) - span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT) span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_SERVICE) + span.set_tag(Contrib::Ext::RPC::TAG_SYSTEM, Ext::TAG_SYSTEM) + span.set_tag(Contrib::Ext::RPC::TAG_SERVICE, formatter.legacy_grpc_service) + span.set_tag(Contrib::Ext::RPC::TAG_METHOD, formatter.legacy_grpc_method) + span.set_tag(Contrib::Ext::RPC::GRPC::TAG_FULL_METHOD, formatter.grpc_full_method) + # Set analytics sample rate Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled? # Measure service stats Contrib::Analytics.set_measured(span) rescue StandardError => e - Datadog.logger.debug("GRPC client trace failed: #{e}") - end - - def format_resource(proto_method) - proto_method - .owner - .to_s - .downcase - .split('::') - .<<(proto_method.name) - .join('.') + Datadog.logger.debug("GRPC server trace failed: #{e}") end end end diff --git a/lib/datadog/tracing/contrib/grpc/formatting.rb b/lib/datadog/tracing/contrib/grpc/formatting.rb new file mode 100644 index 00000000000..0a026f3d3ca --- /dev/null +++ b/lib/datadog/tracing/contrib/grpc/formatting.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Datadog + module Tracing + module Contrib + module GRPC + module Formatting + VALUE_UNKNOWN = 'unknown' + + # A class to extract GRPC span attributes from the GRPC implementing class method object. + class MethodObjectFormatter + # grpc_full_method is a string containing all the rpc method information (from the Protobuf definition) + # in a single string with the following format: /$package.$service/$method + attr_reader :grpc_full_method + + # legacy_grpc_service is built using the Ruby GRPC service implementation package and class name instead + # of the rpc interface representation from Protobuf. It's kept for compatibility. + attr_reader :legacy_grpc_service + + # legacy_grpc_method is built using the Ruby GRPC service implementation method name instead of the rpc + # interface representation from Protobuf. It's kept for compatibility. + attr_reader :legacy_grpc_method + + # resource_name is used for the span resource name. + attr_reader :resource_name + + def initialize(grpc_method_object) + @grpc_full_method = format_full_method(grpc_method_object) + @resource_name = format_resource_name(grpc_method_object) + @legacy_grpc_method = extract_legacy_grpc_method(grpc_method_object) + @legacy_grpc_service = extract_legacy_grpc_service(grpc_method_object) + end + + private + + def format_full_method(grpc_method_object) + service = extract_grpc_service(grpc_method_object) + method = extract_grpc_method(grpc_method_object) + "/#{service}/#{method}" + end + + def extract_grpc_service(grpc_method_object) + owner = grpc_method_object.owner + return VALUE_UNKNOWN unless owner.instance_variable_defined?(:@service_name) + + # Ruby protoc generated code includes this variable which directly contains the value from the original + # protobuf definition + owner.service_name.to_s + end + + # extract_grpc_method attempts to find the original method name from the Protobuf file definition, + # since grpc gem forces the implementation method name to be in snake_case. + def extract_grpc_method(grpc_method_object) + owner = grpc_method_object.owner + + return VALUE_UNKNOWN unless owner.instance_variable_defined?(:@rpc_descs) + + method, = owner.rpc_descs.find do |k, _| + ::GRPC::GenericService.underscore(k.to_s) == grpc_method_object.name.to_s + end + + return VALUE_UNKNOWN if method.nil? + + method.to_s + end + + def extract_legacy_grpc_service(grpc_method_object) + grpc_method_object.owner.to_s + end + + def extract_legacy_grpc_method(grpc_method_object) + grpc_method_object.name + end + + def format_resource_name(grpc_method_object) + grpc_method_object + .owner + .to_s + .downcase + .split('::') + .<<(grpc_method_object.name) + .join('.') + end + end + + # A class to extract GRPC span attributes from the full method string. + class FullMethodStringFormatter + # grpc_full_method is a string containing all the rpc method information (from the Protobuf definition) + # in a single string with the following format: /$package.$service/$method + attr_reader :grpc_full_method + + # resource_name is used for the span resource name. + attr_reader :resource_name + + def initialize(grpc_full_method) + @grpc_full_method = grpc_full_method + @resource_name = format_resource_name(grpc_full_method) + end + + private + + def format_resource_name(grpc_full_method) + grpc_full_method + .downcase + .split('/') + .reject(&:empty?) + .join('.') + end + end + end + end + end + end +end diff --git a/sig/datadog/tracing/contrib/grpc/formatting.rbs b/sig/datadog/tracing/contrib/grpc/formatting.rbs new file mode 100644 index 00000000000..5eec228cd6f --- /dev/null +++ b/sig/datadog/tracing/contrib/grpc/formatting.rbs @@ -0,0 +1,42 @@ +module Datadog + module Tracing + module Contrib + module GRPC + module Formatting + VALUE_UNKNOWN: "unknown" + class MethodObjectFormatter + attr_reader grpc_full_method: String + attr_reader legacy_grpc_service: String + attr_reader legacy_grpc_method: String + attr_reader resource_name: String + + def initialize: (untyped grpc_method_object) -> void + + private + + def format_full_method: (untyped grpc_method_object) -> ::String + + def extract_grpc_service: (untyped grpc_method_object) -> String + def extract_grpc_method: (untyped grpc_method_object) -> String + + def extract_legacy_grpc_service: (untyped grpc_method_object) -> String + + def extract_legacy_grpc_method: (untyped grpc_method_object) -> String + + def format_resource_name: (untyped grpc_method_object) -> String + end + class FullMethodStringFormatter + attr_reader grpc_full_method: String + attr_reader resource_name: String + + def initialize: (String grpc_full_method) -> void + + private + + def format_resource_name: (String grpc_full_method) -> String + end + end + end + end + end +end diff --git a/spec/datadog/tracing/contrib/grpc/datadog_interceptor/server_spec.rb b/spec/datadog/tracing/contrib/grpc/datadog_interceptor/server_spec.rb index a8ba4aefb18..c3e3f0a90df 100644 --- a/spec/datadog/tracing/contrib/grpc/datadog_interceptor/server_spec.rb +++ b/spec/datadog/tracing/contrib/grpc/datadog_interceptor/server_spec.rb @@ -84,7 +84,7 @@ expect(span).to have_error_message('test error') expect(span).to have_error_type('TestError') expect(span).to have_error_stack(include('server_spec.rb')) - expect(span.get_tag('rpc.system')).to eq 'grpc' + expect(span.get_tag('rpc.system')).to eq('grpc') expect(span.get_tag('span.kind')).to eq('server') end end diff --git a/spec/datadog/tracing/contrib/grpc/integration_test_spec.rb b/spec/datadog/tracing/contrib/grpc/integration_test_spec.rb index aaa5e5f6a25..791c3193744 100644 --- a/spec/datadog/tracing/contrib/grpc/integration_test_spec.rb +++ b/spec/datadog/tracing/contrib/grpc/integration_test_spec.rb @@ -62,6 +62,68 @@ before { run_request_reply } it_behaves_like 'associates child spans with the parent' + + it 'both server and client spans have correct tags' do + server_span = spans.find { |span| span.name == 'grpc.service' } + client_span = spans.find { |span| span.name == 'grpc.client' } + + expect(server_span.get_tag('span.kind')).to eq('server') + expect(server_span.get_tag('rpc.system')).to eq('grpc') + expect(server_span.get_tag('rpc.grpc.status_code')).to eq(0) + expect(server_span.get_tag('rpc.grpc.full_method')).to eq('/ruby.test.Testing/Basic') + + # the following tags should be set by backend + expect(server_span.get_tag('rpc.grpc.package')).to eq(nil) + + # the following tags should be set by the backend but they are kept for now to not make breaking changes + expect(server_span.get_tag('rpc.service')).to eq('GRPCHelper::TestService') + expect(server_span.get_tag('rpc.method')).to eq('basic') + + expect(client_span.get_tag('span.kind')).to eq('client') + expect(client_span.get_tag('rpc.system')).to eq('grpc') + expect(client_span.get_tag('rpc.grpc.status_code')).to eq(0) + expect(client_span.get_tag('rpc.grpc.full_method')).to eq('/ruby.test.Testing/Basic') + + # the following tags should be set by backend + expect(client_span.get_tag('rpc.service')).to eq(nil) + expect(client_span.get_tag('rpc.method')).to eq(nil) + expect(client_span.get_tag('rpc.grpc.package')).to eq(nil) + end + end + + context 'request reply with error status code' do + before do + expect { run_request_reply_error }.to raise_error(GRPC::BadStatus) + end + + it_behaves_like 'associates child spans with the parent' + + it 'both server and client spans have correct tags' do + server_span = spans.find { |span| span.name == 'grpc.service' } + client_span = spans.find { |span| span.name == 'grpc.client' } + + expect(server_span.get_tag('span.kind')).to eq('server') + expect(server_span.get_tag('rpc.system')).to eq('grpc') + expect(server_span.get_tag('rpc.grpc.status_code')).to eq(3) + expect(server_span.get_tag('rpc.grpc.full_method')).to eq('/ruby.test.Testing/Error') + + # the following tags should be set by backend + expect(server_span.get_tag('rpc.grpc.package')).to eq(nil) + + # the following tags should be set by the backend but they are kept for now to not make breaking changes + expect(server_span.get_tag('rpc.service')).to eq('GRPCHelper::TestService') + expect(server_span.get_tag('rpc.method')).to eq('error') + + expect(client_span.get_tag('span.kind')).to eq('client') + expect(client_span.get_tag('rpc.system')).to eq('grpc') + expect(client_span.get_tag('rpc.grpc.status_code')).to eq(3) + expect(client_span.get_tag('rpc.grpc.full_method')).to eq('/ruby.test.Testing/Error') + + # the following tags should be set by backend + expect(client_span.get_tag('rpc.service')).to eq(nil) + expect(client_span.get_tag('rpc.method')).to eq(nil) + expect(client_span.get_tag('rpc.grpc.package')).to eq(nil) + end end context 'client stream' do diff --git a/spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0/test_service_pb.rb b/spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0/test_service_pb.rb new file mode 100644 index 00000000000..ec85ab3bf56 --- /dev/null +++ b/spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0/test_service_pb.rb @@ -0,0 +1,13 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: test_service.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_message "ruby.test.TestMessage" do + end +end + +module GRPCHelper + TestMessage = Google::Protobuf::DescriptorPool.generated_pool.lookup("ruby.test.TestMessage").msgclass +end diff --git a/spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0/test_service_services_pb.rb b/spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0/test_service_services_pb.rb new file mode 100644 index 00000000000..ac168128aa1 --- /dev/null +++ b/spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0/test_service_services_pb.rb @@ -0,0 +1,26 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# Source: test_service.proto for package 'GRPCHelper' + +require 'grpc' +require 'test_service_pb' + +module GRPCHelper + module Testing + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'ruby.test.Testing' + + rpc :Basic, TestMessage, TestMessage + rpc :Error, TestMessage, TestMessage + rpc :StreamFromClient, stream(TestMessage), TestMessage + rpc :StreamFromServer, TestMessage, stream(TestMessage) + rpc :StreamBothWays, stream(TestMessage), stream(TestMessage) + end + + Stub = Service.rpc_stub_class + end +end diff --git a/spec/datadog/tracing/contrib/grpc/support/gen/test_service_pb.rb b/spec/datadog/tracing/contrib/grpc/support/gen/test_service_pb.rb new file mode 100644 index 00000000000..568a1f3ba07 --- /dev/null +++ b/spec/datadog/tracing/contrib/grpc/support/gen/test_service_pb.rb @@ -0,0 +1,15 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: test_service.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("test_service.proto", :syntax => :proto3) do + add_message "ruby.test.TestMessage" do + end + end +end + +module GRPCHelper + TestMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ruby.test.TestMessage").msgclass +end diff --git a/spec/datadog/tracing/contrib/grpc/support/gen/test_service_services_pb.rb b/spec/datadog/tracing/contrib/grpc/support/gen/test_service_services_pb.rb new file mode 100644 index 00000000000..a4d1eed514f --- /dev/null +++ b/spec/datadog/tracing/contrib/grpc/support/gen/test_service_services_pb.rb @@ -0,0 +1,26 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# Source: test_service.proto for package 'GRPCHelper' + +require 'grpc' +require 'test_service_pb' + +module GRPCHelper + module Testing + class Service + + include ::GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'ruby.test.Testing' + + rpc :Basic, ::GRPCHelper::TestMessage, ::GRPCHelper::TestMessage + rpc :Error, ::GRPCHelper::TestMessage, ::GRPCHelper::TestMessage + rpc :StreamFromClient, stream(::GRPCHelper::TestMessage), ::GRPCHelper::TestMessage + rpc :StreamFromServer, ::GRPCHelper::TestMessage, stream(::GRPCHelper::TestMessage) + rpc :StreamBothWays, stream(::GRPCHelper::TestMessage), stream(::GRPCHelper::TestMessage) + end + + Stub = Service.rpc_stub_class + end +end diff --git a/spec/datadog/tracing/contrib/grpc/support/gen_proto.sh b/spec/datadog/tracing/contrib/grpc/support/gen_proto.sh new file mode 100755 index 00000000000..1a54a6bcb79 --- /dev/null +++ b/spec/datadog/tracing/contrib/grpc/support/gen_proto.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +mkdir -p ./spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0 + +gem install grpc-tools -v 1.19.0 + +grpc_tools_ruby_protoc \ + -I ./spec/datadog/tracing/contrib/grpc/support/proto \ + --ruby_out=./spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0 \ + --grpc_out=./spec/datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0 \ + ./spec/datadog/tracing/contrib/grpc/support/proto/test_service.proto + +gem install grpc-tools + +grpc_tools_ruby_protoc \ + -I ./spec/datadog/tracing/contrib/grpc/support/proto \ + --ruby_out=./spec/datadog/tracing/contrib/grpc/support/gen \ + --grpc_out=./spec/datadog/tracing/contrib/grpc/support/gen \ + ./spec/datadog/tracing/contrib/grpc/support/proto/test_service.proto diff --git a/spec/datadog/tracing/contrib/grpc/support/grpc_helper.rb b/spec/datadog/tracing/contrib/grpc/support/grpc_helper.rb index edd11aee569..a575b72ae49 100644 --- a/spec/datadog/tracing/contrib/grpc/support/grpc_helper.rb +++ b/spec/datadog/tracing/contrib/grpc/support/grpc_helper.rb @@ -1,12 +1,21 @@ require 'grpc' - require 'spec/support/thread_helpers' +if RUBY_VERSION < '2.3' + require_relative './gen/grpc-1.19.0/test_service_services_pb' +else + require_relative './gen/test_service_services_pb' +end + module GRPCHelper def run_request_reply(address = available_endpoint, client = nil) runner(address, client) { |c| c.basic(TestMessage.new) } end + def run_request_reply_error(address = available_endpoint, client = nil) + runner(address, client) { |c| c.error(TestMessage.new) } + end + def run_client_streamer(address = available_endpoint, client = nil) runner(address, client) { |c| c.stream_from_client([TestMessage.new]) } end @@ -38,61 +47,51 @@ def runner(address, client) client ||= TestService.rpc_stub_class.new(address, :this_channel_is_insecure) yield client + rescue StandardError => e + Datadog.logger.debug("GRPC call failed: #{e}") + raise + ensure server.stop until server.stopped?; end t.join end - class TestMessage - class << self - def marshal(_o) - '' - end - - def unmarshal(_o) - new - end - end - end - - class TestService - include GRPC::GenericService - - rpc :basic, TestMessage, TestMessage - rpc :stream_from_client, stream(TestMessage), TestMessage - rpc :stream_from_server, TestMessage, stream(TestMessage) - rpc :stream_both_ways, stream(TestMessage), stream(TestMessage) - + class TestService < GRPCHelper::Testing::Service attr_reader :received_metadata + # rubocop:disable Lint/MissingSuper def initialize(**keywords) @trailing_metadata = keywords @received_metadata = [] end + # rubocop:enable Lint/MissingSuper - # provide implementations for each registered rpc interface def basic(_request, call) call.output_metadata.update(@trailing_metadata) @received_metadata << call.metadata unless call.metadata.nil? - TestMessage.new + GRPCHelper::TestMessage.new + end + + def error(_request, call) + raise GRPC::BadStatus.new_status_exception(GRPC::Core::StatusCodes::INVALID_ARGUMENT) end def stream_from_client(call) call.output_metadata.update(@trailing_metadata) call.each_remote_read.each {} # Consume data - TestMessage.new + GRPCHelper::TestMessage.new end def stream_from_server(_request, call) call.output_metadata.update(@trailing_metadata) - [TestMessage.new, TestMessage.new] + [GRPCHelper::TestMessage.new, GRPCHelper::TestMessage.new] end def stream_both_ways(_requests, call) call.output_metadata.update(@trailing_metadata) call.each_remote_read.each {} # Consume data - [TestMessage.new, TestMessage.new] + [GRPCHelper::TestMessage.new, GRPCHelper::TestMessage.new] end end end diff --git a/spec/datadog/tracing/contrib/grpc/support/proto/test_service.proto b/spec/datadog/tracing/contrib/grpc/support/proto/test_service.proto new file mode 100644 index 00000000000..75446c20a31 --- /dev/null +++ b/spec/datadog/tracing/contrib/grpc/support/proto/test_service.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package ruby.test; +option ruby_package = "GRPCHelper"; + +message TestMessage {} + +service Testing { + rpc Basic(TestMessage) returns (TestMessage) {} + rpc Error(TestMessage) returns (TestMessage) {} + rpc StreamFromClient(stream TestMessage) returns (TestMessage) {} + rpc StreamFromServer(TestMessage) returns (stream TestMessage) {} + rpc StreamBothWays(stream TestMessage) returns (stream TestMessage) {} +} diff --git a/spec/test_service_pb.rb b/spec/test_service_pb.rb new file mode 100644 index 00000000000..35da5acf871 --- /dev/null +++ b/spec/test_service_pb.rb @@ -0,0 +1,7 @@ +# This file is a hack for the grpc protoc autogenerated gen/test_service_services_pb.rb files to work, +# since they add the following import line: require 'test_service_pb' and they wouldn't work otherwise. +if RUBY_VERSION < '2.3' + require_relative 'datadog/tracing/contrib/grpc/support/gen/grpc-1.19.0/test_service_pb' +else + require_relative 'datadog/tracing/contrib/grpc/support/gen/test_service_pb' +end