-
Notifications
You must be signed in to change notification settings - Fork 84
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
Integrate OpenTracing #426
Conversation
@prashantv @uber/rpc |
@yurishkuro Looking over the changes now, some high level feedback:
I think the outbound side looks good, just a little unsure about inbound. If the TChannel library is used without any of the |
The problem with that is it will make the solution really tied to Zipkin-style tracing systems, or at least those who support injection carrier matching On the inbound side, if we start the span before app headers are known, we'd have to start it from the frame tracing fields, i.e. only in Zipkin-specific way. Whereas the span created in One dirty way would be to start a span in
Yes, which is in fact the intention, the user code may add other tags/events/baggage to the span.
Correct, there are no "flags" in OpenTracing, so the way these are exposed to |
@prashantv I think this current form does what you want. If the tracer supports zipkin-style span context, then the span is created directly in the I've extended the propagation test to use 3 tracers, the default Noop tracer, Jaeger tracer, and basictracer-go (which does not support zipkin style). |
@yurishkuro Nice! Thanks for making the changes, I definitely like this approach more.
I'm assuming that all opencontext headers are added with a prefix even in TChannel's headers so they won't clash right? Should we put the code to extract/inject tracing into some internal package that can be used by Thrift + JSON. |
We can do it in two different ways. What I've done currently is disabling the sampling of the current trace (or rather span and its children). OT tracers are not obligated to respect the sampling hints. Another approach would be to completely disable all calls to OpenTracing API if disableTracing is requested.
It is duplicated. The headers are read/set via standard OpenTracing API. If we really want to avoid duplication, I can hack it via yet another special Format, like BaggageOnlyTextMapFormat, which will only be known to Jaeger and TChannel, and if the tracer says that format is not supported, then fallback to full span context headers
I can use opentracing.Noop span, by default, but will need another flag to distinguish default from actual span, because
Normally the headers returned by a Tracer are prefixed with tracer-specific strings. E.g. Jaeger's headers are either |
@prashantv @breerly this is ready for review |
@[ ! -s lint.log ] | ||
else | ||
@echo "Skipping linters on" $(GO_VERSION) | ||
endif | ||
|
||
todos: |
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.
suggest moving this to diff PR
"golang.org/x/net/context" | ||
|
||
"github.com/opentracing/opentracing-go/ext" | ||
"github.com/uber/tchannel-go/typed" |
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.
Import groups are off.
@@ -76,9 +76,7 @@ func getTChannelParams(ctx context.Context) *tchannelCtxParams { | |||
|
|||
// NewContext returns a new root context used to make TChannel requests. | |||
func NewContext(timeout time.Duration) (context.Context, context.CancelFunc) { | |||
return NewContextBuilder(timeout). | |||
setSpan(NewRootSpan()). |
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.
When you make a new context, is there any way to know what the trace ID will be for the call you're about to make?
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.
First, trace ID is not a concept that's exposed in OpenTracing API, so strictly speaking - no.
In practice, if there is already an OpenTracing span in the current context, then a child span will be created when the outbound call is made (but not when the context is created). If the tracer is Zipkin-compatible, then technically it's possible to extract trace ID from the current span, and internally the outbound call will do that and store that trace ID in the frame's Trace
fields.
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.
One use case we have is yab
which makes a call from an outbound perspective, but then outputs the traceID (so you can find the trace later). It uses the CurrentSpan(ctx)
after creating a context, so I wonder if every new context should include a new root span. While yab
can be updated, I don't want to break any other uses of this pattern.
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.
The main issue I had with Context doing anything related to spans is that spans can only be created by the Tracer. Passing the tracer to Context methods seemed very wrong.
I think we should revisit the use case of yab
. What you describe sounds like a debugging feature, which is much better handled by configuring the Tracer with a logging reporter (alone or in addition to real span reporter). For example, in Jaeger this is achieved by setting logSpans: true
in the config (or programmatically).
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.
While it is a debugging feature, the TChannel library should allow these use cases. E.g. yab
should have control over how the trace ID is outputted. Passing tracer does seem pretty wrong, would setting a parent span (that the user creates) be a better solution?
There also seems internal users using CurrentSpan(ctx)
, not sure how they're using it or whether they'll be affected.
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.
NB: if returning nil from CurrentSpan is a concern, we could return a pointer to a default/empty Span{}
with 0 IDs.
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.
Sorry I should have been clearer. Returning nil
isn't the issue, it's more that there's existing internal callers to CurrentCall
that might be depending on the trace ID being set as they're about to make an outbound call.
I'll sync up over email about what existing callers are and we can figure out whether they need it to work or not.
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.
In YARPC, we should guarantee that there is a span associated with a response context. We do need tracing to expose an arbitrary string for applications like what @prashantv mentions for tools and logging. Something you can use to associate and search for the trace. It does not have to conform to the Zipkin scheme.
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.
@kriskowal OpenTracing cannot impose any specific restrictions on how a "trace identity" is represented in a string, because it's an implementation detail of the tracer and they vary greatly. However, it is reasonable to require that span.String()
returns something that can be used to locate the trace in the respective UI. It's just that this "something" will be implementation specific. For example, Jaeger spans return a string like this: 73754768284bbd0f:73754768284bbd0f:0:0
. Note that the string will be unique for each span, but the first segment is a common trace id (implementation detail).
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.
It would be satisfying for spans to be Stringers without any requirements for the form of the string. This would reinforce the need for YARPC to return a context. We need a mechanism for YARPC to communicate back the span it created on your behalf. cc @bombela
@prashantv any other comments? |
lgtm. please await stamp from @prashantv. |
// Tracer returns the OpenTracing Tracer for this channel. | ||
// If no tracer was provided in the configuration, returns opentracing.GlobalTracer(). | ||
func (ccc channelConnectionCommon) Tracer() opentracing.Tracer { | ||
if ccc.tracer != nil { |
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.
shouldn't we only need to do this check once when creating the channel rather than everytime Tracer
is called?
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 did it as late binding, in case the global tracer is set after the channel is created
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 don't understand -- you can't change ccc.tracer
after the channel is created right?
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.
correct, but opentracing.GlobalTracer()
can change (by default it returns Noop tracer, not a real one).
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.
Ah, makes sense. Can you update the comment to call that out, so we don't accidentally "optimize" this and break it. (ideally a unit test too).
} | ||
|
||
// PropagationTestSuite is a collection of test cases for a certain encoding | ||
type PropagationTestSuite struct { |
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 test is great, nice work!
Thanks for the benchmark numbers, good to see no regressions. Can you rebase off the latest dev as well? |
ec8fb3b
to
5cc3ee0
Compare
rebased off dev |
a1863bc
to
7cb6e16
Compare
return true | ||
}) | ||
} | ||
carrier.RemoveTracingKeys() |
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.
@prashantv I've added the namespacing feature for tracing keys by using a custom tracingHeadersCarrier
which uses a prefix for all tracing keys. The RemoveTracingKeys()
call restores the application headers to a pre-injection state.
The prefixed and unprefixed keys are cached.
5dc127f
to
2b55b7d
Compare
2b55b7d
to
fcca900
Compare
// tracingKeyPrefix is used to prefix all keys used by the OpenTracing Tracer to represent | ||
// its trace context and baggage. The prefixing is done in order to distinguish tracing | ||
// headers from the actual application headers and to hide the former from the user code. | ||
const tracingKeyPrefix = "$tracing$" |
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.
@prashantv ok to use this prefix?
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.
Yep fine with me, @kriskowal @abhinav are you OK with this as well?
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.
Sure. @kriskowal?
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.
🆗
Code lgtm, there's a few minor comments left to address -- can you address those and then we can merge. |
defer func() { | ||
// restore it on exit | ||
opentracing.InitGlobalTracer(origTracer) | ||
}() |
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.
you can simplify this to:
defer opentracing.InitGlobalTracer(origTracer)
Ready for review
Changes:
opentracing.Tracer
, otherwise it defaults toopentracing.GlobalTracer()
(which is no-op by default)(c *Connection) beginCall
callReq
json/call.go
Response
and finished in a couple of places inoutbound.go
inbound.go
and baggage is added in the encoding handlerinbound.go
TODO:
avoid duplicating trace IDs in the headers and in the frameSpan
if tracer is not zipkin-compatible