Add support to use SemanticAttributes with Span.Builder.#1076
Add support to use SemanticAttributes with Span.Builder.#1076Oberon00 wants to merge 2 commits intoopen-telemetry:masterfrom
Conversation
dc23ac3 to
bca7dd7
Compare
bca7dd7 to
5ff9226
Compare
|
What's the disadvantage of adding an overload for each setter to take a // BooleanAttributeSetter
public void set(Span.Builder builder, boolean value) {
builder.setAttribute(key(), value);
} |
|
It's only about losing chaining really. Or rather, having uglier chaining, since you can also do |
| * | ||
| * @param arg the input argument | ||
| */ | ||
| void exec(T arg); |
There was a problem hiding this comment.
On an interface called Acceptor, I'd expect this method to be called accept.
There was a problem hiding this comment.
thinking out loud here, but what if we had this return T, rather than void, making it feel more like a way to extend the builder fluent nature?
| Builder setStartTimestamp(long startTimestamp); | ||
|
|
||
| /** | ||
| * Applies the given consumer to this {@link Builder}. |
There was a problem hiding this comment.
should probably update the doc to call it an acceptor, rather than consumer.
| public Acceptor<Span.Builder> set(final boolean value) { | ||
| return new Acceptor<Span.Builder>() { | ||
| @Override | ||
| public void exec(Span.Builder arg) { |
There was a problem hiding this comment.
rename the arg to builder?
| public Acceptor<Span.Builder> set(final double value) { | ||
| return new Acceptor<Span.Builder>() { | ||
| @Override | ||
| public void exec(Span.Builder arg) { |
| public Acceptor<Span.Builder> set(final long value) { | ||
| return new Acceptor<Span.Builder>() { | ||
| @Override | ||
| public void exec(Span.Builder arg) { |
There was a problem hiding this comment.
you guessed it, rename arg to builder
| public Acceptor<Span.Builder> set(@Nullable final String value) { | ||
| return new Acceptor<Span.Builder>() { | ||
| @Override | ||
| public void exec(Span.Builder arg) { |
| } else if (attribute instanceof BooleanAttributeSetter) { | ||
| keys.add(((BooleanAttributeSetter) attribute).key()); | ||
| spanBuilder.apply(((BooleanAttributeSetter) attribute).set(true)); | ||
| } |
There was a problem hiding this comment.
maybe add an else that calls fail, so that this test will highlight something not being covered?
There was a problem hiding this comment.
IMHO the test for both this and the old SemConv API needs quite some improvements (such as testing that setAttribute was actually called, which is unfortunately not possible with only the API due to Mockito not supporting final classes). But I just copied & adapted the tests for the set method for now.
There was a problem hiding this comment.
I'd be all in favor of making those classes non-final!
|
One more thing to keep in mind is whether we can/should add methods to the public API, i.e. add a new method in |
|
I played around with this a bit locally, and I think the |
Yes, I'd like to play around with this a bit more and see if we can do something similar without increasing the API surface. I like the idea of this change a lot, but am also hesitant to add this to the API. |
|
Or, without changing the API, we could do something like this (which I know isn't quite as fluent): |
|
Having to name the attribute twice is a no-go IMHO. You can forget to use the attribute for the value, it still works; use the wrong attribute (different from key, it still works. Also, this is so cumbersome that I fear people will rather use setAttribute after the span is created, just because the syntax looks so much nicer (and in doing so, deprive the sampler of attribute information). |
|
WDYT? Should I clean up this PR or do we want to look for & decide on another approach? |
|
What about: |
On which class/interface? Are you proposing adding this to the |
|
Yes (and something similar on |
|
@Oberon00 If we want to do #1081, it seems like we should probably solve both of these issues in one implementation. It seems like adding an |
|
Yeah, we'd need to bridge that (get the key/value from the Attribute, and create a new Attribute in the embedded SDK). |
|
@jkwatson So basically we replace the generic Acceptor with an Attribute and instead of |
It enables this usage, which you gave a 👍 to above: OpenTelemetry.getTracerProvider().get("").spanBuilder("foo")
.setAttribute(SemanticAttributes.DB_STATEMENT.withValue("select foo from bar"))
.setAttribute(SemanticAttributes.DB_URL.withValue("jdbc:blah:blah:blah"))
.setAttribute(SemanticAttributes.DB_USER.withValue("username"))
.startSpan(); |
|
You are right, I think this design is viable, but after thinking it through, does it offer any benefit over the one in this PR? |
|
BTW, in Java 8 with Lambdas and Optional.map, we would have more design possibilities. |
I almost never cry "performance" when trying to make an API ergonomics decision, but I do wonder a bit if we impose an extra 2 method invocations per attribute if that might be a performance penalty with some JVMs. That being said, I'm really ok with either option, especially if we're going to remove @carlosalberto @bogdandrutu any opinions? |
I don't think the extra 2 method invocations hurt compared to the extra memory allocation required for the temporary Acceptor/Attribute object. In theory the JVM could do escape analysis on this, but it's nice not to rely on that. That's (partly) why I like passing in the name and value as separate args, instead of creating a temporary object to hold them both, e.g. |
How is this really different than what we have today? That is, what does it buy us over the existing setAttribute methods that take a string key that can be supplied via the existing SemanticAttributes class? We can already do this: |
Gives you type-safety for SemanticAttributes. If type-safety is not a requirement for SemanticAttributes, then SemanticAttributes could just be |
|
@trask I think your suggestion would be the most straightforward and performant design, but it also means that at least the AttributeSetter classes cannot be moved out of the API package. If we are OK with that, that's what I would go for. However, we should probably introduce an abstract base class implementing the Span.Builder interface that takes care of the boilerplate of calling |
Oh yes. Somehow I missed the one actual important part of what you suggested. I think I got distracted by the long names, and missed the point. What if we did this, but just called it |
|
@jkwatson I think we got lost in pseudocode here, the actual name is |
|
If we do #1081 first, then I'm OK with naming it just "Attribute" as there is no potential for confusion anymore, or at least only for people who work with SDK internals. |
|
I'm ok-ish with trying @trask approach, but honestly slightly afraid of adding more boilerplate for helping with some chaining (are users expected to really be setting tags quite often? I'd say 'no', as mostly only people doing instrumentation will, which is mostly 'us'). Similar to #1081 I'd be up for a PR that prototypes it out. |
|
@carlosalberto I am also quite leery about increasing our API surface without some very deep thought and very clear non-trivial impact. It would be good if we had some sort of metric we could apply before adding API surface. Like, "this change will reduce the instrumentation code by 3 lines per span", which means 300 less lines of code in the instrumentation altogether. |
I'll see if I can put together a prototype draft PR for this. Unless someone else wants to get to it before me. |
See #1096 |
| /** | ||
| * A function to be executed with a single argument, having a side effect. | ||
| * | ||
| * <p>Adapted from Java 8's {@code java.util.function.Consumer}. |
There was a problem hiding this comment.
They may be to call it Consumer as well?
There was a problem hiding this comment.
Oracle and Copyright made me think that it might be better to have a different name.
|
This is superseded by now. |
Suggestion on how to resolve #1075.
This design ensures that the API would not depend on a hypothetical contrib-semantic-attributes package. If we don't care about that, we could add overloads taking *AttributeSetter directly to the Span.Builder interface, without needing the Acceptor indirection.
This was designed with chainability in mind, i.e. you can do things like
spanBuilder.setAttribute(foo, bar).apply(ATTR1.set(23)).apply(ATTR2.set(43)).startSpan().