-
Notifications
You must be signed in to change notification settings - Fork 855
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
More user-friendly Context util classes #1189
Conversation
* @return a new context with the given {@link Span} set. | ||
* @since 0.5.0 | ||
*/ | ||
public abstract Context withSpan(Span span); |
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 breaks the layering principle to me, as well as creates a circular dependency between context layer and higher telemetry layers.
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.
Concur with this. Also, the idea is that when adding new concerns/layers, they all have their own Context
related handling code (such as TracingContextUtils
, which we can rename to something better, btw).
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 seems to create a more user-friendly API if we invert the dependency, and make the context depend on the "stuff", instead of making the "stuff" depend on the context:
E.g. here is some code in master:
Context context =
TracingContextUtils.withSpan(
DefaultSpan.create(contextShim.getSpanContext()), Context.current());
context =
CorrelationsContextUtils.withCorrelationContext(
contextShim.getCorrelationContext(), context);
and this is what is looks like in this PR:
Context context =
CurrentContext.get()
.withSpan(DefaultSpan.create(contextShim.getSpanContext()))
.withCorrelationContext(contextShim.getCorrelationContext());
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 could add additional an "user friendly context" layer that depends on both the low level context and "stuff" to provide a more friendly API, if this is deemed important enough.
We already had this in the past, but decided not to go on that route, to avoid double wrapping of |
Overall I'm a little bit confused. I thought the effort to isolate Also, one of the goals of the current Context API as defined in the Specification, is to allow users to define their own |
I recommend to hold off on further review, I'm making some significant changes to address initial feedback above and from @bogdandrutu, I'll post back when ready to re-review. |
Ok, I'm back to being confused. From a user-friendly API perspective I like this PR. But I also totally don't understand all of the implication and desires around context propagation, so keep shooting holes in it 😄. |
Just to continue this experiment, I removed the Tracer and CorrelationContextManager methods that rely on the thread-bound context. This makes it easier to see whether we can invert the dependency (e.g. making the context depend on "stuff") without creating a circular dependency. The results show two notable exceptions:
|
Context is merely a container for key-value pairs. It cannot know about things like Span. |
I converted Context to key-value pairs only. |
Codecov Report
@@ Coverage Diff @@
## master #1189 +/- ##
============================================
+ Coverage 85.08% 85.10% +0.02%
+ Complexity 1174 1160 -14
============================================
Files 149 148 -1
Lines 4378 4384 +6
Branches 405 403 -2
============================================
+ Hits 3725 3731 +6
Misses 494 494
Partials 159 159
Continue to review full report at Codecov.
|
I added back gRPC Context, because this PR has evolved mostly into changes that I think make the API around Context usage nicer, and are worth considering independent of the gRPC Context question. New summary: CurrentContext API (with 5 methods) is single place users need to go for working with thread-bound contexts, replacing:
For users that are not working with thread-bound Contexts, the only API they need is Span.KEY and CorrelationContext.KEY, which define the keys for adding and retrieving Spans and CorrelationContexts from the Context. I saw the note in the specification that says "it is recommended that concerns mediate data access via an API, rather than provide direct public access to their keys", but putting the keys in the interfaces makes them more discoverable/rememberable for users compared to mediating access via a Util class, and it makes user's code more readable, so I think it's at least worth considering, e.g. Using util classes in master:
Using keys in PR:
|
@trask if we expose the
You may argue that we never need to do this :), but I wouldn't design an API with this potential problem. |
Ah, that helps to understand the use case for that 😄. Ok, so I've re-hidden the context keys. This part of the context API is now essentially back to what it is in master (static util class), but with nicer(?) naming. Here's the same example as above from Using util classes in master:
Using nicer(?) named util classes in PR:
|
Actually I have locally changes to also have
The specification explicitly tries to prevent cross-cutting concerns to expose the keys directly, so we keep this as an implementation detail. Also, we would need to potentially add |
This makes me remember some idea we had back in OT about having a low level API (the current API/SDK) and a higher level one (as an extra artifact), which would make things simpler for end users. This would map to something like: try (Scope scope = OpenTelemetry.createSpan("hello")) {
OpenTelemetry.propagate(carrier); // Use the current Context
OpenTelemetry.setSpanAttribute("mykey", "myvalue"); // Use the current Span
// Or access objects directly.
log.info(OpenTelemetry.getCurrentSpan());
log.info(OpenTelemetry.getCurrentCorrelationContext());
} The low-level API would exist for actual instrumentation and advanced scenarios that need fine-tuned code. Otherwise, general users would be advised to use the high level one. Something to consider (and prototype eventually) ;) |
maybe you can add a RATIONALE doc for why this appears to require classloader based configuration (statics)? Otherwise let me know what I'm missing |
@adriancole both thread-bound context usage (statics) and non- thread-bound context usage (non-statics) is supported. I'm not sure if that answers your question? |
when the current context impl is accessed statically, this implies it is configured statically. ex. try (Scope scope = CurrentContext.withSpan(span)) { vs a field or similar try (Scope scope = currentContext.withSpan(span)) { This is the part to focus on. Whether what's configured internally uses an instance bound or static bound thread local or none at all is separate topic. |
particularly when most (all?) code uses static access, this hints at optimizing for this, which is a problem for configuration. Even if non-static is permitted, if in practice everything is written to use statics, and doesn't have a way to override this, it limits applicability. imho code examples like try (Scope scope = CurrentContext.withSpan(span)) { should never be written as they put people into corners that at best rely on static registries. Alternatively, a field is better. In the edge case you want to use static, you can default the ctor to it. try (Scope scope = currentContext.withSpan(span)) { If you believe that static access is better, then basically please put that in a RATIONALE as when this causes problems people can go to one place for the answer. |
As it might not be obvious to folks used to using agents, there are a few main points (non exhaustive, just to jog the mind)
You may not agree with these points, but in your rationale, please mention there are trade offs when the library is biased towards everything static registry. |
@adriancole What if I rename I agree that the word "current" is taking on too much here, as there are lots of ways to configure and pass around a "current" It sounds like your other concern is that the example code is skewed towards using this, rather than injecting and passing around the |
it is less about the name more about the last thing you mentioned. Though I wouldn't say using a field implies end users are doing this manually. First, that something besides static registry is managing something doesn't imply manual. Normal Spring, Guice etc can bind and so I wouldn't call something that is field based manual especially in examples copresented with tracer as a field. Context is an even lower abstraction than tracer.. Secondly, this is less about end users, more about instrumentors. Most end users are only going to be tagging, using automatic features like annotations, etc. not using tracing libraries to perform scoping commands. An api for the minimal surface is a different problem than scoping ops iotw. Make sense? |
the point about audience means, we should encourage behavior that leads to least bugs and config glitches and classloader problems when the audience is instrumentors, as misunderstandings here cause a large blast radius of problems. Examples are how people learn, so we should be careful what they are learning to do. |
For dependency injection, are you talking about an object that has short-lived scope and gets the real
or are you talking about injecting something similar to
|
ps renaming doesnt address the root concern. if getting the instance of
context statically it is better to do something like Context.current()
which returns a context vs expose methods that it has as statics. even if
folks like static initialization it is similar logic to Logger which while
often initialized statically doesnt also expose log as a static.. rather
that's always an instance method.
opting out of static methods helps people do the right thing in normal
cases where you can inject something or abnormal when you cant and thus
rely on static. ack that normal is relative to pov but in any case the
point remains.
hope that helps
…On Tue, May 12, 2020, 11:27 AM Trask Stalnaker ***@***.***> wrote:
@adriancole <https://github.com/adriancole> What if I rename
CurrentContext to ThreadContext to make it clearer that this util class
is only applicable for users who wish to thread-bind their Context?
I agree that the word "current" is taking on too much here, as there are
lots of ways to configure and pass around a "current" Context.
It sounds like your other concern is that the example code is skewed
towards using this, rather than injecting and passing around the Context
manually?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1189 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAPVV6XWDLRG4XLHOCRJVDRRC6YVANCNFSM4M35GGYA>
.
|
@trask I think Adrian is talking about your second example: a "context manager" provided as dependency (similar to tracer provided as dependency), where "context manager" is a pure interface without static methods for getting current context. This allows maximum flexibility in swapping the implementations. It is also compatible with explicit context propagation. |
I think what you are calling A slimmed down current span api for users is a separate thing.. in brave we have SpanCustomizer for this which avoids some problems. (ex a lazy lookup implementation is possible as is a no-op for request based customizations) |
@adriancole @yurishkuro thanks for the feedback. I'll take another stab at it in a few days and post back for more input. |
Latest changes:
It definitely makes dependencies clearer, e.g. having to pass
The circular dependency between the I could remove the That would not keep as clear of a separation for thread-bound context usage though, which was nice about isolating them all inside Though we use "current span" in Tracer already (when creating a new span, inheriting the "current span" as parent), so I guess the separation is not perfect anyways, and bringing back I guess the other option is to just drop those helper methods completely, but that leaves the user doing a lot:
and
(the later isn't so bad, but not as nice as One last thing, along these lines of reducing statics, maybe we should replace usage of |
Closing this. There's some good discussion archived above, and various things prototyped in the PR, but the PR itself is very out-of-date now, and mostly useful as a reference if someone wants to continue moving any of these ideas forward. |
Throwing this out for discussion.
I think it has some interesting benefits: