-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gRPC Interceptors #378
gRPC Interceptors #378
Conversation
Looks like there's a Rubocop complaint.
I'll submit a commit to fix that sometime today. A couple question for the reviewer(s):
Thanks,
|
Hey @Jared-Prime thanks for submitting this. Pretty excited about this one! In answer to your questions:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some general feedback, but overall off to a great start here.
I might need to do additional reviews later given there's a lot of rich contextual detail to be absorbed/understood. Hopefully this gets things rolling, though.
service: Datadog.configuration[:grpc][:service_name], | ||
span_type: 'grpc' | ||
} | ||
span = ddtracer.trace(name, tracer_options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question about name
: looks like this value would be server
or client
. Is there a more meaningful name that can be given? That might add a little context to what it's doing?
Secondary consideration; in many of the ddtrace integrations, we often prefix name with the name of the integration. Perhaps grpc.X
might be helpful?
private | ||
|
||
def ddtracer | ||
@ddtracer ||= @options.fetch(:ddtracer, Datadog.tracer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a tracer
option on the gRPC configuration. Should we replace Datadog.tracer
with Datadog.configuration[:grpc][:tracer]
?
include Ext::DistributedTracing | ||
|
||
def self.inject!(context, metadata) | ||
metadata[HTTP_HEADER_TRACE_ID] = context.trace_id.to_s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate the idea of being consistent with the other context propagation headers, but would it make sense to create a new set of constants for gRPC? e.g. GRPC_METADATA_TRACE_ID
/GRPC_TRACE_ID
? Even if the values are roughly the same or equivalent?
Might be a good idea to avoid coupling with HTTP implementation in case there's some divergence in implementation later.
span_type: 'grpc' | ||
} | ||
span = ddtracer.trace(name, tracer_options) | ||
Datadog::GRPCPropagator.inject!(span.context, metadata) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a silly question, but in regards to injecting the context info into the metadata...
If I'm understanding the code correctly, GRPC
is supposed to provide a metadata
Hash via request_response(request: nil, call: nil, method: nil, metadata: nil)
options correct? Is there a case where it doesn't actually provide this metadata Hash?
I ask here because above, metadata ||= {}
will initialize the nil value to a Hash, but the inject!
would end up modifying a local Hash variable that'd end up discarded anyways wouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good question, and I'm not sure about the answer. I'll look it up. My suspicion is that the Hash is always supplied; I'm matching a method signature that I've seen elsewhere. I'll verify what's really needed here. Oh, I get it. Will improve
span = ddtracer.trace(name, tracer_options) | ||
Datadog::GRPCPropagator.inject!(span.context, metadata) | ||
|
||
yield |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since an interceptor is like middleware, does this yield
equivocate to the actual gRPC call? In which case, could we wrap the trace
method around it like:
ddtracer.trace(name, tracer_options) do |span|
yield
end
The benefit you'd get here is automatic span.set_error
and span.finish
functions rendering the rescue
and ensure
blocks as moot.
private | ||
|
||
def ddtracer | ||
@ddtracer ||= @options.fetch(:ddtracer, Datadog.tracer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note that the same general feedback from DatadogClientInterceptor
likely applies here, too.
spec/support/grpc_helpers.rb
Outdated
@@ -0,0 +1,64 @@ | |||
require 'grpc' | |||
|
|||
def run_service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a major thing, but would it be more appropriate to put this method into a module? The module could then be composed into the RSpec examples using include GRPCHelpers
, while having the benefit of not having a global level run_service
method.
require 'ddtrace/tracer' | ||
require 'ddtrace/propagation/grpc_propagator' | ||
|
||
class GRPCPropagatorTest < Minitest::Test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've been working on moving away from Minitest towards RSpec. Does this test benefit from something in Minitest that isn't available or conducive to RSpec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No major benefit of Minitest over RSpec. I will rewrite this in RSpec instead
Thanks @delner. I've refactored a bit according to your recommendations. I also found that my test for bidirectional (bidi) streaming was simply broken; I've fixed it and able to confirm the code should work for bidi streams as well. No rush on reviewing; I will be on vacation the rest of this week and next. I'll look forward to tying up any loose ends when I'm back. |
General thought when looking at this implementation: would it be conceivable to use GRPC to connect to different services? And if that's the case, would it be reasonable to want to trace those GRPC calls as different services? If so, we might want to change how we tag traces with To this end we have the Does GRPC have a "client" or something similar we could pin configuration on? If not, maybe we should think about some strategy to support GRPC calls as different services. |
Also:
|
Just to give you a sense of what the plan is for this one, I think we'd ultimately like to target a 0.13.0 release for this PR. This is a really awesome, but sophisticated feature that warrants some additional performance testing among other due diligence measures. Our 0.12.0 release will be rolling soon, and instead of rushing this in, it'd be good to give this one the time and attention it deserves. We'll be opening a |
I agree with that choice of release target and your proposed steps towards shipping 👍 . I'll be able to give this branch more love this week. |
separate server and client interceptors into different files create a serparate propagator for grpc usage test for bidirectional streaming support earlier inability to test this was due to a typo in the TestService class definition fix bug with GRPCPropagator A miswritten test masked a bug. The GRPCPropagator potentially supplied a value of 0 for a sampling priority when no sampling priority was declared [REFACTOR] implement PR review suggestions - different constants for gRPC metadata - supply tracer object via configuration - supply run_service helper method as part of a module - prefix trace name with "grpc" - use rspec rather than minitest some additional changes include - supply the protocol method name as the datadog resource - supply the grpc peer information as datadog tags - set metadata information as tags fix some testing errors use circleci tests, remove old minitest test
57846c4
to
ebe657d
Compare
closing this in favor of improved implementation at #403 |
This PR provides an implementation for distributed tracing for Ruby gRPC services and clients.
In gRPC nomenclature, an "interceptor" is similar to middleware. When running a service or a client, a programmer must declare intended interceptors for use during runtime; as a result, the Datadog
#patch
implementation merely supplies the constantsGRPC::DatadogClientInterceptor
andGRPC::DatadogServerInterceptor
for use in interceptor registries. Future work may embed tracing in the interceptor call chain itself, making the instrumentation more transparent to the application developer.The following types of gRPC calls have been implemented and tested:
The following types of gRPC have not yet been implemented and/or tested:
I've relied upon Datadog's documentation on their platform as well as documentation of the Datadog Ruby gem, Datadog Python package, the Opentracing.io standard, and the existing Rack implementation in this project. Thanks a million to those contributors, as I couldn't have figured out how to do this without their work.
I plan to use my patch going forward and am grateful for any feedback that will help get this code ready for general usage.