Client span keys: suppressing same instrumentation - #3691
Conversation
trask
left a comment
There was a problem hiding this comment.
InstrumentationTypeclass is created to achieve extensibility and we may also use it to improve the registration of custom types. But I'm also happy to shrink it back to enum.
I sent a small PR to your PR, to simplify InstrumentationType a bit (short of making it an enum). I also like the idea of simplifying it all the way to an enum.
It would be good to have a configuration setting to suppress all nested CLIENT spans (preserving current behavior), since some backends don't handle nested CLIENT spans well yet.
| // TODO we should be able to figure it out from the schema_url? | ||
| // but then we'd have multiple versions with different urls | ||
| // and we want to suppress multiple versions of the same instrumentation | ||
| private final InstrumentationType instrumentationType; |
There was a problem hiding this comment.
I think schema_url is something like https://opentelemetry.io/schemas/1.5.0 and doesn't include the semantic convention
There was a problem hiding this comment.
I think http and db schemas may be versioned differently and in the same app you may have instrumentations with different versions (e.g. auto and native or manual instrumentations).
Also schema_url is an arbitrary url that you get Tracer with, so ideally it may be somehow associated with just this instrumentation type (e.g. https://opentelemetry.io/schemas/http/1.5.0). Anyway, that's not how it works now.
| InstrumentationType instrumentationType, | ||
| SpanNameExtractor<? super REQUEST> spanNameExtractor) { | ||
| return new InstrumenterBuilder<>(openTelemetry, instrumentationName, spanNameExtractor); | ||
| return new InstrumenterBuilder<>(openTelemetry, instrumentationName, instrumentationType, spanNameExtractor); |
There was a problem hiding this comment.
Since instrumentationType is only for client spans(?), it would be nice to scope it to just client instrumenters. Let's see if @anuraaga and @mateuszrzeszutek have any thoughts how to do this.
There was a problem hiding this comment.
I recall we thought it would be useful to have a server span key as well, but as a way to enrich it rather than suppress other. Is there a need in java, e.g. in Spring to ever suppress server spans? I remember such cases in .NET world.
Anyway, agree that beyond client spans it's not that important
There was a problem hiding this comment.
oh that makes sense, we do suppress nested SERVER spans in Java instrumentation today. i c now, since ClientSpan creates it's own context keys, those wouldn't overlap with context keys created by ServerSpan 👍
Also the opposite: not suppress anything. |
In SIG last week I thought your use case was capturing HTTP spans under Database spans (and network spans under HTTP spans in the future). And that you didn't need to capture "duplicate" HTTP client spans under another HTTP client span. Though the logic seems nicely centralized with this approach and so adding such an option seems reasonable if someone really wants that capability. |
| instrumentationKeys.put(InstrumentationType.HTTP, ContextKey.named(TYPED_CONTEXT_KEY_PREFIX + InstrumentationType.HTTP)); | ||
| instrumentationKeys.put(InstrumentationType.DB, ContextKey.named(TYPED_CONTEXT_KEY_PREFIX + InstrumentationType.DB)); | ||
| instrumentationKeys.put(InstrumentationType.RPC, ContextKey.named(TYPED_CONTEXT_KEY_PREFIX + InstrumentationType.RPC)); | ||
| instrumentationKeys.put( | ||
| InstrumentationType.MESSAGING, ContextKey.named(TYPED_CONTEXT_KEY_PREFIX + InstrumentationType.MESSAGING)); |
There was a problem hiding this comment.
Hmm, using a map (instead of just static final fields) might make our bridging really complicated (see AgentContextStorage). Perhaps it'd be a bit simpler (from the javaagent perspective) if each InstrumentationTypea had its own ContextKey` field?
e693cd4 to
59b9054
Compare
|
Thanks everyone for comments and suggestions! @mateuszrzeszutek, I followed your idea : #3734 in this version, please take a look |
59b9054 to
f2c8f05
Compare
7486c13 to
691263f
Compare
|
@trask @mateuszrzeszutek this one is ready for review, can you please take a look. It needs #3771 to distinguish server/consumer and pass tests (thanks @mateuszrzeszutek for creating it). |
trask
left a comment
There was a problem hiding this comment.
I sent a PR to your PR, with an idea to rename/restructure InstrumentationType/SuppressingSpanWrapper into SpanKey and SpanSuppressionStrategy.
I think the explicit ContextKey constants in the SpanKey class will make bridging easier (between user/library instrumentation and javaagent instrumentation).
| import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
|
||
| // TODO make class (and fields/methods below) package-private after tracers are gone and no need for | ||
| // bridging from ClientSpan/ServerSpan/ConsumerSpan |
There was a problem hiding this comment.
I think something like ServerSpan#fromContextOrNull() will always be needed - there are many instrumentations that use it to update the server span name.
There was a problem hiding this comment.
this makes sense, we can remove the comment (it was mine)
|
|
||
| // this is used instead of above, depending on the configuration value for | ||
| // otel.instrumentation.experimental.span-suppression-by-type | ||
| // named outgoing because it covers both client and producer spans |
There was a problem hiding this comment.
Previously we didn't suppress PRODUCER spans at all - should we maintain that behavior? Having a PRODUCER -> CLIENT relationship happens a few times in our messaging instrumentations (a producer calls a http/rpc client that actually sends the message to the broker)
There was a problem hiding this comment.
I guess we never suppressed them because there as never 2 producer spans so far and if there were, we'd want to suppress nested ones? With suppression-by-type we'd not suppress HTTP client under messaging producer because types are different. You're right that without it we'd start suppressing them.
BTW, it seems messaging producer can sometimes be client, not sure if we follow it though.
A producer of a message should set the span kind to PRODUCER unless it synchronously waits for a response: then it should use CLIENT.
Anyway, I've separated producer strategy from client and we'd suppress nested producers, but never suppress CLIENT under PRODUCER.
There was a problem hiding this comment.
I guess we never suppressed them because there as never 2 producer spans so far and if there were, we'd want to suppress nested ones?
I that case we probably would want to suppress them.
BTW, it seems messaging producer can sometimes be client, not sure if we follow it though.
🤯
I don't think that we follow it; we always use PRODUCER for all outgoing messaging spans. (Anyway, that's another reason why not using the span kind when suppressing might be a good idea)
| boolean shouldSuppress(SpanKind spanKind, Context parentContext) { | ||
| switch (spanKind) { |
There was a problem hiding this comment.
I'd love to remove the association between span kind and span suppression at some point in the future - but I guess that's still impossible at this point. Maybe when we split our AttributesExtractor implementations into server/client? We've been thinking of doing that for HTTP, we could as well do the same thing for other conventions.
There was a problem hiding this comment.
it seems we'd have to specialize instrumenters (so specific instumenter instance work for one span kind) to remove the association between kind/suppression. Or perhaps, if we move CompositeStrategy into the instrumenter and it would have 4 of them - one per kind, it would not remove the association but maybe a bit cleaner.
Either way, a combination of type + kind is important for suppression (i.e. server HTTP span should not suppress client HTTP span)
Another thought is that all strategies can be the same regardless of kind - suppress same type+kind (or suppress the same kind if type disabled) - it removes the association. I assume it is rare to have nested SERVERs of different types and we can easily allow that.
|
Sorry for being a bit late, but between some additional surprise work and GH issues I didn't manage to look at it yesterday 🙇♂️ Can you rebase your branch with |
c056510 to
bc7ef1f
Compare
trask
left a comment
There was a problem hiding this comment.
thx @lmolkova!!
I have some vague thoughts around naming changes, but will keep thinking and possibly propose some naming changes in a follow-up PR, e.g.
- calling the options "single"/"layered"/"all" (I'm hoping no one really wants the "all", but we'll see...)
- the SpanSuppressionStrategy class is more than just about span suppression, it's also about populating span keys for later enrichment, but I'm not sure how to convey both of those purposes. one possible answer to the naming problem is to split SpanSuppressionStratety into two classes, one that handles suppression and one that handles span key management.
| import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
|
||
| // TODO make class (and fields/methods below) package-private after tracers are gone and no need for | ||
| // bridging from ClientSpan/ServerSpan/ConsumerSpan |
There was a problem hiding this comment.
this makes sense, we can remove the comment (it was mine)
We pretty much only use |
5ad442f to
186ca58
Compare
anuraaga
left a comment
There was a problem hiding this comment.
Just some small points, thanks!
| private static final boolean ENABLE_SPAN_SUPPRESSION_BY_TYPE = | ||
| Config.get() | ||
| .getBooleanProperty( | ||
| "otel.instrumentation.experimental.outgoing-span-suppression-by-type", false); |
There was a problem hiding this comment.
Since this can be overridden through direct call to enableInstrumentationTypeSuppression I think we should use default- in the property name. Or maybe can remove that method
There was a problem hiding this comment.
removing enableInstrumentationTypeSuppression method sounds good 👍
There was a problem hiding this comment.
removing this method makes suppression untestable through instrumetner API. The method is package-private, though, so instrumentations won't be able to call it.
|
🥳 |
This is a draft implementation of client span keys per instrumentations that suppresses spans with the same semantics from being created.
E.g.
Features:
IntrumentationTypepackage-private.